How to handle WndProc messages in WPF?
Asked Answered
P

9

122

In Windows Forms, I'd just override WndProc, and start handling messages as they came in.

Can someone show me an example of how to achieve the same thing in WPF?

Pugnacious answered 8/3, 2009 at 21:49 Comment(0)
S
70

Actually, as far as I understand such a thing is indeed possible in WPF using HwndSource and HwndSourceHook. See this thread on MSDN as an example. (Relevant code included below)

// '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;
}

Now, I'm not quite sure why you'd want to handle Windows Messaging messages in a WPF application (unless it's the most obvious form of interop for working with another WinForms app). The design ideology and the nature of the API is very different in WPF from WinForms, so I would suggest you just familiarise yourself with WPF more to see exactly why there is no equivalent of WndProc.

Superable answered 8/3, 2009 at 22:18 Comment(8)
Well, USB Device (dis)connect events seem to be coming over this message loop, so it's not a bad thing to know how to hook up from WPFKao
@Noldorin: Can you please provide references (articles/books) that can help me understand the part "The design ideology and the nature of the API is very different in WPF from WinForms,... why there is no equivalent of WndProc"?Langmuir
WM_MOUSEWHEEL for example, the only way to reliably trap those messages was by adding the WndProc to a WPF window. This worked for me, whereas the official MouseWheelEventHandler simply didn't work as expected. I was unable to get the correct WPF tachyons lined up just right to get reliable behavior with MouseWheelEventHandler, hence the need for direct access to the WndProc.Easily
The fact is, many (most?) WPF applications are run on standard desktop Windows. That the WPF architecture chooses not to expose all the underlying capabilities of Win32 is deliberate on Microsoft's part, but still annoying to deal with. I'm building a WPF application that targets only desktop Windows but integrates with USB devices as @Kao mentioned and the only way to receive device notifications is to access the message loop. Sometimes breaking the abstraction is unavoidable.Masthead
Monitoring the clipboard is one reason we might need a WndProc. Another is to detect that the application is not idle by processing messages.Peplum
Distinguishing the [X] button is another reason - https://mcmap.net/q/16146/-wpf-how-to-distinguish-between-window-close-call-and-system-menu-close-action-duplicate Basically, anything WPF can't do itself.Plectrum
handling WM_POWERBROADCAST for exampleRhpositive
Another useful example would be getting the mouse delta movement using RAW_INPUT.Knot
K
156

You can do this via the System.Windows.Interop namespace which contains a class named HwndSource.

Example of using this

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

Completely taken from the excellent blog post: Using a custom WndProc in WPF apps by Steve Rands

Knurl answered 18/12, 2009 at 7:12 Comment(8)
Is it possible to receive WndProc messages without a window?Starks
@Starks - think carefully about what you asked, and you will have your answer.Borstal
@Starks Without a window that is drawn on the screen and visible to the user? Yes. That's why some programs have weird empty Windows which sometimes become visible if the program's state becomes corrupted.Gossamer
@Starks they are not WndProc (window procedure) messages if there is no window. It is possible for a console program to have a message loop and receive some messages in it.Peplum
@user34660: You need a window. The point Peter was making is that you do not need a window visible to the user, you can use one which is hidden.Genitor
@BenVoigt what I said is true, as in PostThreadMessage to Console Application. The comment is not relevant to this question so I did not post details to avoid unnecessary discussion such as this. The comment is irrelevant, correct?Peplum
@user34660: Sorry I misunderstood the point you were trying to make. Unfortunately OP didn't say what exact messages he is trying to receive, but since there are a whole bunch that are only sent to windows, never posted directly to a thread, it's quite likely OP does in fact need a (possibly hidden) window. If he's posting the messages himself, rather than subscribing to system notifications, then perhaps a message loop without a window could work, as you say.Genitor
I'm using dotnet core 3.1. This answer worked for me. I tried calling HwndSource source = ... source.AddHook in the app constructor but this failed (null reference exception), it only worked in the override OnSourceInitialized as outlined in this answer.Homo
S
70

Actually, as far as I understand such a thing is indeed possible in WPF using HwndSource and HwndSourceHook. See this thread on MSDN as an example. (Relevant code included below)

// '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;
}

Now, I'm not quite sure why you'd want to handle Windows Messaging messages in a WPF application (unless it's the most obvious form of interop for working with another WinForms app). The design ideology and the nature of the API is very different in WPF from WinForms, so I would suggest you just familiarise yourself with WPF more to see exactly why there is no equivalent of WndProc.

Superable answered 8/3, 2009 at 22:18 Comment(8)
Well, USB Device (dis)connect events seem to be coming over this message loop, so it's not a bad thing to know how to hook up from WPFKao
@Noldorin: Can you please provide references (articles/books) that can help me understand the part "The design ideology and the nature of the API is very different in WPF from WinForms,... why there is no equivalent of WndProc"?Langmuir
WM_MOUSEWHEEL for example, the only way to reliably trap those messages was by adding the WndProc to a WPF window. This worked for me, whereas the official MouseWheelEventHandler simply didn't work as expected. I was unable to get the correct WPF tachyons lined up just right to get reliable behavior with MouseWheelEventHandler, hence the need for direct access to the WndProc.Easily
The fact is, many (most?) WPF applications are run on standard desktop Windows. That the WPF architecture chooses not to expose all the underlying capabilities of Win32 is deliberate on Microsoft's part, but still annoying to deal with. I'm building a WPF application that targets only desktop Windows but integrates with USB devices as @Kao mentioned and the only way to receive device notifications is to access the message loop. Sometimes breaking the abstraction is unavoidable.Masthead
Monitoring the clipboard is one reason we might need a WndProc. Another is to detect that the application is not idle by processing messages.Peplum
Distinguishing the [X] button is another reason - https://mcmap.net/q/16146/-wpf-how-to-distinguish-between-window-close-call-and-system-menu-close-action-duplicate Basically, anything WPF can't do itself.Plectrum
handling WM_POWERBROADCAST for exampleRhpositive
Another useful example would be getting the mouse delta movement using RAW_INPUT.Knot
C
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;
}
Creatine answered 14/7, 2009 at 16:58 Comment(0)
C
4

If you don't mind referencing WinForms, you can use a more MVVM-oriented solution that doesn't couple service with the view. You need to create and initialize a System.Windows.Forms.NativeWindow which is a lightweight window that can receive messages.

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

Use SpongeHandle to register for messages you're interested in and then override WndProc to process them:

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

The only downside is that you have to include System.Windows.Forms reference, but otherwise this is a very encapsulated solution.

Cinnabar answered 2/2, 2017 at 17:8 Comment(0)
C
3

Here is a link on overriding WindProc using Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Edit: better late than never] Below is my implementation based on the above link. Although revisiting this I like the AddHook implementations better. I might switch to that.

In my case I wanted to know when the window was being resized and a couple other things. This implementation hooks up to the Window xaml and sends events.

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>
Carlisle answered 8/4, 2011 at 17:26 Comment(0)
J
1

You can attach to the 'SystemEvents' class of the built-in Win32 class:

using Microsoft.Win32;

in a WPF window class:

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();
    }
}
Joni answered 8/9, 2017 at 7:40 Comment(0)
C
-1

There are ways to handle messages with a WndProc in WPF (e.g. using a HwndSource, etc.), but generally those techniques are reserved for interop with messages that can't directly be handled through WPF. Most WPF controls aren't even windows in the Win32 (and by extension Windows.Forms) sense, so they won't have WndProcs.

Corked answered 8/3, 2009 at 21:58 Comment(2)
-1 / Inaccurate. While it is true that WPF forms are not WinForms, and therefore have no exposed WndProc to override, the System.Windows.Interop allows you to get a HwndSource object by way of HwndSource.FromHwnd or PresentationSource.FromVisual(someForm) as HwndSource, that you can bind a specially-patterned delegate to. This delegate has many of the same arguments as a WndProc Message object.Neurogenic
I mention HwndSource in the answer? Certainly your top level window will have an HWND, but it is still accurate to say most controls are not.Corked
U
-7

WPF doesn't operate on WinForms type wndprocs

You can host an HWndHost in an appropriate WPF element then override the Hwndhost's wndproc, but AFAIK that's as close as you're going to get.

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

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

Usually answered 8/3, 2009 at 21:56 Comment(0)
P
-16

The short answer is you can't. WndProc works by passing messages to a HWND on a Win32 level. WPF windows have no HWND and hence can't participate in WndProc messages. The base WPF message loop does sit on top of WndProc but it abstracts them away from core WPF logic.

You can use a HWndHost and get at a WndProc for it. However this is almost certainly not what you want to do. For the majority of purposes, WPF does not operate on HWND and WndProc. Your solution almost certainly relies on making a change in WPF not in WndProc.

Pterous answered 8/3, 2009 at 21:57 Comment(2)
"WPF windows have no HWND" - This is simply untrue.Merrel
Wpf windows have HWND.The window HWND can be accessed by using this : var hwnd=new WindowInteropHelper(window).Handle; and its very easy.Needleful

© 2022 - 2024 — McMap. All rights reserved.