views:

308

answers:

3

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);
    }
}
A: 

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

serge_gubenko
+2  A: 

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;
Jerb
This almost works, but I can't get a message for CTRL+C. If I press C, `keyData` is `C`, but if I press Control, with or without another key, `keyData` is `LButton | ShiftKey | Control`, and there's no way to determine which other key, if any, was pressed.
SLaks
The last case statement "case Keys.Control|Keys.N" is doing exactly what you are trying to do. The | is used to catch multiple keys. so "case Keys.Control|Keys.C:" should do the trick.
Jerb
+1  A: 

You need to implement IDocHostUIHandler.TranslateAccelerator[1] and return S_FALSE to let the host instead of the webbrowser control to handle the key message. If Windows Forms's implementation of [IDocHostUIHandler.TranslateAccelerator], which checks the WebBrowserShortcutsEnabled first, then check if the message is WM_CHAR and the key is System.Windows.Forms.Shortcut, does not work for you, you need to create or find a webbrowser ActiveX wrapper class because a bug in Windows Forms prevent you from overriding its IDocHostUIHandler implementation.

Sheng Jiang 蒋晟