WebBrowser keyboard shortcuts - c#

I have a WebBrowser control displaying some HTML.
I want the user to be able to copy the entire document, but not do anything else.
I've set the IsWebBrowserContextMenuEnabled and WebBrowserShortcutsEnabled properties to false, and I want to handle KeyUp and run some code when the user presses Ctrl+C.
How can I do that?
The WebBrowser control doesn't support keyboard events.
I tried using the form's KeyUp event with KeyPreview, but it didn't fire at all.
EDIT: Here's my solution, inspired by Jerb's answer.
class CopyableWebBrowser : WebBrowser {
public override bool PreProcessMessage(ref Message msg) {
if (msg.Msg == 0x101 //WM_KEYUP
&& msg.WParam.ToInt32() == (int)Keys.C && ModifierKeys == Keys.Control) {
DoCopy();
return true;
}
return base.PreProcessMessage(ref msg);
}
void DoCopy() {
Document.ExecCommand("SelectAll", false, null);
Document.ExecCommand("Copy", false, null);
Document.ExecCommand("Unselect", false, null);
}
}

You could try this method as well. Put it in your main form area and it should catch all of the keyboard commands. I use it to add keyboard shortcuts to dynamically created tabs.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
switch (keyData)
{
case Keys.Control|Keys.Tab:
NextTab();
return true;
case Keys.Control|Keys.Shift|Keys.Tab:
PreviousTab();
return true;
case Keys.Control|Keys.N:
CreateConnection(null);
return true;
}
return false;

It is a bug in Windows Forms. Its IDocHostUIHandler.TranslateAccelerator implementation actually tries to send the keystroke to the ActiveX host by returning S_OK after checking WebBrowserShortcutsEnabled and comparing the key data to predefined shortcuts. unfortunately in Windows Forms's keyboard processing, the shortcutkey property is checked during ProcessCmdKey, which means IDocHostUIHandler.TranslateAccelerator returned a little bit too late. That causes anything in the Shortcut enum (e.g. Control+C, Del, Control+N etc) stops working when WebBrowserShortcutsEnabled is set to false.
You can create or find a webbrowser ActiveX wrapper class (e.g. csexwb2) that provides a different IDocHostUIHandler.TranslateAccelerator implementation to check shortcut keys again. The Windows Forms webbrowser control does not allow customizing its IDocHostUIHandler implementation.

you can set a keyboard messages hook to your webbrowser control and filter out keyup keys messages or do some handling for them. Please see if code below would work for you:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, IntPtr windowTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
public const int WH_KEYBOARD = 2;
public static int hHook = 0;
// keyboard messages handling procedure
public static int KeyboardHookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
{
Keys keyPressed = (Keys)wParam.ToInt32();
Console.WriteLine(keyPressed);
if (keyPressed.Equals(Keys.Up) || keyPressed.Equals(Keys.Down))
{
Console.WriteLine(String.Format("{0} stop", keyPressed));
return -1;
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
// find explorer window
private IntPtr FindExplorerWindow()
{
IntPtr wnd = FindWindowEx(webBrowser1.Handle, IntPtr.Zero, "Shell Embedding", IntPtr.Zero);
if (wnd != IntPtr.Zero)
{
wnd = FindWindowEx(wnd, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero);
if (wnd != IntPtr.Zero)
return FindWindowEx(wnd, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero);
}
return IntPtr.Zero;
}
...
// install hook
IntPtr wnd = FindExplorerWindow();
if (wnd != IntPtr.Zero)
{
// you can either subclass explorer window or install a hook
// for hooking you don't really need a window handle but can use it
// later to filter out messages going to this exact window
hHook = SetWindowsHookEx(WH_KEYBOARD, new HookProc(KeyboardHookProcedure),
(IntPtr)0, GetCurrentThreadId());
//....
}
...
hope this helps, regards

After investigating a lot, we came to know it is browser compatibility issue.
We have added meta tag into the HTML page,then shortcuts are working fine. Below is the sample code.
<html>
<body>
<Head>
<meta http-equiv="X-UA-Compatible" content="IE=IE8" />
</head>
<form>
First name:<br>
<input type="text" name="firstname">
<br>
Last name:<br>
<input type="text" name="lastname">
</form></body>
</html>
There are three different solutions for this problem.
Adding meta tag to make the Web site browser compatible.
Override "PreocessCmdKey" method and handle the shortcuts.
Emulate browser by adding the key under FEATURE_BROWSER_EMULATION.
If you don't want to set the meta tag in html code, you can assign meta tag to the Document text property of webbrowser control before navigating the URL. Below is the sample.
//Setting compatible mode of IE.
this.m_oWebBrowser.DocumentText = #"<html>
<head><meta http-equiv=""X-UA-Compatible"" content=""IE=IE8"" /> </head>
<body></body>
</html>";
this.m_oWebBrowser.Navigate("www.google.com");

Related

C# - trigger key down event for active control

I found command System.Windows.Forms.SendKeys.Send() for sending keypress some key. This function work if open external app like a notepad and set focus and I will be see that my Key printed in this text field. How do same but with key down event, System.Windows.Forms.SendKeys.SendDown("A");, for example?
I tried call in Timer this command System.Windows.Forms.SendKeys.Send() but have runtime error associated with very fast taped.
You can't use the SendKeys class for that, unfortunately. You will need to go to a lower level API.
Poking a window with a keydown message
In Windows, keyboard events are sent to windows and controls via the Windows message pump. A piece of code using PostMessage should do the trick:
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
const uint WM_KEYDOWN = 0x0100;
void SendKeyDownToProcess(string processName, System.Windows.Forms.Keys key)
{
Process p = Process.GetProcessesByName(processName).FirstOrDefault();
if (p != null)
{
PostMessage(p.MainWindowHandle, WM_KEYDOWN, (int)key, 0);
}
}
Note that the application receiving these events may not do anything with it until a corresponding WM_KEYUP is received. You can get other message constants from here.
Poking a control other than the main window
The above code will send a keydown to the "MainWindowHandle." If you need to send it to something else (e.g. the active control) you will need to call PostMessage with a handle other than p.MainWindowHandle. The question is... how do you get that handle?
This is actually very involved... you will need to temporarily attach your thread to the window's message input and poke it to figure out what the handle is. This can only work if the current thread exists in a Windows Forms application and has an active message loop.
An explanation can be found here, as well as this example:
using System.Runtime.InteropServices;
public partial class FormMain : Form
{
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport("user32.dll")]
static extern IntPtr AttachThreadInput(IntPtr idAttach,
IntPtr idAttachTo, bool fAttach);
[DllImport("user32.dll")]
static extern IntPtr GetFocus();
public FormMain()
{
InitializeComponent();
}
private void timerUpdate_Tick(object sender, EventArgs e)
{
labelHandle.Text = "hWnd: " +
FocusedControlInActiveWindow().ToString();
}
private IntPtr FocusedControlInActiveWindow()
{
IntPtr activeWindowHandle = GetForegroundWindow();
IntPtr activeWindowThread =
GetWindowThreadProcessId(activeWindowHandle, IntPtr.Zero);
IntPtr thisWindowThread = GetWindowThreadProcessId(this.Handle, IntPtr.Zero);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
IntPtr focusedControlHandle = GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
return focusedControlHandle;
}
}
The good news-- if SendKeys worked for you, then you might not need to do all this-- SendKeys also sends messages to the main window handle.

Capture Key press globally but suppress only for current app and child windows regardless of focus

I'm running into a weird problem.
I have a WinForms application that opens another program (billing system emulator), sets it as a child window and then disables it. This works fine, the user cannot send any keys to the that child window, and the winforms application does its thing, sending commands to the child window.
However, it's been discovered that pushing the shift or control, even if the winforms application doesn't have focus, causes an error in the billing system emulator as they aren't valid keys. Users have taken to not using the shift or control keys while the winforms app runs but that's obviously not a practical solution.
My attempted solution was:
Global keyboard hook to capture when those keys are pressed.
Overriding OnKeyDown in the winforms application to stop those keys.
That however still doesn't solve the problem of the shift and alt keys being sent to the child window when the winforms app is not in focus. I can stop shift and alt globally while the winforms app is running but I don't think that is valid. So I need to somehow in the global hook stop the keypress for the winforms app and its children but allow globally. Any ideas/thoughts?
This is my code.
I don't think there's a good answer for your scenario... =\
Here's a hack you can try. It will "release" Control/Shift if they are down, then you send your message afterwards:
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int extraInfo);
[DllImport("user32.dll")]
static extern short MapVirtualKey(int wCode, int wMapType);
private void button1_Click(object sender, EventArgs e)
{
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
{
keybd_event((int)Keys.ShiftKey, (byte)MapVirtualKey((int)Keys.ShiftKey, 0), 2, 0); // Shift Up
}
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
{
keybd_event((int)Keys.ControlKey, (byte)MapVirtualKey((int)Keys.ControlKey, 0), 2, 0); // Control Up
}
// ... now try sending your message ...
}
This obviously isn't foolproof.
I took a look at the only constructor of the globalKeyboardHook and looks like it is designed only for global hook. You can add another overload to hook into the current running module only like this:
class globalKeyboardHook {
[DllImport("kernel32")]
private static extern int GetCurrentThreadId();
[DllImport("user32")]
private static extern IntPtr SetWindowsHookEx(int hookType, KeyBoardProc proc, IntPtr moduleHandle, int threadId);
[DllImport("user32")]
private static extern int CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);
public globalKeyboardHook(bool currentModuleOnly){
if(currentModuleOnly){
proc = KeyBoardCallback;
//WH_KEYBOARD = 0x2
hhook = SetWindowsHookEx(2, proc, IntPtr.Zero, GetCurrentThreadId());
} else hook();
}
public delegate int KeyBoardProc(int nCode, IntPtr wParam, IntPtr lParam);
public int KeyBoardCallback(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0) {
Keys key = (Keys)wParam;
var lp = lParam.ToInt64();
//your own handling with the key
if ((lp >> 31) == 0)//Key down
{
//your own code ...
} else { //Key up
//your own code ...
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
KeyBoardProc proc;
//other code ...
}
//then use the new overload constructor instead of the parameterless constructor:
globalHook = new globalKeyboardHook(true);
NOTE: You can implement your own KeyDown and KeyUp event based on what I posted above (the comment your own code ...). After some searching I understand that the WH_KEYBOARD_LL supports global hook only while the WH_KEYBOARD is for thread hook only. That should be what you want instead of the WH_KEYBOARD_LL.
BTW, I doubt that the IMessageFilter which can be registered/added by your application can be used in this case. It also supports a PreFilterMessage method helping you to intercept any key and mouse messages at the application-level. You should try searching on that, it's easy to follow.

How do you get a child window using its control ID?

I am new to WINAPI and have figured out how to send a message to another program. The program I am using however I would like to be able to have it click on a specific button. From what I have learned by viewing Spy++ windows handles change for the programs every time they are reloaded and so do the handles for their controls. The control ID stays the same. After two days of trying to figure it out I am here.
under SendMesssageA if I specify the current handle as viewable by Spy++ and use that and run the code it works fine and clicks the button on my external application. I am attempting to use GetDlgItem as I have read that I can get the handle for the control (child window) using it. I am doing something wrong however since no matter what I do it returns 0 or 'null'.
How can I get GetDlgItem to return the child control handle so that I may use it to sendmessage to click that control in the external application?
Thanks for your help an input ahead of time.
[DllImport("User32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
Process[] myProcess = Process.GetProcessesByName("program name here");
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessageA(IntPtr hwnd, int wMsg, int wParam, uint lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetDlgItem(int hwnd, int childID);
public const int WM_LBUTTONDOWN = 0x0201;
public const int WM_LBUTTONUP = 0x0202;
public void SendClick()
{
IntPtr hwnd = myProcess[0].MainWindowHandle;
SetForegroundWindow(hwnd);
int intCID = 1389;
IntPtr ptrTest = GetDlgItem(hwnd, intCID);
SendKeys.SendWait(" ");
Thread.Sleep(1000);
SendKeys.SendWait("various text to be sent here");
Thread.Sleep(1000);
SendKeys.SendWait("{ENTER}");
Thread.Sleep(1000);
SendMessageA(ptrTest, WM_LBUTTONDOWN, WM_LBUTTONDOWN, 0);
}
I think you have to use the Win32 API to find the "receiving" application window, and then find a child window of that handle.
This is something I found googling Win32 API FindWindow
http://www.c-sharpcorner.com/UploadFile/shrijeetnair/win32api12062005005528AM/win32api.aspx

RadioButton click on another form

We have a legacy program with a GUI that we want to use under control of a C# program to compute some values. We can successfully enter values in the numerical input controls, press the compute button, and read the produced answers from text display boxes.
But we can't seem to control a pair of radio buttons .
Calling CheckRadioButton() returns a code of success, but the control does not change state.
Sending a message of BM_CLICK does not change the state.
Attempts at sending WM_LBUTTONDOWN and WM_LBUTTONUP events haven't changed the state.
Has anyone been successful at "remote control" of radio buttons?
Portions of code to illustrate what we are doing:
[DllImport("user32.dll", EntryPoint="SendMessage")]
public static extern int SendMessageStr(int hWnd, uint Msg, int wParam, string lParam);
[DllImport("user32.dll")]
public static extern int SendMessage(int hWnd, uint Msg, long wParam, long lParam);
[DllImport("user32.dll", EntryPoint="FindWindow", SetLastError=true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", EntryPoint="CheckRadioButton")]
public static extern bool CheckRadioButton(IntPtr hwnd, int firstID, int lastID, int checkedID);
static IntPtr GetControlById(IntPtr parentHwnd, int controlId) {
IntPtr child = new IntPtr(0);
child = GetWindow(parentHwnd, GetWindow_Cmd.GW_CHILD);
while (GetWindowLong(child.ToInt32(), GWL_ID) != controlId) {
child = GetWindow(child, GetWindow_Cmd.GW_HWNDNEXT);
if (child == IntPtr.Zero) return IntPtr.Zero;
}
return child;
}
// find the handle of the parent window
IntPtr ParenthWnd = new IntPtr(0);
ParenthWnd = FindWindowByCaption(IntPtr.Zero, "Legacy Window Title");
// set "N" to 10
IntPtr hwndN = GetControlById(ParenthWnd, 17);
SendMessageStr(hwndN.ToInt32(), WM_SETTEXT, 0, "10");
// press "compute" button (seems to need to be pressed twice(?))
int hwndButton = GetControlById(ParenthWnd, 6).ToInt32();
SendMessage(hwndButton, BM_CLICK, 0, 0);
SendMessage(hwndButton, BM_CLICK, 0, 0);
// following code runs succesfully, but doesn't toggle the radio buttons
bool result = CheckRadioButton(ParenthWnd, 12, 13, 12);
Send the BM_SETCHECK message. Be sure to use a tool like Spy++ to see the messages.
in this case i used another message BM_SETSTATE
SendMessage((IntPtr)hWnd, Win32Api.BM_SETSTATE, (IntPtr)newState, IntPtr.Zero);

Sending Keyboard Macro Commands to Game Windows

I wanna do a macro program for a game. But there is a problem with sending keys to only game application (game window). I am using keybd_event API for sending keys to game window. But I only want to send keys to the game window, not to explorer or any opened window while my macro program is running. When I changed windows its still sending keys. I tried to use Interaction.App with Visual Basic.dll reference. But Interaction.App only Focus the game window.
I couldn't find anything about my problem. Can anyone help me? Thanx
i fixed my problem.
in this field ;
PostMessage(hWnd, WM_KEYDOWN, key, {have to give lParam of the key});
otherwise it does not work.And we can control of ChildWindow Class with Spy++ tool of Microsoft.
Thanks everyone for helping.
Are you retrieving the handle of the window all the time, or are you remembering it?
If you use the FindWindow() API, you can simply store the Handle and use the SendMessage API to send key/mouse events manually.
FindWindow API:
http://www.pinvoke.net/default.aspx/user32.FindWindowEx
SendMessage API:
http://www.pinvoke.net/default.aspx/user32/SendMessage.html
VB
Private Const WM_KEYDOWN As Integer = &H100
Private Const WM_KEYUP As Integer = &H101
C#
private static int WM_KEYDOWN = 0x100
private static int WM_KEYUP = 0x101
class SendKeySample
{
private static Int32 WM_KEYDOWN = 0x100;
private static Int32 WM_KEYUP = 0x101;
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, int Msg, System.Windows.Forms.Keys wParam, int lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
public static IntPtr FindWindow(string windowName)
{
foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
{
if (p.MainWindowHandle != IntPtr.Zero && p.MainWindowTitle.ToLower() == windowName.ToLower())
return p.MainWindowHandle;
}
return IntPtr.Zero;
}
public static IntPtr FindWindow(IntPtr parent, string childClassName)
{
return FindWindowEx(parent, IntPtr.Zero, childClassName, string.Empty);
}
public static void SendKey(IntPtr hWnd, System.Windows.Forms.Keys key)
{
PostMessage(hWnd, WM_KEYDOWN, key, 0);
}
}
Calling Code
var hWnd = SendKeySample.FindWindow("Untitled - Notepad");
var editBox = SendKeySample.FindWindow(hWnd, "edit");
SendKeySample.SendKey(editBox, Keys.A);
If you want to communicate with a game, you typically will have to deal with DirectInput, not the normal keyboard API's.

Categories