如何在WPF中处理WndProc消息?

122
在Windows Forms中,我会覆盖WndProc并在消息到达时开始处理它们。请问有人能展示一个在WPF中实现同样功能的例子吗?
9个回答

154
你可以通过 System.Windows.Interop 命名空间来实现此操作,其中包含一个名为 HwndSource 的类。
以下是使用示例。
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;
        }
    }
}

完全取自卓越的博客文章:使用自定义WndProc在WPF应用程序中 by Steve Rands


是否可能在没有窗口的情况下接收WndProc消息? - Mo0gles
13
@Mo0gles - 仔细思考你所问的问题,你就会得到答案。 - Ian Kemp
2
@Mo0gles 没有被绘制在屏幕上并对用户可见的窗口?是的。这就是为什么一些程序会有奇怪的空白窗口,有时会因为程序状态变得损坏而变得可见。 - Peter
@Mo0gles 如果没有窗口,它们就不是_WndProc_(窗口过程)消息。控制台程序有可能拥有一个消息循环并在其中接收一些消息。 - Sam Hobbs
@user34660:你需要一个窗口。Peter 的意思是,你不需要一个对用户可见的窗口,可以使用一个隐藏的窗口。 - Ben Voigt
显示剩余3条评论

70

实际上,据我所知,使用 HwndSourceHwndSourceHook 在 WPF 中确实可以做到这一点。 请参阅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消息(除非它是与另一个WinForms应用程序交互的最明显形式)。WPF的设计思想和API的性质与WinForms非常不同,因此我建议你只需更加熟悉WPF,以了解为什么没有WndProc的等效物。


53
USB设备(断开/连接)事件似乎是通过此消息循环传递的,因此了解如何从WPF连接是一件好事。 - flq
7
@Noldorin:你能否提供一些参考资料(文章/书籍),帮助我理解“WPF的设计思想和API的本质与WinForms非常不同,为什么没有WndProc的等效物”的部分? 可以看一下以下这些文章或书籍:
  • "Windows Presentation Foundation Unleashed" by Adam Nathan
  • "Programming WPF" by Chris Sells and Ian Griffiths
  • "Windows Forms 2.0 Programming" by Chris Sells and Michael Weinhardt
这些资料可以帮助你深入了解WPF和WinForms之间的差异,以及在WPF中处理消息和事件的方式。
- atiyar
2
例如,可靠地捕获WM_MOUSEWHEEL消息的唯一方法是将WndProc添加到WPF窗口中。这对我很有效,而官方的MouseWheelEventHandler并没有像预期的那样工作。我无法正确地将WPF时空子弹对齐以获得可靠的MouseWheelEventHandler行为,因此需要直接访问WndProc - Chris O
4
事实上,许多(大多数?)WPF应用程序在标准桌面Windows上运行。 WPF架构选择不公开Win32的所有基本功能是Microsoft故意为之,但仍然很麻烦。我正在构建一个针对桌面Windows的WPF应用程序,但要与USB设备集成,就像@flq提到的那样,唯一接收设备通知的方法是访问消息循环。有时打破抽象是不可避免的。 - NathanAldenSr
1
监视剪贴板是我们可能需要 WndProc 的一个原因。另一个原因是通过处理消息来检测应用程序是否处于非空闲状态。 - Sam Hobbs
显示剩余3条评论

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

4

如果您不介意参考WinForms,您可以使用更加面向MVVM的解决方案,它不会将服务与视图耦合在一起。您需要创建和初始化一个System.Windows.Forms.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;
    }
}

使用SpongeHandle注册你感兴趣的消息,然后重写WndProc来处理它们:
public class WindowsMessageListenerService : WinApiServiceBase
{
    protected override void WndProc(Message message)
    {
        Debug.WriteLine(message.msg);
    }
}

唯一的缺点是你必须包含System.Windows.Forms参考,但除此之外这是一个非常封装的解决方案。

3
这里有一个关于使用行为覆盖WindProc的链接:http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35 [编辑:迟做总比不做好]以下是我基于上面链接的实现。尽管重新访问这个内容后,我更喜欢AddHook实现的方式。我可能会转换到那种方式。
在我的案例中,我想知道窗口何时被调整大小以及其他一些事情。这个实现钩连到Window xaml并发送事件。
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>

1
你可以附加到内置的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();
    }
}

-1

有多种方法可以使用WPF中的WndProc处理消息(例如,使用HwndSource等),但通常这些技术是保留用于与无法直接通过WPF处理的消息进行交互。大多数WPF控件甚至不是Win32中窗口的窗口(并且通过延伸Windows.Forms),因此它们不会有WndProcs。


-1 / 不准确。虽然WPF表单不是WinForms,因此没有公开的WndProc可覆盖,但是System.Windows.Interop允许您通过HwndSource.FromHwndPresentationSource.FromVisual(someForm) as HwndSource获取HwndSource对象,您可以将一个特殊模式的委托绑定到该对象。这个委托有许多与WndProc消息对象相同的参数。 - Andrew Gray
我在回答中提到了HwndSource吗?当然,您的顶级窗口将具有HWND,但仍然准确地说,大多数控件都没有。 - Logan Capaldo

-7

-16

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

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


14
“WPF 的窗口没有 HWND” - 这是不正确的说法。 - Scott Solmer
Wpf窗口具有HWND。可以使用以下代码访问窗口HWND:var hwnd=new WindowInteropHelper(window).Handle; 这非常容易。 - trickymind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接