我有一个WPF项目,它加载一个包含WebBrowser控件的窗口。 打开窗口的类可用于COM互操作性。
当作为Windows应用程序运行项目时,窗口打开并且WebBrowser控件正常工作,但是当编译为类库并且从外部应用程序使用COM打开窗口时,WebBrowser都没有shortcut keys工作。 (例如CTRL + A,DELETE,CTRL + X,TAB等)
This SO Question似乎解释了问题的原因,但是由于PreProcessMessage或ProcessCmdKey永远不会被调用,因此对我不起作用的建议。 (除非作为Windows应用程序运行)
我还阅读了讨论调用TranslateAccelerator方法的链接here和here。但我无法尝试这个,因为我订阅的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互操作打开时,如何启用快捷键?
答案 0 :(得分:1)
在其他开发人员的帮助下,我们可以根据需要运行浏览器。
还有一些测试要做,但到目前为止它正确处理所有输入,剪贴板,全选,删除退格,箭头键命令。
我仍然愿意接受改进建议,但现在我很高兴它正在发挥作用。
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());
}
}
}
}
}
答案 1 :(得分: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());
尽管如此,我仍然认为这不是解决问题的正确方法。从评论到问题:
那么,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);
}
}
答案 2 :(得分:0)
使用Excel WebBrowser控件而不是System.Windows.Forms WebBrowser可能更简单;它处理特殊的密钥转发,如TAB,DEL,CTRL + V等
从
更改WebBrowser构造函数new System.Windows.Forms.WebBrowser();
到
new Microsoft.Office.Tools.Excel.Controls.WebBrowser();
您需要添加对项目的引用: Project / Add Referernce / Extensions选择 Microsoft.Tools.Outlook&amp; Microsoft.Tools.Outlook.v4.0.Utilities
参考:https://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.controls.webbrowser.aspx
答案 3 :(得分:-1)
我不知道你是否有时间从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.