【问题标题】:How to handle WndProc messages in WPF?如何在 WPF 中处理 WndProc 消息?
【发布时间】:2010-10-12 01:58:00
【问题描述】:

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

谁能告诉我一个如何在 WPF 中实现相同目标的示例?

【问题讨论】:

    标签: c# wpf wndproc


    【解决方案1】:

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

    完全取自优秀博文:Using a custom WndProc in WPF apps by Steve Rands

    【讨论】:

    • 链接已损坏。你能解决它吗?
    • @Martin,那是因为 Steve Rand 的网站不再存在。我能想到的唯一解决方法是删除它。我认为如果该网站将来返回它仍然会增加价值,所以我不会删除它 - 但如果您不同意,请随时编辑。
    • 是否可以在没有窗口的情况下接收 WndProc 消息?
    • @Mo0gles - 仔细想想你问了什么,你就会得到答案。
    • @Mo0gles 没有在屏幕上绘制且用户可见的窗口?是的。这就是为什么有些程序有奇怪的空窗口,如果程序的状态损坏,有时会变得可见。
    【解决方案2】:

    实际上,据我了解,在 WPF 中使用HwndSourceHwndSourceHook 确实可以做到这一点。以this thread on 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 的等价物。

    【讨论】:

    • 嗯,USB 设备(断开)连接事件似乎正在通过这个消息循环,所以知道如何从 WPF 连接并不是一件坏事
    • @Noldorin:您能否提供参考资料(文章/书籍)以帮助我理解“WPF 中的设计思想和 API 的性质与 WinForms 非常不同,...为什么没有等效的 WndProc"?
    • WM_MOUSEWHEEL 例如,可靠地捕获这些消息的唯一方法是将WndProc 添加到 WPF 窗口。这对我有用,而官方MouseWheelEventHandler 根本没有按预期工作。我无法正确排列正确的 WPF 快子以获得与 MouseWheelEventHandler 的可靠行为,因此需要直接访问 WndProc
    • 事实上,许多(大多数?)WPF 应用程序都在标准桌面 Windows 上运行。 WPF 架构选择不公开 Win32 的所有底层功能是微软故意的,但处理起来仍然很烦人。我正在构建一个 WPF 应用程序,它仅针对桌面 Windows,但与 @flq 提到的 USB 设备集成,接收设备通知的唯一方法是访问消息循环。有时打破抽象是不可避免的。
    • 监控剪贴板是我们可能需要 WndProc 的原因之一。另一种是通过处理消息来检测应用程序没有空闲。
    【解决方案3】:
    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 引用,否则这是一个非常封装的解决方案。

      更多信息可以阅读here

      【讨论】:

        【解决方案5】:

        这里是使用行为覆盖 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>
        

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

            有一些方法可以在 WPF 中使用 WndProc 处理消息(例如,使用 HwndSource 等),但通常这些技术保留用于与无法通过 WPF 直接处理的消息进行互操作。大多数 WPF 控件甚至不是 Win32(以及扩展为 Windows.Forms)意义上的窗口,因此它们不会有 WndProcs。

            【讨论】:

            • -1 / 不准确。虽然 WPF 表单确实不是 WinForms,因此没有暴露的 WndProc 可以覆盖,但 System.Windows.Interop 允许您通过 HwndSource.FromHwndPresentationSource.FromVisual(someForm) as HwndSource 获取 HwndSource 对象,您可以绑定一个特殊模式的代表。这个委托有许多与WndProc Message 对象相同的参数。
            • 我在回答中提到了 HwndSource?当然,您的顶级窗口会有 HWND,但说大多数控件没有。
            【解决方案8】:

            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

            【讨论】:

              【解决方案9】:

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

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

              【讨论】:

              • “WPF 窗口没有 HWND” - 这完全是不真实的。
              • Wpf 窗口有 HWND。可以使用以下命令访问窗口 HWND: var hwnd=new WindowInteropHelper(window).Handle;而且很容易。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-04-30
              • 2021-05-12
              • 2021-12-05
              • 2021-04-03
              相关资源
              最近更新 更多