如何在WPF中处理WndProc消息?


112

在Windows窗体中,我只是覆盖 WndProc,并开始处理传入的消息。

有人可以向我展示如何在WPF中实现相同目标的示例吗?

Answers:


62

实际上,据我了解,在WPF中使用HwndSource和确实可以做到这一点HwndSourceHook。请参见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的性质在WPF中与WinForms完全不同,因此,我建议您进一步熟悉WPF,以了解为什么没有WndProc。


48
好吧,USB设备(断开)连接事件似乎正在通过此消息循环进行,因此知道如何从WPF进行连接并不是一件坏事
flq 2011年

7
@Noldorin:能否请您提供参考资料(文章/书),以帮助我理解以下部分:“ WPF中的设计思想和API的性质与WinForms完全不同,...为什么没有等效的WndProc”?
atiyar

2
WM_MOUSEWHEEL例如,可靠地捕获那些消息的唯一方法是将其添加WndProc到WPF窗口。这对我有用,而官员MouseWheelEventHandler根本没有按预期工作。我无法正确地排列正确的WPF速球来获得可靠的行为MouseWheelEventHandler,因此需要直接访问WndProc
克里斯·奥

4
事实是,许多(大多数?)WPF应用程序都在标准桌面Windows上运行。WPF架构选择不公开Win32的所有基本功能是微软的故意做法,但仍然令人讨厌。我正在构建一个WPF应用程序,该应用程序仅针对桌面Windows,但与USB设备集成(如@flq所述),接收设备通知的唯一方法是访问消息循环。有时打破抽象是不可避免的。
NathanAldenSr

1
监视剪贴板是我们可能需要WndProc的原因之一。另一个是通过处理消息来检测应用程序是否空闲。
user34660

135

您可以通过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;
        }
    }
}

完全取材于出色的博客文章:Steve Rands在WPF应用程序中使用自定义WndProc


1
链接断开。您能解决它吗?
Martin Hennings

1
@Martin,这是因为Steve Rand的网站不再存在。我能想到的唯一解决方法是将其删除。我认为,如果站点将来返回,它仍然会增加价值,因此我不会删除它-但是,如果您不同意,请随时进行编辑。
罗伯特·麦克莱恩

是否可以在没有窗口的情况下接收WndProc消息?
Mo0gles 2013年

8
@ Mo0gles-仔细考虑您的要求,您将得到答案。
伊恩·坎普

1
@ Mo0gles没有在屏幕上绘制且用户可见的窗口?是。这就是为什么某些程序具有怪异的空Windows的原因,如果程序的状态损坏,则有时可以看到这些窗口。
彼得

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

3

如果您不介意引用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引用,但否则,这是一个非常封装的解决方案。

更多信息可以在这里阅读


1

以下是有关使用行为覆盖WindProc的链接: http

[编辑:总比没有好。]下面是基于以上链接的实现。尽管重新讨论了这一点,但我更喜欢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>

尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。
最大

@max>现在可能有点晚了。
Rook

1
@Rook我认为StackOverflow的审阅服务的行为很奇怪,我只喜欢20个确切的Here is a link...答案:
2014年

1
@Max有点晚了,但是我更新了答案以包含相关代码。
韦斯

0

您可以附加到内置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)意义上都不是Windows,因此它们将没有WndProcs。


-1 /不准确。虽然WPF表单确实不是WinForms,因此没有暴露WndProc于覆盖状态,但是WPF表单System.Windows.Interop允许您HwndSource通过HwndSource.FromHwnd或来获取对象PresentationSource.FromVisual(someForm) as HwndSource,您可以将特殊格式的委托绑定到该对象。该委托具有许多与WndProcMessage对象相同的参数。
Andrew Gray

我在答案中提到HwndSource吗?当然,您的顶级窗口将具有HWND,但准确地说大多数控件没有HWND 。
Logan Capaldo


-13

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

您可以使用HWndHost并获取WndProc。但是,这几乎肯定不是您想要执行的操作。对于大多数目的,WPF不能在HWND和WndProc上运行。您的解决方案几乎可以肯定依赖于WPF而不是WndProc中的更改。


13
“ WPF窗口没有HWND”-这根本是不正确的。
Scott Solmer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.