1

I have a WPF project that loads a window that contains a WebBrowser control. The class that opens the window is made available for COM interoperability.

When running the project as a windows application the window is opened and the WebBrowser control works fine, but when compiled as a class library and COM is used from an external application to open the window, none of the WebBrowser's shortcut keys work. (e.g. CTRL+A, DELETE, CTRL+X, TAB etc.)

This SO Question seems to explain the cause of the issue, but the suggestions there don't work for me as PreProcessMessage or ProcessCmdKey never get called. (unless run as a windows application)

I also read through the links here and here that discuss calling the TranslateAccelerator method. but i am unable to attempt this as none of the KeyDown events I subscribe to are being fired. I've tried WebBrowser.KeyDown, WebBrowser.PreviewKeyDown, and various onkeydown events associated with WebBrowser.Document and WebBrowser.Document.Body. none of these were triggered for me. (unless run as a windows application)

COM visible class

[ProgId("My.Project")]
[ComVisible(true)]
public class MyComVisibleClass : IMyComVisibleInterface
{
    private BrowserWindow myWpfWindow;
    public void OpenWpfWindow()
    {
        ...
        myWpfWindow = new myWpfWindow();
        ...
        myWpfWindow.Show();
    }
}

XAML

<WebBrowser x:Name="EmbeddedBrowser" Focusable="True"    />
<!--I tried using forms host too-->
<!--<WindowsFormsHost Name="wfHost" Focusable="True" >
    <common:WebBrowser x:Name="EmbeddedBrowser" WebBrowserShortcutsEnabled="True"     ObjectForScripting="True"  />
</WindowsFormsHost>-->

WPF browser window

public partial class BrowserWindow : Window
    {
        public BrowserWindow(Uri uri)
        {
            InitializeComponent();
            ...
            EmbeddedBrowser.Focus();
            EmbeddedBrowser.Navigate(uri); 
            ...  
        }
    }
}

What can I do to enable the shortcut keys when opened through COM interop?

Community
  • 1
  • 1
X-Dev
  • 465
  • 1
  • 6
  • 22
  • This may help: http://stackoverflow.com/q/18256886/1768303 – noseratio Nov 26 '14 at 11:12
  • Thanks @Noseratio, but I've already looked at the suggestions there. I've gone through the suggestions there but nothing I've tried so far has worked. – X-Dev Nov 27 '14 at 07:33
  • 1
    Why are you using WinForm's `WebBrowser` (via `WindowsFormsHost`), rather than WPF's [`WebBrowser`](https://msdn.microsoft.com/en-us/library/system.windows.controls.webbrowser%28v=vs.110%29.aspx), directly? – noseratio Feb 13 '15 at 06:16
  • I am using the wpf browser control. the win forms xaml is commented so you can see what i've tried. – X-Dev Feb 14 '15 at 02:24
  • What do you mean under "when opened through COM interop"? Do you host this as an ActiveX control in an unmanaged container (e.g., in VB6)? – noseratio Feb 14 '15 at 02:34
  • a type library is generated using regasm and used by a legacy gui application written in a different language. the object itself is only used to handle events, and invoked methods. – X-Dev Feb 14 '15 at 07:58
  • So, does `myWpfWindow` behaves like a modeless, independent top-level window in that legacy app? Or does it correlate somehow with the rest of the legacy app's GUI? – noseratio Feb 15 '15 at 03:08
  • independent top level window. – X-Dev Feb 15 '15 at 10:44

4 Answers4

2

One real bug in your solution is here:

hHook = SetWindowsHookEx(WH_GETMESSAGE, new HookHandlerDelegate(HookCallBack), (IntPtr)0, GetCurrentThreadId());

The newly allocated delegate new HookHandlerDelegate(HookCallBack) gets garbage-collected at some point, which later leads AccessViolationException. You should keep a strong reference to this delegate until you have called UnhookWindowsHookEx:

this._hookCallBack = new HookHandlerDelegate(HookCallBack);
this.hHook = SetWindowsHookEx(WH_GETMESSAGE, _hookCallBack, (IntPtr)0, GetCurrentThreadId());

That said, I still don't think this is the right approach to tackle the problem. From the comments to question:

So, does myWpfWindow behaves like a modeless, independent top-level window in that legacy app? Or does it correlate somehow with the rest of the legacy app's GUI?


independent top level window.

WPF and Win32 Interoperation (particulary, Sharing Message Loops Between Win32 and WPF) assumes you have control over the Win32 legacy app's code.

Apparently, this is not the case here, therefore I suggest you open this WPF window on a separate UI thread with WPF dispatcher (and its own message loop). This would solve the WebBrowser shortcut issues and, potentially, some other issues as well.

You can use AttachThreadInput to attach the user input queue of the original STA thread (where your COM object lives) to the that of the new WPF thread. There are other aspects, like marshaling COM events and methods calls to the correct thread. The below code illustrates this concept. It's a complete WinForms test app which uses a COM object which, in turn, creates a WPF window with WebBrowser on a dedicated thread.

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;

namespace LegacyWinApp
{
    // by noseratio - https://stackoverflow.com/a/28573841/1768303

    /// <summary>
    /// Form1 - testing MyComVisibleClass from a client app
    /// </summary>
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Load += Form1_Load;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            var comObject = new MyComVisibleClass();

            var status = new Label { Left = 10, Top = 10, Width = 50, Height = 25, BorderStyle = BorderStyle.Fixed3D };
            this.Controls.Add(status);

            comObject.Loaded += () =>
                status.Text = "Loaded!";

            comObject.Closed += () =>
                status.Text = "Closed!";

            var buttonOpen = new Button { Left = 10, Top = 60, Width = 50, Height = 50, Text = "Open" };
            this.Controls.Add(buttonOpen);
            buttonOpen.Click += (_, __) =>
            {
                comObject.Open();
                status.Text = "Opened!";
                comObject.Load("http://example.com");
            };

            var buttonClose = new Button { Left = 10, Top = 110, Width = 50, Height = 50, Text = "Close" };
            this.Controls.Add(buttonClose);
            buttonClose.Click += (_, __) =>
                comObject.Close();
        }
    }

    /// <summary>
    /// MyComVisibleClass
    /// </summary>

    [ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IComObject
    {
        void Open();
        void Load(string url);
        void Close();
    }

    [ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IComObjectEvents
    {
        void Loaded();
        void Closed();
    }

    /// <summary>
    /// MyComVisibleClass
    /// </summary>
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IComObject))]
    [ComSourceInterfaces(typeof(IComObjectEvents))]
    public class MyComVisibleClass : IComObject
    {
        internal class EventHelper
        {
            MyComVisibleClass _parent;
            System.Windows.Threading.Dispatcher _clientThreadDispatcher;

            internal EventHelper(MyComVisibleClass parent)
            {
                _parent = parent;
                _clientThreadDispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
            }

            public void FireLoaded()
            {
                _clientThreadDispatcher.InvokeAsync(() =>
                    _parent.FireLoaded());
            }

            public void FireClosed()
            {
                _clientThreadDispatcher.InvokeAsync(() =>
                    _parent.FireClosed());
            }
        }

        WpfApartment _wpfApartment;
        BrowserWindow _browserWindow;
        readonly EventHelper _eventHelper;

        public MyComVisibleClass()
        {
            _eventHelper = new EventHelper(this);
        }

        // IComObject methods

        public void Open()
        {
            if (_wpfApartment != null)
                throw new InvalidOperationException();

            // start a new thread with WPF Dispatcher
            _wpfApartment = new WpfApartment();

            // attach the input queue of the current thread to that of c
            var thisThreadId = NativeMethods.GetCurrentThreadId();
            _wpfApartment.Invoke(() =>
                NativeMethods.AttachThreadInput(thisThreadId, NativeMethods.GetCurrentThreadId(), true));

            // create an instance of BrowserWindow on the WpfApartment's thread
            _browserWindow = _wpfApartment.Invoke(() => new BrowserWindow(_eventHelper) { 
                Left = 200, Top = 200, Width = 640, Height = 480 });
            _wpfApartment.Invoke(() => _browserWindow.Initialize());
        }

        public void Load(string url)
        {
            if (_wpfApartment == null)
                throw new InvalidOperationException();

            _wpfApartment.Run(async () =>
            {
                try
                {
                    await _browserWindow.LoadAsync(url);
                    _eventHelper.FireLoaded();
                }
                catch (Exception ex)
                {
                    System.Windows.MessageBox.Show(ex.Message);
                    throw;
                }
            });
        }

        public void Close()
        {
            if (_wpfApartment == null)
                return;

            if (_browserWindow != null)
                _wpfApartment.Invoke(() => 
                    _browserWindow.Close());

            CloseWpfApartment();
        }

        void CloseWpfApartment()
        {
            if (_wpfApartment != null)
            {
                _wpfApartment.Dispose();
                _wpfApartment = null;
            }
        }

        // IComObjectEvents events

        public event Action Loaded = EmptyEventHandler;

        public event Action Closed = EmptyEventHandler;

        // fire events, to be called by EventHelper

        static void EmptyEventHandler() { }

        internal void FireLoaded()
        {
            this.Loaded();
        }

        internal void FireClosed()
        {
            _browserWindow = null;
            CloseWpfApartment();            
            this.Closed();
        }
    }

    /// <summary>
    /// BrowserWindow
    /// </summary>
    class BrowserWindow: System.Windows.Window
    {
        System.Windows.Controls.WebBrowser _browser;
        MyComVisibleClass.EventHelper _events;

        public BrowserWindow(MyComVisibleClass.EventHelper events)
        {
            _events = events;
            this.Visibility = System.Windows.Visibility.Hidden;
            this.ShowActivated = true;
            this.ShowInTaskbar = false;
        }

        bool IsReady()
        {
            return (this.Visibility != System.Windows.Visibility.Hidden && _browser != null);
        }

        public void Initialize()
        {
            if (IsReady())
                throw new InvalidOperationException();

            this.Show();
            _browser = new System.Windows.Controls.WebBrowser();
            this.Content = _browser;
        }

        public async Task LoadAsync(string url)
        {
            if (!IsReady())
                throw new InvalidOperationException();

            // navigate and handle LoadCompleted
            var navigationTcs = new TaskCompletionSource<bool>();

            System.Windows.Navigation.LoadCompletedEventHandler handler = (s, e) =>
                navigationTcs.TrySetResult(true);

            _browser.LoadCompleted += handler;
            try
            {
                _browser.Navigate(url);
                await navigationTcs.Task;
            }
            finally
            {
                _browser.LoadCompleted -= handler;
            }

            // make the content editable to check if WebBrowser shortcuts work well
            dynamic doc = _browser.Document;
            doc.body.firstChild.contentEditable = true;
            _events.FireLoaded();
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            _browser.Dispose();
            _browser = null;
            _events.FireClosed();
        }
    }

    /// <summary>
    /// WpfApartment
    /// </summary>
    internal class WpfApartment : IDisposable
    {
        Thread _thread; // the STA thread

        TaskScheduler _taskScheduler; // the STA thread's task scheduler

        public TaskScheduler TaskScheduler { get { return _taskScheduler; } }

        // start the STA thread with WPF Dispatcher
        public WpfApartment()
        {
            var tcs = new TaskCompletionSource<TaskScheduler>();

            // start an STA thread and gets a task scheduler
            _thread = new Thread(_ =>
            {
                // post the startup callback,
                // it will be invoked when the message loop stars pumping
                Dispatcher.CurrentDispatcher.InvokeAsync(
                    () => tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext()), 
                    DispatcherPriority.ApplicationIdle);

                // run the WPF Dispatcher message loop
                Dispatcher.Run();
            });

            _thread.SetApartmentState(ApartmentState.STA);
            _thread.IsBackground = true;
            _thread.Start();
            _taskScheduler = tcs.Task.Result;
        }

        // shutdown the STA thread
        public void Dispose()
        {
            if (_taskScheduler != null)
            {
                var taskScheduler = _taskScheduler;
                _taskScheduler = null;

                if (_thread != null && _thread.IsAlive)
                {
                    // execute Dispatcher.ExitAllFrames() on the STA thread
                    Task.Factory.StartNew(
                        () => Dispatcher.ExitAllFrames(),
                        CancellationToken.None,
                        TaskCreationOptions.None,
                        taskScheduler).Wait();

                    _thread.Join();
                }
                _thread = null;
            }
        }

        // Task.Factory.StartNew wrappers
        public void Invoke(Action action)
        {
            Task.Factory.StartNew(action,
                CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Wait();
        }

        public TResult Invoke<TResult>(Func<TResult> func)
        {
            return Task.Factory.StartNew(func,
                CancellationToken.None, TaskCreationOptions.None, _taskScheduler).Result;
        }

        public Task Run(Action action, CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
        }

        public Task<TResult> Run<TResult>(Func<TResult> func, CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew(func, token, TaskCreationOptions.None, _taskScheduler);
        }

        public Task Run(Func<Task> func, CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew(func, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
        }

        public Task<TResult> Run<TResult>(Func<Task<TResult>> func, CancellationToken token = default(CancellationToken))
        {
            return Task.Factory.StartNew(func, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
        }
    }

    /// <summary>
    /// NativeMethods
    /// </summary>
    internal class NativeMethods
    {
        [DllImport("kernel32.dll", PreserveSig = true)]
        public static extern uint GetCurrentThreadId();

        [DllImport("user32.dll", PreserveSig = true)]
        public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
    }
}
Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    Well i haven't tried keeping a strong reference to HookHandlerDelegate yet, but this does seem like it could be a solution to the exception. Anyway, the separate UI thread with WPF dispatcher was the solution i was looking for. I have not worked with dispatchers before but it seems to be the solution to many of the issues i experienced when trying different experiments with threading to resolve the key input issue. This is a detailed and thorough answer and addresses both issues raised. With links and sample code this is a quality answer. Thank you. – X-Dev Feb 18 '15 at 23:03
1

With help from other developers we got the browser functioning as required.

  1. To get around the key events not firing for the webBrowser control we used SetWindowsHookEx to handle the WH_GETMESSAGE hook.
  2. Since the hook is for every window on the current thread we needed to check the active window then pass a converted struct to the webBrowser control's TranslateAccelerator method.
  3. the problem here is that particular keys were already handled correctly by the webBrowser control and this resulted in the key command being duplicated. A condition was added to not handle these key events.

There is still a bit of testing to do but so far it handles all typing, clipboard, select-all, delete backspace, arrow key commands correctly.

I'm still open to suggestions for improvement, but for now I'm glad it's working.

public partial class BrowserWindow : Window
    {
    public BrowserWindow(Uri uri)
    {
        InitializeComponent();
        ...
        EmbeddedBrowser.Focus();
        EmbeddedBrowser.Navigate(uri); 
        ...  
        EmbeddedBrowser.LoadCompleted+= (sender, args) =>
        {
            ...
            InstallHook();
        }
    }

    ...

    DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, IntPtr windowTitle);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr SetWindowsHookEx(int idHook, HookHandlerDelegate lpfn, IntPtr hInstance, int threadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll")]
    public static extern int GetCurrentThreadId();

    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);

    public delegate IntPtr HookHandlerDelegate(int nCode, IntPtr wParam, IntPtr lParam);

    //Keyboard API constants
    private const int WH_GETMESSAGE = 3;
    private const int WM_KEYUP = 0x101;
    private const int WM_KEYDOWN = 0x0100;
    private const int WM_SYSKEYUP = 0x0105;
    private const int WM_SYSKEYDOWN = 0x0104;


    private const uint VK_BACK = 0x08;
    private const uint VK_LEFT = 0x25;
    private const uint VK_UP = 0x26;
    private const uint VK_RIGHT = 0x27;
    private const uint VK_DOWN = 0x28;

    private List<uint> ignoreKeys = new List<uint>()
    {
        VK_BACK,
        VK_LEFT,
        VK_UP,
        VK_RIGHT,
        VK_DOWN,
    };

    //Remove message constants
    private const int PM_NOREMOVE = 0x0000;

    //Variables used in the call to SetWindowsHookEx
    private IntPtr hHook = IntPtr.Zero;

    private IntPtr HookCallBack(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 || wParam.ToInt32() == PM_NOREMOVE)
        {
            MSG msg = (MSG)Marshal.PtrToStructure(lParam, typeof(MSG));
            if (msg.message == WM_KEYDOWN || msg.message == WM_SYSKEYDOWN || msg.message == WM_KEYUP || msg.message == WM_SYSKEYUP)
            {
                if (!ignoreKeys.Contains((uint)msg.wParam))
                    if (this.IsLoaded && this.IsActive)
                    {
((IKeyboardInputSink)EmbeddedBrowser).TranslateAccelerator(ref msg, ModifierKeys.None);
                        return (IntPtr)1;
                    }
            }
        }
        return CallNextHookEx(hHook, nCode, wParam, lParam);
    }

    private void InstallHook()
    { 
        IntPtr wnd = EmbeddedBrowser.Handle;
        if (wnd != IntPtr.Zero)
        {
            wnd = FindWindowEx(wnd, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero);
            if (wnd != IntPtr.Zero)
            {
                wnd = FindWindowEx(wnd, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero);
                if (wnd != IntPtr.Zero)
                {
                    hHook = SetWindowsHookEx(WH_GETMESSAGE, new HookHandlerDelegate(HookCallBack), (IntPtr)0, GetCurrentThreadId());
                }
            }
        }
    }
}
X-Dev
  • 465
  • 1
  • 6
  • 22
  • According to Hans Passant this is not the way to solve this issue. So i am still in need of a solution here. see SO question for related issue http://stackoverflow.com/questions/28471895/accessviolationexception-in-wpf-com-using-setwindowshookex Although this does work on win7 it causes issues on win8. – X-Dev Feb 12 '15 at 22:15
  • Shouldn't you re-enable the keys when closing the app? – Ross Bush Feb 12 '15 at 22:26
  • well, the code doesn't disable any keys. but in subsequent versions i have been uninstalling the hook on the closing and finalizer methods of the class – X-Dev Feb 12 '15 at 23:11
  • Do not define your own version of `struct MSG`. Use `System.Windows.Interop.MSG` everywhere, including your p/invoke declarations, there's no need to copy it. That will probably fix your `AccessViolationException` problem. That said, depending on what your client application for this COM object is, there might be a better solution. I asked to clarify that in the comments to the question. – noseratio Feb 14 '15 at 04:55
  • I'll try on Monday to use Marshal.PtrToStructure using the System.Windows.Interop.MSG struct instead. I didn't think the pointer could point to the interop msg class since it has a different structure. – X-Dev Feb 14 '15 at 08:12
  • `System.Windows.Interop.MSG` has the correct layout (check [here](http://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Interop/MSG.cs,8a0b462145bdb8d8)) and it matches the unmanaged [`MSG`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms644958%28v=vs.85%29.aspx) structure. Note how it uses `IntPtr` for `LPARAM` and `WPARAM`. – noseratio Feb 14 '15 at 08:53
  • I have updated the code as suggested. I am still however getting an AccessViolationException though. I have done additional testing and nothing seems to have changed. – X-Dev Feb 17 '15 at 04:42
0

It might be simpler to use the Excel WebBrowser Control instead of the System.Windows.Forms WebBrowser; it handles special key forwarding as TAB, DEL, CTRL+V, etc.

For that change the WebBrowser contructor from

new System.Windows.Forms.WebBrowser();

to

new Microsoft.Office.Tools.Excel.Controls.WebBrowser();  

You would need to add references to your project: Project/Add Referernce/Extensions select Microsoft.Tools.Outlook & Microsoft.Tools.Outlook.v4.0.Utilities

Ref: https://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.controls.webbrowser.aspx

bateast
  • 11
  • 2
-1

I don't know if you have time to translate code from Delphi. I had a project in which I could not rely on .NET being installed and had to enable and disable the hooks into the WebBrowser com object. Here is the code in Delphi that worked for me.

unit Browser.Hooks;

interface

uses
    Winapi.Windows, Winapi.Messages, Vcl.Forms;
type
  PKBDLLHOOKSTRUCT = ^TKBDLLHOOKSTRUCT;
  TKBDLLHOOKSTRUCT = packed record
    vkCode: DWORD;
    scanCode: DWORD;
    flags: DWORD;
    time: DWORD;
    dwExtraInfo: DWORD;
  end;

  function DisableWindowsKeys: Boolean;
  function EnableWindowsKeys: Boolean;
  function WindowsKeysDisabled: Boolean;

  function EnableWindowsClicks : Boolean;
  function DisableWindowsClicks : Boolean;
  function WindowsClicksDisabled: Boolean;


const
  WH_KEYBOARD_LL = 13;
  LLKHF_ALTDOWN = $0020;

function MouseProc(nCode: Integer; wParam, lParam: Longint): LongInt; stdcall;
function KeyboardProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): HRESULT; stdcall;


var
  MouseHook: HHook=0;
  KeyboardHook: HHook=0;

implementation
 //-----------------------------------------------------------------------------
function MouseProc(nCode: Integer; wParam, lParam: Longint): LongInt; stdcall;
var
    classbuf: array[0..255] of Char;
const
  ie = 'Internet Explorer_Server';
begin
  Result:=0;
  if(nCode=HC_ACTION)then
  begin
    if((wParam=WM_RBUTTONDOWN) or (wParam=WM_RBUTTONUP))then begin
      //GetClassName(PMOUSEHOOKSTRUCT(lParam)^.HWND, classbuf, SizeOf(classbuf)) ;
      //if lstrcmp(@classbuf[0], @ie[1]) = 0 then
      Result:=HC_SKIP;
    end;
  end;
  if(Result=0) then
    Result := CallNextHookEx(MouseHook, nCode, wParam, lParam) ;
 end; (*MouseProc*)
//------------------------------------------------------------------------------
function KeyboardProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): HRESULT; stdcall;
var
  pkbhs: PKBDLLHOOKSTRUCT;
  isALTDown:boolean;
  isCTLDown:boolean;
  keyCode:Cardinal;
begin
  pkbhs := PKBDLLHOOKSTRUCT(lParam);
  Result:=0;
  if (nCode = HC_ACTION) then
  begin
    isALTDown:=LongBool(pkbhs^.flags and  LLKHF_ALTDOWN);
    isCTLDown:=WordBool(GetAsyncKeyState(VK_CONTROL) and $8000);
    keyCode:=pkbhs^.vkCode;
    case keyCode of
        //VK_ESCAPE : if(isCTLDown or isALTDown) then Result:=HC_SKIP;
        VK_ESCAPE : begin
          if(isALTDown or isCTLDown) then Result:=HC_SKIP;
          if(isCTLDown)then
            Application.Terminate;
        end;

        VK_TAB    : if(isALTDown) then Result:=HC_SKIP;
        VK_SNAPSHOT,
        VK_LWIN,
        VK_RWIN,
        VK_APPS   : Result:=HC_SKIP;
    end;
  end;
  if(Result=0)then
    Result := CallNextHookEx(KeyboardHook, nCode, wParam, lParam);
end;
//------------------------------------------------------------------------------
function DisableWindowsKeys: Boolean;
begin
  if KeyboardHook = 0 then
    KeyboardHook := SetWindowsHookEx(WH_KEYBOARD_LL, @KeyboardProc, HInstance, 0);
  Result := (KeyboardHook <> 0)
end;
//------------------------------------------------------------------------------
function EnableWindowsKeys: Boolean;
begin
  Result := False;
  if (KeyboardHook <> 0) and UnhookWindowsHookEx(KeyboardHook) then
  begin
    KeyboardHook := 0;
    Result := True;
  end;
end;
//------------------------------------------------------------------------------
function WindowsKeysDisabled: Boolean;
begin
  Result := (KeyboardHook <> 0)
end;

//------------------------------------------------------------------------------
function DisableWindowsClicks: Boolean;
begin
  if MouseHook = 0 then
    MouseHook := SetWindowsHookEx(WH_MOUSE_LL, @MouseProc, HInstance, 0);
  Result := (MouseHook <> 0)
end;
//------------------------------------------------------------------------------
function EnableWindowsClicks: Boolean;
begin
  Result := False;
  if (MouseHook <> 0) and UnhookWindowsHookEx(MouseHook) then
  begin
    MouseHook := 0;
    Result := True;
  end;
end;
//------------------------------------------------------------------------------
function WindowsClicksDisabled: Boolean;
begin
  Result := (MouseHook <> 0)
end;
//------------------------------------------------------------------------------
end.
Ross Bush
  • 14,648
  • 2
  • 32
  • 55
  • Thanks for your response, I have tried using the WH_KEYBOARD_LL hook but all of my attempts to pass the keypress to the browser failed. I couldn't find a way to pass the message to the browser TranslateAccelerator unless i'm using the WH_GETMESSAGE hook. – X-Dev Feb 12 '15 at 23:16