【问题标题】:WebBrowser control's shortcut keys are not workingWebBrowser 控件的快捷键不起作用
【发布时间】:2015-01-24 07:37:33
【问题描述】:

我有一个 WPF 项目,它加载一个包含 WebBrowser 控件的窗口。 打开窗口的类可用于 COM 互操作性。

当将项目作为 Windows 应用程序运行时,会打开窗口并且 WebBrowser 控件工作正常,但是当编译为类库并从外部应用程序使用 COM 来打开窗口时,WebBrowser 的 shortcut keys 不起作用. (例如 CTRL+A、DELETE、CTRL+X、TAB 等)

This SO Question 似乎解释了问题的原因,但那里的建议对我不起作用,因为 PreProcessMessage 或 ProcessCmdKey 永远不会被调用。 (除非作为 Windows 应用程序运行)

我还阅读了讨论调用 TranslateAccelerator 方法的链接 herehere。但我无法尝试这样做,因为我订阅的 KeyDown 事件都没有被触发。 我已经尝试过 WebBrowser.KeyDown、WebBrowser.PreviewKeyDown 以及与 WebBrowser.Document 和 WebBrowser.Document.Body 相关的各种 onkeydown 事件。这些都不是我触发的。 (除非作为 Windows 应用程序运行)

COM 可见类

[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 浏览器窗口

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

通过 COM 互操作打开时如何启用快捷键?

【问题讨论】:

  • 这可能会有所帮助:stackoverflow.com/q/18256886/1768303
  • 感谢@Noseratio,但我已经看过那里的建议。我已经完成了那里的建议,但到目前为止我尝试过的任何方法都没有奏效。
  • 你为什么直接使用WinForm的WebBrowser(通过WindowsFormsHost),而不是WPF的WebBrowser
  • 我正在使用 wpf 浏览器控件。 win 表单 xaml 已注释,因此您可以查看我尝试过的内容。
  • “当通过 COM 互操作打开时”是什么意思?您是否将此作为 ActiveX 控件托管在非托管容器中(例如,在 VB6 中)?

标签: c# .net wpf com webbrowser-control


【解决方案1】:

your solution 中的一个真正的错误在这里:

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

新分配的委托new HookHandlerDelegate(HookCallBack) 在某个时候被垃圾收集,后来导致AccessViolationException。在你调用UnhookWindowsHookEx之前,你应该保持对这个委托的强烈引用:

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

也就是说,我仍然认为这不是解决问题的正确方法。从 cmets 到问题:

那么,myWpfWindow 是否表现得像一个无模式、独立的顶层? 那个旧版应用程序中的窗口?或者它是否与其余部分相关 旧版应用程序的 GUI?


独立的顶层窗口。

WPF and Win32 Interoperation(尤其是Sharing Message Loops Between Win32 and WPF)假定您可以控制 Win32 旧版应用程序的代码。

显然,这里不是这种情况,因此我建议您使用 WPF 调度程序(及其自己的消息循环)在单独的 UI 线程上打开此 WPF 窗口。这将解决 WebBrowser 快捷方式问题,并且可能还解决其他一些问题。

您可以使用AttachThreadInput 将原始 STA 线程(您的 COM 对象所在的位置)的用户输入队列附加到新 WPF 线程的用户输入队列。还有其他方面,例如将 COM 事件和方法调用编组到正确的线程。下面的代码说明了这个概念。这是一个完整的 WinForms 测试应用程序,它使用一个 COM 对象,然后在专用线程上创建一个带有 WebBrowser 的 WPF 窗口。

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

【讨论】:

  • 好吧,我还没有尝试保持对 HookHandlerDelegate 的强引用,但这似乎可以解决异常。无论如何,带有 WPF 调度程序的单独 UI 线程是我正在寻找的解决方案。我以前没有与调度员合作过,但这似乎是我在尝试不同的线程实验以解决关键输入问题时遇到的许多问题的解决方案。这是一个详细而彻底的答案,并解决了提出的两个问题。通过链接和示例代码,这是一个高质量的答案。谢谢。
  • @X-Dev,很高兴它有帮助。
【解决方案2】:

在其他开发人员的帮助下,我们使浏览器按要求运行。

  1. 为了避开未触发 webBrowser 控件的关键事件,我们使用 SetWindowsHookEx 处理 WH_GETMESSAGE 挂钩。
  2. 由于钩子适用于当前线程上的每个窗口,我们需要检查活动窗口,然后将转换后的结构传递给 webBrowser 控件的 TranslateAccelerator 方法。
  3. 这里的问题是 webBrowser 控件已经正确处理了特定的键,这导致键命令被重复。添加了一个不处理这些关键事件的条件。

还有一些测试要做,但到目前为止它可以正确处理所有打字、剪贴板、全选、删除退格、箭头键命令。

我仍然愿意接受改进建议,但现在我很高兴它正在发挥作用。

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());
                }
            }
        }
    }
}

【讨论】:

  • 根据 Hans Passant 的说法,这不是解决此问题的方法。所以我仍然需要一个解决方案。有关相关问题,请参阅 SO question stackoverflow.com/questions/28471895/… 虽然这在 win7 上有效,但在 win8 上会导致问题。
  • 关闭应用时不应该重新启用按键吗?
  • 好吧,代码不会禁用任何键。但在后续版本中,我一直在卸载类的关闭和终结器方法上的钩子
  • 不要定义您自己的struct MSG 版本。在任何地方使用System.Windows.Interop.MSG,包括您的 p/invoke 声明,无需复制它。这可能会解决您的AccessViolationException 问题。也就是说,根据您的 COM 对象的客户端应用程序是什么,可能会有更好的解决方案。我要求在问题的 cmets 中澄清这一点。
  • 我将在星期一尝试使用 Marshal.PtrToStructure 来代替 System.Windows.Interop.MSG 结构。我不认为指针可以指向 interop msg 类,因为它具有不同的结构。
【解决方案3】:

使用 Excel WebBrowser 控件而不是 System.Windows.Forms WebBrowser 可能更简单;它处理特殊键转发,如 TAB、DEL、CTRL+V 等。

为此更改 WebBrowser 构造函数

new System.Windows.Forms.WebBrowser();

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

您需要添加对项目的引用: 项目/添加参考/扩展选择 Microsoft.Tools.Outlook & Microsoft.Tools.Outlook.v4.0.Utilities

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

【讨论】:

    【解决方案4】:

    我不知道你是否有时间翻译 Delphi 的代码。我有一个项目,我不能依赖正在安装的 .NET,并且必须启用和禁用 WebBrowser com 对象的挂钩。这是 Delphi 中对我有用的代码。

    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.
    

    【讨论】:

    • 感谢您的回复,我尝试使用 WH_KEYBOARD_LL 挂钩,但我所有将按键传递给浏览器的尝试都失败了。除非我使用 WH_GETMESSAGE 挂钩,否则我找不到将消息传递给浏览器 TranslateAccelerator 的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    相关资源
    最近更新 更多