WebBrowser控件的快捷键不工作快捷键、控件、工作、WebBrowser

2023-09-06 10:41:25 作者:年轻不嗨何时嗨

我有一个WPF项目加载一个包含WebBrowser控件的窗口。 打开的窗口中的类实现COM互操作性提供。

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.

在运行该项目作为Windows应用程序的窗口将打开,WebBrowser控件工作正常,但是当编译为一个类库和COM从外部应用程序来打开窗口,没有任何web浏览器的的快捷键工作。 (例如Ctrl + A,DELETE,CTRL + X,TAB等)

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.)

此等问题似乎可以解释这个问题的原因,但建议不要我作为preProcessMessage或ProcessCmdKey从来没有得到所谓的工作。 (除非作为Windows应用程序运行)

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)

我通过链接也读这里和这里的商量调用TranslateAccelerator方法。但我不能尝试此为none在KeyDown事件,我订阅了被解雇的。 我试过WebBrowser.KeyDown,web浏览器。previewKeyDown,并与WebBrowser.Document和WebBrowser.Document.Body相关的各种事件的onkeydown。所有这些都引起了我。 (除非作为Windows应用程序运行)

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可见的类

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

XAML

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

WPF browser window

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

我能做些什么,使快捷键时,通过COM互操作?

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

推荐答案

一个真正的错误,在您的解决方案是在这里:

One real bug in your solution is here:

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

新分配的代表新HookHandlerDelegate(HookCallBack)变垃圾收集在某些时候,后来引线 AccessViolationException 。你应该保持一个强引用这个代理,直到您叫 UnhookWindowsHookEx

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:

那么,这是否myWpfWindow就像一个无模式,独立的顶级   在传统的应用程序的窗口?或者它与其余相关莫名其妙   遗留应用程序的GUI?      独立的顶层窗口。

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和Win32互操作 (格外,共享消息循环间Win32和WPF ),假设你有过在Win32遗留应用程序的code控制。

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

显然,这不是这里的情况,所以我建议你与WPF调度程序(和自己的消息循环)一个独立的UI线程中打开此WPF窗口。这样就解决了 web浏览器快捷方式的问题以及潜在的其他一些问题也是如此。

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.

您可以使用 AttachThreadInput 附上原STA线程(在您的COM对象的生命)的新WPF线程的用户输入队列。还有其它方面中,像编组COM事件和方法调用正确的线程。下面code说明了这个概念。这是一个完整的WinForms测试应用程序,它使用COM对象,反过来,在专用线程创建一个WPF窗口 web浏览器

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