在阅读此答案之前,请考虑以下几点:
- 在 System.Windows.Forms 应用程序中,每个控件都是一个具有自己的窗口句柄的窗口(即:
Control.Handle)
- 在 WPF 应用程序中,只有顶级窗口才能获得窗口句柄,即:
Application.Current.MainWindow。
- WPF 用户控件负责在其父窗口的缓冲区中绘制自己的内容,但它们实际上从未获得自己的窗口句柄。
- 这就是为什么
new WindowInteropHelper(this).Handle 及其同类仅在从顶级窗口的代码调用时,并且仅在窗口完全初始化后才在 WPF 应用程序中有效。
以下代码演示了如何从 UserControl 挂钩主窗口的 WndProc...
CoreUI.xaml:
<UserControl x:Class="StackOverflow.CoreUI"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:StackOverflow"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<Grid>
<TextBox Text="{Binding Text}" />
</Grid>
</UserControl>
CoreUI.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
namespace StackOverflow
{
public class LogEventArgs : EventArgs {
public string Message { get; set; }
}
public delegate void LogEventHandler(object sender, LogEventArgs e);
public partial class CoreUI : UserControl
{
public string Text { get; set; } = "Hello, world!";
public LogEventHandler OnLog;
public CoreUI()
{
InitializeComponent();
DataContext = this;
}
public void HookWndProc(Window window)
{
var source = HwndSource.FromVisual(window) as HwndSource;
source.AddHook(new HwndSourceHook(WndProc));
}
protected IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
MouseButtonEventArgs args;
const int WM_LBUTTONDOWN = 0x0201;
const int WM_LBUTTONUP = 0x0202;
const int WM_MOUSEMOVE = 0x0200;
////Not required...
//// REF: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest
//const int WM_NCHITTEST = 0x0084;
//const int HTCLIENT = 0x0001;
switch (msg)
{
////Not required...
//case WM_NCHITTEST:
// {
// handled = true;
// return new IntPtr(HTCLIENT);
// }
case WM_LBUTTONDOWN:
{
OnLog?.Invoke(this, new LogEventArgs() { Message = "LBUTTONDOWN" });
handled = true;
break;
}
case WM_LBUTTONUP:
{
OnLog?.Invoke(this, new LogEventArgs() { Message = "LBUTTONUP" });
handled = true;
break;
}
case WM_MOUSEMOVE:
{
OnLog?.Invoke(this, new LogEventArgs() { Message = "MOUSEMOVE" });
handled = true;
break;
}
}
return IntPtr.Zero;
}
}
}
MainWindow.xaml:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StackOverflow"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:CoreUI x:Name="coreUI" Height="30" VerticalAlignment="Top"/>
<TextBox x:Name="textBox" Margin="0,30,0,0" TextWrapping="Wrap" Text="TextBox"/>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Windows;
namespace StackOverflow
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
textBox.Clear();
coreUI.OnLog += new LogEventHandler(OnLogHandler);
}
protected void OnLogHandler(object sender, LogEventArgs e)
{
textBox.AppendText(e.Message + "\r\n");
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
coreUI.HookWndProc(App.Current.MainWindow);
}
}
}
注意OnSourceInitialized() 的使用。如果您从OnInitialized() 尝试此操作,则主窗口尚未分配窗口句柄。
UserControl (CoreUI) 接收整个应用程序窗口的WM_LBUTTONDOWN、WM_LBUTTONUP 和WM_MOUSEMOVE 事件。如果您的 UserControl 没有填满整个应用程序窗口,那么您的控件将负责通过检查鼠标坐标与其自身的边界来过滤这些事件。