如何在 WPF 中处理 WndProc 消息?

在 Windows 窗体中,我只需覆盖 WndProc,并在消息进入时开始处理它们。

谁能给我举个例子说明如何在 WPF 中实现同样的功能?

106860 次浏览

WPF 不能在 WinForms 类型 wndprocs 上操作

您可以在适当的 WPF 元素中托管一个 HWndHost,然后覆盖 HWndHost 的 wndproc,但是 AFAIK 是最接近的。

http://msdn.microsoft.com/en-us/library/ms742522.aspx

Http://blogs.msdn.com/nickkramer/archive/2006/03/18/554235.aspx

简而言之,你不能。WndProc 通过在 Win32级别上将消息传递给 HWND 来工作。WPF 窗口没有 HWND,因此不能参与 WndProc 消息。基本 WPF 消息循环确实位于 WndProc 之上,但是它将它们从核心 WPF 逻辑抽象出来。

您可以使用 HWndHost 并获取它的 WndProc。然而,这几乎肯定不是您想要做的。在大多数情况下,WPF 不在 HWND 和 WndProc 上运行。您的解决方案几乎肯定依赖于对 WPF 进行更改,而不是在 WndProc 中进行更改。

在 WPF 中使用 WndProc 处理消息的方法有很多(例如使用 HwndSource 等) ,但通常这些技术都是为了与不能直接通过 WPF 处理的消息进行互操作而保留的。大多数 WPF 控件甚至不是 Win32(扩展为 Windows)中的窗口。表单) ,所以他们不会有 WndProcs。

事实上,据我所知,这样的事情确实可能在 WPF 使用 HwndSourceHwndSourceHook。以 MSDN 上的这个帖子为例。(相关代码如下)

// 'this' is a Window
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));


private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
//  do stuff


return IntPtr.Zero;
}

现在,我不太确定为什么要在 WPF 应用程序中处理 Windows Messaging 消息(除非它是与另一个 WinForms 应用程序协同工作的最明显形式)。WPF 的设计思想和 API 的本质与 WinForms 非常不同,所以我建议你更多地熟悉 WPF,看看 为什么到底有没有 WndProc 的等价物。

HwndSource src = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
src.AddHook(new HwndSourceHook(WndProc));




.......




public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{


if(msg == THEMESSAGEIMLOOKINGFOR)
{
//Do something here
}


return IntPtr.Zero;
}

您可以通过包含名为 HwndSource的类的 System.Windows.Interop命名空间来实现这一点。

使用此

using System;
using System.Windows;
using System.Windows.Interop;


namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}


protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}


private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle messages...


return IntPtr.Zero;
}
}
}

完全取自优秀的博客文章: 在 WPF 应用程序中使用自定义 WndProc 作者: Steve Rands

这里有一个关于使用行为重写 WindProc 的链接: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[编辑: 迟到总比不到好]下面是我基于以上链接的实现。虽然我会重新考虑这个问题,但是我更喜欢 AddHook 的实现。我可能会改用那个。

对我来说,我想知道窗户什么时候调整大小,还有一些其他的事情。这个实现挂接到 Windowxaml 并发送事件。

using System;
using System.Windows.Interactivity;
using System.Windows; // For Window in behavior
using System.Windows.Interop; // For Hwnd


public class WindowResizeEvents : Behavior<Window>
{
public event EventHandler Resized;
public event EventHandler Resizing;
public event EventHandler Maximized;
public event EventHandler Minimized;
public event EventHandler Restored;


public static DependencyProperty IsAppAskCloseProperty =  DependencyProperty.RegisterAttached("IsAppAskClose", typeof(bool), typeof(WindowResizeEvents));
public Boolean IsAppAskClose
{
get { return (Boolean)this.GetValue(IsAppAskCloseProperty); }
set { this.SetValue(IsAppAskCloseProperty, value); }
}


// called when the behavior is attached
// hook the wndproc
protected override void OnAttached()
{
base.OnAttached();


AssociatedObject.Loaded += (s, e) =>
{
WireUpWndProc();
};
}


// call when the behavior is detached
// clean up our winproc hook
protected override void OnDetaching()
{
RemoveWndProc();


base.OnDetaching();
}


private HwndSourceHook _hook;


private void WireUpWndProc()
{
HwndSource source = HwndSource.FromVisual(AssociatedObject) as HwndSource;


if (source != null)
{
_hook = new HwndSourceHook(WndProc);
source.AddHook(_hook);
}
}


private void RemoveWndProc()
{
HwndSource source = HwndSource.FromVisual(AssociatedObject) as HwndSource;


if (source != null)
{
source.RemoveHook(_hook);
}
}


private const Int32 WM_EXITSIZEMOVE = 0x0232;
private const Int32 WM_SIZING = 0x0214;
private const Int32 WM_SIZE = 0x0005;


private const Int32 SIZE_RESTORED = 0x0000;
private const Int32 SIZE_MINIMIZED = 0x0001;
private const Int32 SIZE_MAXIMIZED = 0x0002;
private const Int32 SIZE_MAXSHOW = 0x0003;
private const Int32 SIZE_MAXHIDE = 0x0004;


private const Int32 WM_QUERYENDSESSION = 0x0011;
private const Int32 ENDSESSION_CLOSEAPP = 0x1;
private const Int32 WM_ENDSESSION = 0x0016;


private IntPtr WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, ref Boolean handled)
{
IntPtr result = IntPtr.Zero;


switch (msg)
{
case WM_SIZING:             // sizing gets interactive resize
OnResizing();
break;


case WM_SIZE:               // size gets minimize/maximize as well as final size
{
int param = wParam.ToInt32();


switch (param)
{
case SIZE_RESTORED:
OnRestored();
break;
case SIZE_MINIMIZED:
OnMinimized();
break;
case SIZE_MAXIMIZED:
OnMaximized();
break;
case SIZE_MAXSHOW:
break;
case SIZE_MAXHIDE:
break;
}
}
break;


case WM_EXITSIZEMOVE:
OnResized();
break;


// Windows is requesting app to close.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa376890%28v=vs.85%29.aspx.
// Use the default response (yes).
case WM_QUERYENDSESSION:
IsAppAskClose = true;
break;
}


return result;
}


private void OnResizing()
{
if (Resizing != null)
Resizing(AssociatedObject, EventArgs.Empty);
}


private void OnResized()
{
if (Resized != null)
Resized(AssociatedObject, EventArgs.Empty);
}


private void OnRestored()
{
if (Restored != null)
Restored(AssociatedObject, EventArgs.Empty);
}


private void OnMinimized()
{
if (Minimized != null)
Minimized(AssociatedObject, EventArgs.Empty);
}


private void OnMaximized()
{
if (Maximized != null)
Maximized(AssociatedObject, EventArgs.Empty);
}
}


<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:behaviors="clr-namespace:RapidCoreConfigurator._Behaviors"
Title="name" Height="500" Width="750" BorderBrush="Transparent">


<i:Interaction.Behaviors>
<behaviors:WindowResizeEvents IsAppAskClose="{Binding IsRequestClose, Mode=OneWayToSource}"
Resized="Window_Resized"
Resizing="Window_Resizing" />
</i:Interaction.Behaviors>


...


</Window>

如果你不介意引用 WinForms,你可以使用一个更加面向 MVVM 的解决方案,它不会将服务和视图耦合起来。您需要创建并初始化一个 System。窗户。表格。NativeWindow,它是一个可以接收消息的轻量级窗口。

public abstract class WinApiServiceBase : IDisposable
{
/// <summary>
/// Sponge window absorbs messages and lets other services use them
/// </summary>
private sealed class SpongeWindow : NativeWindow
{
public event EventHandler<Message> WndProced;


public SpongeWindow()
{
CreateHandle(new CreateParams());
}


protected override void WndProc(ref Message m)
{
WndProced?.Invoke(this, m);
base.WndProc(ref m);
}
}


private static readonly SpongeWindow Sponge;
protected static readonly IntPtr SpongeHandle;


static WinApiServiceBase()
{
Sponge = new SpongeWindow();
SpongeHandle = Sponge.Handle;
}


protected WinApiServiceBase()
{
Sponge.WndProced += LocalWndProced;
}


private void LocalWndProced(object sender, Message message)
{
WndProc(message);
}


/// <summary>
/// Override to process windows messages
/// </summary>
protected virtual void WndProc(Message message)
{ }


public virtual void Dispose()
{
Sponge.WndProced -= LocalWndProced;
}
}

使用海绵句柄注册您感兴趣的消息,然后覆盖 WndProc 来处理它们:

public class WindowsMessageListenerService : WinApiServiceBase
{
protected override void WndProc(Message message)
{
Debug.WriteLine(message.msg);
}
}

唯一的缺点是必须包含 System。窗户。表单引用,但除此之外,这是一个非常封装的解决方案。

关于这方面的更多信息可以阅读 给你

您可以附加到内置 Win32类的“ SystemEvents”类:

using Microsoft.Win32;

在 WPF 窗口类中:

SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
SystemEvents.SessionEnded += SystemEvents_SessionEnded;


private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
await vm.PowerModeChanged(e.Mode);
}


private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
await vm.PowerModeChanged(e.Mode);
}


private async void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
await vm.SessionSwitch(e.Reason);
}


private async void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
{
if (e.Reason == SessionEndReasons.Logoff)
{
await vm.UserLogoff();
}
}


private async void SystemEvents_SessionEnded(object sender, SessionEndedEventArgs e)
{
if (e.Reason == SessionEndReasons.Logoff)
{
await vm.UserLogoff();
}
}