¿Cómo manejar los mensajes de WndProc en WPF?

112

En Windows Forms, simplemente anularía WndProcy comenzaría a manejar los mensajes a medida que ingresaban.

¿Alguien puede mostrarme un ejemplo de cómo lograr lo mismo en WPF?

Shuft
fuente

Respuestas:

62

En realidad, hasta donde tengo entendido, tal cosa es posible en WPF usando HwndSourcey HwndSourceHook. Vea este hilo en MSDN como ejemplo. (Código relevante incluido a continuación)

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

Ahora, no estoy muy seguro de por qué querría manejar los mensajes de Windows Messaging en una aplicación WPF (a menos que sea la forma más obvia de interoperabilidad para trabajar con otra aplicación WinForms). La ideología de diseño y la naturaleza de la API son muy diferentes en WPF de WinForms, por lo que le sugiero que se familiarice más con WPF para ver exactamente por qué no existe un equivalente de WndProc.

Noldorin
fuente
48
Bueno, los eventos de (dis) conexión de dispositivos USB parecen venir a través de este bucle de mensajes, por lo que no es malo saber cómo conectarse desde WPF
flq
7
@Noldorin: ¿Puede proporcionar referencias (artículos / libros) que puedan ayudarme a comprender la parte "La ideología de diseño y la naturaleza de la API es muy diferente en WPF de WinForms, ... por qué no hay equivalente de WndProc"?
atiyar
2
WM_MOUSEWHEELpor ejemplo, la única forma de capturar esos mensajes de manera confiable era agregando el WndProca una ventana de WPF. Esto funcionó para mí, mientras que el funcionario MouseWheelEventHandlersimplemente no funcionó como se esperaba. No pude alinear los taquiones WPF correctos para obtener un comportamiento confiable MouseWheelEventHandler, de ahí la necesidad de acceso directo al archivo WndProc.
Chris O
4
El hecho es que muchas (¿la mayoría?) De las aplicaciones WPF se ejecutan en Windows de escritorio estándar. El hecho de que la arquitectura WPF elija no exponer todas las capacidades subyacentes de Win32 es deliberado por parte de Microsoft, pero sigue siendo molesto. Estoy creando una aplicación WPF que se dirige solo a Windows de escritorio, pero se integra con dispositivos USB como mencionó @flq y la única forma de recibir notificaciones del dispositivo es acceder al bucle de mensajes. A veces, romper la abstracción es inevitable.
NathanAldenSr
1
Monitorear el portapapeles es una de las razones por las que podríamos necesitar un WndProc. Otra es detectar que la aplicación no está inactiva procesando mensajes.
user34660
135

Puede hacer esto a través del System.Windows.Interopespacio de nombres que contiene una clase llamada HwndSource.

Ejemplo de uso de esto

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

Completamente tomado de la excelente publicación del blog: Uso de un WndProc personalizado en aplicaciones WPF de Steve Rands

Robert MacLean
fuente
1
El vínculo está roto. ¿Podrías arreglarlo?
Martin Hennings
1
@Martin, eso se debe a que el sitio web de Steve Rand ya no existe. La única solución que se me ocurre es eliminarlo. Creo que aún agrega valor si el sitio regresa en el futuro, así que no lo eliminaré, pero si no está de acuerdo, no dude en editarlo.
Robert MacLean
¿Es posible recibir mensajes WndProc sin una ventana?
Mo0gles
8
@ Mo0gles: piense detenidamente en lo que preguntó y tendrá su respuesta.
Ian Kemp
1
@ Mo0gles ¿Sin una ventana dibujada en la pantalla y visible para el usuario? Si. Es por eso que algunos programas tienen ventanas vacías extrañas que a veces se vuelven visibles si el estado del programa se corrompe.
Peter
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;
}
softwerx
fuente
3

Si no le importa hacer referencia a WinForms, puede usar una solución más orientada a MVVM que no combine el servicio con la vista. Necesita crear e inicializar un System.Windows.Forms.NativeWindow, que es una ventana liviana que puede recibir mensajes.

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 para registrarse para los mensajes que le interesan y luego anule WndProc para procesarlos:

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

El único inconveniente es que debe incluir la referencia System.Windows.Forms, pero por lo demás, esta es una solución muy encapsulada.

Se puede leer más sobre esto aquí.

Tyrrrz
fuente
1

Aquí hay un enlace sobre cómo anular WindProc usando comportamientos: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Editar: más vale tarde que nunca] A continuación se muestra mi implementación basada en el enlace anterior. Aunque revisando esto, me gustan más las implementaciones de AddHook. Podría cambiar a eso.

En mi caso, quería saber cuándo se estaba cambiando el tamaño de la ventana y un par de cosas más. Esta implementación se conecta al xaml de Windows y envía eventos.

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>
Wes
fuente
Si bien este enlace puede responder a la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si cambia la página enlazada.
Máximo
@max> es probablemente un poco tarde para eso ahora.
Torre
1
@Rook Creo que el servicio de revisión de StackOverflow está actuando extraño, solo tenía como 20 Here is a link...respuestas exactas: como arriba.
Máximo
1
@Max Un poco tarde, pero actualicé mi respuesta para incluir el código relevante.
Wes
0

Puede adjuntar a la clase 'SystemEvents' de la clase Win32 incorporada:

using Microsoft.Win32;

en una clase de ventana 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();
    }
}
AndresRohrAtlasInformatik
fuente
-1

Hay formas de manejar mensajes con un WndProc en WPF (por ejemplo, usando un HwndSource, etc.), pero generalmente esas técnicas están reservadas para la interoperabilidad con mensajes que no se pueden manejar directamente a través de WPF. La mayoría de los controles WPF ni siquiera son ventanas en el sentido de Win32 (y por extensión Windows.Forms), por lo que no tendrán WndProcs.

Logan Capaldo
fuente
-1 / Inexacto. Si bien es cierto que los formularios WPF no son WinForms y, por lo tanto, no están expuestos WndProca invalidarse, le System.Windows.Interoppermite obtener un HwndSourceobjeto a través de HwndSource.FromHwndo al PresentationSource.FromVisual(someForm) as HwndSourceque puede vincular un delegado con un patrón especial. Este delegado tiene muchos de los mismos argumentos que un WndProcobjeto Mensaje.
Andrew Gray
¿Menciono HwndSource en la respuesta? Ciertamente, su ventana de nivel superior tendrá un HWND, pero aún es correcto decir que la mayoría de los controles no lo son.
Logan Capaldo
-13

La respuesta corta es que no puedes. WndProc funciona pasando mensajes a un HWND en un nivel Win32. Las ventanas de WPF no tienen HWND y, por lo tanto, no pueden participar en los mensajes de WndProc. El bucle de mensajes de WPF base se encuentra encima de WndProc, pero los abstrae de la lógica central de WPF.

Puede usar un HWndHost y obtener un WndProc para ello. Sin embargo, es casi seguro que esto no es lo que desea hacer. Para la mayoría de los propósitos, WPF no opera en HWND y WndProc. Es casi seguro que su solución se basa en realizar un cambio en WPF, no en WndProc.

JaredPar
fuente
13
"Las ventanas de WPF no tienen HWND": esto es simplemente falso.
Scott Solmer