WebBrowser control on MFC property page - need to handle TAB key presses - c#

I'm trying to host the .NET Windows Forms WebBrowser control on an MFC property page. To do this I'm using the CWinFormsControls class. This all works quite nicely, but since I'm on a property page there seems to be a problem with the property sheet consuming TAB and RETURN key presses to perform navigation or default key press behaviour, i.e. pressing the TAB key when the web browser control has focus does not move focus to the next control in the web page (the page has a username and password edit box plus a 'Submit' button and I'd like TAB to move between the boxes on the page and for RETURN to submit). End users aren't going to be satisfied with this mouse-only behaviour so I need the web browser to handle TAB and RETURN keys rather than the property sheet.
After some research - I'm a novice when it comes to MFC and WinForms - I think the problem may be that the property page is consuming these keys thanks to IsDialogMessage, which internally sends a WM_GETDLGCODE message to the web browser and the web browser only returning DLGC_WANTARROWS and DLGC_WANTCHARS, but crucially not returning DLGC_WANTTAB. So the web browser never sees the TAB key to handle it.
So I've tried many things, but the most promising approach appeared to be deriving my own control from WebBrowser and then overriding the WndProc function to explicitly handle WM_GETDLGCODE messages and request the TAB key:
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x87 /* WM_GETDLGCODE */) {
m.Result = (IntPtr)0x83; // DLGC_WANTARROWS|DLGC_WANTTAB|DLGC_WANTCHARS
} else {
base.WndProc(ref m);
}
}
And this function does actually get called. However, the TAB keys still don't work. Attaching Spy++ to the web browser window reveals that despite appearing to handle WM_GETDLGCODE, the response isn't as expected:
Which I can't explain and obviously doesn't solve the problem.
So, am I going about this the right way? Is there a 'correct' solution to this problem?
I've tried using PreTranslateMessage to explicitly intercept TAB and RETURN key presses and somehow (SendKeys API) send them to the web browser control. This worked for TAB keys but caused a buffer overrun for the RETURN key. So no luck there and it felt like a nasty hack anyway.
This post is already getting long. If you need any more info then just ask. All help very gratefully appreciated.

After some research it seems that the better solution was to derive my own class from CPropertyPage and have that override PreTranslateMessage as:
BOOL CMyPage::PreTranslateMessage(MSG* pMsg)
{
return CDialog::PreTranslateMessage(pMsg);
}
i.e. make the property page behave as though it's a regular dialog. The reason for this is that property pages defer to the parent property sheet to handle key presses such as TAB and RETURN.
With this minor change my web browser control is getting all of the key presses and behaving as intended.

In case this helps someone, I was running my webBrowser in a modeless dialog. In the PretranslateMessage I had to do this for tabs, delete-keys and Enter-key to work correctly:
BOOL CMyDialog::PreTranslateMessage(::MSG *pMsg)
{
if (System::Windows::Interop::ComponentDispatcher::RaiseThreadMessage(*reinterpret_cast<System::Windows::Interop::MSG*>(pMsg)))
return TRUE;
else
return CVSimWnd::PreTranslateMessage(pMsg);
}

Related

How to create a custom .NET ComboBox with the textbox active during edit

I'm trying to implement my own combobox like a lot of folks before me. What I want to accomplish is a combobox that filters and highlights items in the dropdown list while the user is typing in the combo textbox. The behaviour of a regular combobox after you click the arrow button is that the dropdown pops up and the focus stays in the textbox. This way you can start typing right away.
In order to customize the dropdown control you have to implement something from scratch. Most of the implementations that I've come across use either a Form or a ToolStripDropDown to host the custom control. Both are toplevel controls which means that you have to somehow close it yourself if the user clicks somewhere outside the dropdown. ToolStripDropDown does this automatically if AutoClose is true, but also somehow steals the combo textbox the focus on show if it is activated. A Form must be shown using ShowWithoutActivation() in order to prevent it from stealing the focus.
The problem is that the dropdown does never close unless I click somewhere within the dropdown and therefore activate it.
Another twist is that the combobox control is supposted to be hosted in an MFC application instead of a pure WinForms app.
The dropdown never being activated (gaining focus) is the main complication here. Otherwise you could just use the forms Deactivate event to hide it. The way to go here is to add an IMessageFilter to the WinForms application and catch mouse click messages. The message filter then determines whether the click took place outisde of the dropdown and closes it. If you are creating a WinForms application you are done.
Some extra work is necessary if you are for example in a MFC application hosting the control in a MFC window. In that case your IMessageFilter is useless. The reason is that the WinForms Application is never being run and therefore the event pump is never being invoked. Instead the MFC message pump does all the message handling. To solve this issue I've come accross a neat trick to activate the Application message pump in MFC applications.
In MFC applications there is usually an equivalent to the WinForms Application which is CWinApp (or CWinAppEx). The trick is to tap into the PreTranslateMessage method and serve the WinForms Application message pump before (or after) the MFC message pump:
BOOL CWinApp::PreTranslateMessage(MSG* pMsg)
{
if (FilterWindowsFormsMessages(pMsg))
{
return TRUE;
}
return CWinApp::PreTranslateMessage(pMsg);
}
BOOL CWinApp::FilterWindowsFormsMessages(MSG* pMsg)
{
Message message = Message::Create(IntPtr(pMsg->hwnd),
int(pMsg->message),
IntPtr((void*)pMsg->wParam),
IntPtr((void*)pMsg->wParam));
if (Application::FilterMessage(message))
{
return TRUE;
}
return FALSE;
}
This way the registered IMessageFilters are being served and everything works fine.

custom onbeforeunload alert/message

As many people, I tried to look for a solution to customize the onbeforeunload pop up window but I came up with no solution
So I was wondering, is it possible to hide this onbeforeunload (or make it not appear) and make a custom alert/pop up doing the same and customized?
You can't do that, if you add an alert to the onbeforeunload event that will give you a confirm message. if you were to stop that from happening, the page would just close no matter what you put there instead.
all you can do is add a message to the alert which is displayed when asking if you want to stay on the page or leave, even then that is ignored in some browsers.
function promptMessage() {
return "==============================\n\nINFO!!\n\n Please wait on this page until bla bla etc.\n\n==============================";
}
window.onbeforeunload = promptMessage;
The reason you can't and never will be able to do this is because it allows the site owner to stop someone from leaving or bombarding the end user when they try to leave.
Really, its a good thing that this is not allowed.
Myself, I do use onbeforeunload when a user has entered information into a form for example but has not saved that information when they try to leave or click on a link...
Onbeforeunload is a pain in th a** so I can of modify it (just the message) or know when it going to be fired (like clicking a button that goes to other page) and with javascript, disable it ( = null) and make a custom div that was hidden appear to look like a pop up.

Possible to simulate click event on Dialog window of Browser?

I know that the dialog(showMessage) is a closed API and that you can not force a click event on Dialog with any web-based technologies such as jQuery or Javascript. The instance of the window within the browser is single threaded and locks the thread until the dialog receives an event. This I understand.
What I am trying to do is simulate a click event pragmatically for Test Case purposes. I am using the Telerik testing framework to run these Test Cases in C# .NET 4.5 environment.
So is it possible to simulate this click event? It is testing the behavior of one our buttons that when clicked the user must confirm they are leaving the page without saving changes.
Thanks to all in advance!
I am not familiar with Telerik's testing tools, but as far as i know the only way to "issue" such a click would be with a ui macro that automated mouse motions and actually clicked the screen at a particular location.
That said, you may be able to solve your problem by using a mocked method. Rather than directly calling window.prompt, instead define your own prompt function along the lines of:
debug = true; //remove or set to false when not testing
var myPrompt = function(){
if(debug){
return "Greetings, Program";
} else {
return prompt("Please enter your greeting:","Greeting");
}
}
You can naturally set this up for other types of message box, so long as you keep the type they return in mind.

Winform App - Webpage Interaction

Windows Form Application – Manipulating input-elements in WinForm WebBrowser
Although I am familiar with HttpWebResponse/HttpWebRequest to login to a website, I was trying it now via using the mshtml library and found some weird behavior and I would like to see if someone else might be able to help me out here..
I have an HTML login page with a java backend with a Username field, a Password field and a Button.
The logic is very basic, I have a built a winform app with a built in webbrowser.
At the Document_Completed event I use the following code to enter my settings and to click the button.
private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser.Url.ToString() == #"MyWebPage/signin")
{
HTMLDocument hdc = new HTMLDocumentClass();
hdc = (HTMLDocument)webBrowser.Document.DomDocument;
IHTMLElement elb = hdc.getElementById("login_button");
IHTMLInputElement elu = (IHTMLInputElement)hdc.getElementById("username");
IHTMLInputElement elp = (IHTMLInputElement)hdc.getElementById("password");
try
{
elu.value = "MyID";
elp.value = "MyPwd";
elb.click();
}
catch { }
}
}
Apart for this code being very quick and without error handling, it should do the trick and it does, partially..
There are two scenario's:
I launch the tool, it loads the webpage.
The tool populates the UserID field and the Password field correctly
The tool fails to click the button
I click the button manually, I am logged in, I click logout, I am back at login page
I immediatly logged in again, the tool enters the information
The tool immediatly clicks the button as well.
Is there anyone who might be able to explain me why this happens and how I could get around this with the current setup (hence not using HttpWebRequest). I don't see the difference between loading the page at startup or being redirected after logout, but apparently there is a difference in there or I am doing something wrong.
Any feedback on this matter is very much appreciated.
Thanks,
Kevin
EDIT:
I added a Button to my Windows Form that bas the same backend Code as below in order to click the button on the webpage, this works perfectly.
I triggered clicking this button in the webBrowser_Completed event but it doesn't work.
For some reason, everything I add to the webBrowser_DocumentCompleted event does not allow me to trigger the click event for the button in my WebBrowser control. Once that entire event has completed, if I then try to trigger it it works but I would like to automate this.. Any advice?
This might be a long shot and not the most elegant workaround but how about letting a backgroundworker run for a second in your DocumentCompleted event that then triggers the button that you clicked from it's seperate thread. This might just get this automated.
As this will run from a different thread, keep in mind that you might have to invoke certain controls so this might be another downside to this workaround..
If this doesn't work then, as Regfor previously suggested, Watin.org can help you out.
how about this :
HtmlElement button = webBrowser.HtmlDocument.GetElementById("login_button");
button.InvokeMember("click");
it works in my program.

Sharepoint Ribbon: Circumventing Context sensitivity

I'm new to sharepoint development and I'm trying to modify the behaviour of the Sharepoint ribbon. As you all know, the ribbon is such that when something else gains focus(e.g a list item), the ribbon automatically switches to an appropriate tab or tab group(e.g the List tools tab group).
I'd like to disable this constant switching of tabs and make the browse tab to always be the active tab, unless the user explicitly clicks on another tab.
I've tried doing the following in the Page_Load() of a Usercontrol, but it only works once, when the page is initially loaded. What am I doing wrong? More importantly, how could I do it right, if at all?
Basically, I'm hoping someone could point me to the event that's fired when the context changes and the ribbon switches, and how I could hook up to this event and force the ribbon to switch back to the browse tab.
protected void Page_Load()
{
string showBrowseTabScript = string.Empty;
showBrowseTabScript = #"
function ShowBrowseTab() {
var ribbon = SP.Ribbon.PageManager.get_instance().get_ribbon();
SelectRibbonTab(""Ribbon.Read"", true);
}
SP.SOD.executeOrDelayUntilScriptLoaded(function() {
var pm = SP.Ribbon.PageManager.get_instance();
pm.add_ribbonInited(function() {
ShowBrowseTab();
});
var ribbon = null;
try
{
ribbon = pm.get_ribbon();
}
catch (e) { }
if (!ribbon) {
if (typeof(_ribbonStartInit) == ""function"")
_ribbonStartInit(_ribbon.initialTabId, false, null);
}
else {
ShowBrowseTab();
}
},
""sp.ribbon.js"");
";
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "BrowseTabScript", showBrowseTabScript, true);
}
Here is my solution to the problem, in case anyone is interested.
Taking Ken Henderson's suggestion into consideration, I was able to achieve what I've been trying to do, although I achieved this by modifying the code of the SP.Ribbon.js and SP.Ribbon.debug.js files. I'm using the SP.Ribbon.debug.js to show my solution below, since it is not as cryptic as the SP.Ribbon.js.
Basically, I use the code below to trick the ribbon into thinking that the User is on a different tab and has clicked on the "Browse" tab. You will notice that I set the old tab information in the code. It will still work without me doing this, but I did it just in case the ribbon needs that information for something else I'm not aware of. This code, in combination with the Page_Load() function I posted in the first post, cause the ribbon to behave just like I needed it to.
SP.Ribbon.PageManager.prototype = {
executeRootCommand: function (commandId, properties, commandInfo, root) {
ULSMg8: ;
var $v_0;
if (!SP.ScriptUtility.isNullOrUndefined(commandInfo) && commandId !== 'RibbonEvent' && (commandId !== 'CommandContextChanged' || (!SP.ScriptUtility.isNullOrUndefined(properties) && properties['ChangedByUser']))) {
// My changes to SP.Ribbon
if (properties["ChangedByUser"] === false) {
properties["ChangedByUser"] = true;
var $NewContextId = properties["NewContextId"];
var $NewContextCommand = properties["NewContextCommand"];
properties["OldContextId"] = $NewContextId;
properties["OldContextCommand"] = $NewContextCommand;
properties["NewContextId"] = "Ribbon.Read";
properties["NewContextCommand"] = "ReadTab";
SelectRibbonTab("Ribbon.Read", true);
}
// End of changes to SP.Ribbon
// the rest of the code has been ommitted for clarity
return $v_0;
}
}
To the best of my knowledge SharePoint doesn't expose any events to detect when the ribbon tabs update (either tabs adding/removing or which is active). At least I was unable to find any a few weeks ago when I was trying to detect when tabs were added/removed (I didn't care which was active just the number/width of them).
(Sorry for the lack of details, the SharePoint dev environment at the office is unavailable at the moment so I can't look up the details very easily.)
There are two possiblities for solving this problem (each has risks/problems):
Override JS Functionality
Figure out what JavaScript function is being called when the user clicks on an item that updates the ribbon. You might be able to replace that function with your own that provides the behavior you want. This would be similar in concept to a custom master page that scrolls on the window and has to change the behavior of the width sizing. I'm unable to verify the details at the moment but it looks like the function is called SingleItemSelect in core.js.
This could be a problem if you have exceptions to when to override this behavior and if MS changes anything in the future you're implementation may break and/or need to be updated.
Add your own event handler
In your JavaScript code try to find an appropriate DOM event to attach an event handler to in the ribbon to detect when MS's code changes the ribbon. There is a good chance given the limitations of the DOM events that there will not be an event to attach a handler to. You may end up adding a function that is called periodically (polling loop/timer) that detects ribbon tab changes and resets the active one.
Honestly this will not work well since there will be flickering as MS's ribbon code changes the active tab and your's changes it back. Additionally you'll need to detect when the user clicks on a tab so that you don't undo their changes.
Wrap up
Honestly I would push back and get this requirement changed so that the ribbon behavior works the way MS designed and not try to fight it. If the ribbon showing up when the user clicks on an item is really an issue then I would propose to the client that instead of forcing the Browse tab as active to add an additional link in the ribbon area somewhere that allows the ribbon (or at least the part that expands over the title area) to be toggled as hidden/shown independently of what MS's JavaScript is doing to the ribbon.

Categories

Resources