Come gestire i messaggi WndProc in WPF?


112

In Windows Forms, sovrascriverei WndProce iniziare a gestire i messaggi non appena entrarono.

Qualcuno può mostrarmi un esempio di come ottenere la stessa cosa in WPF?

Risposte:


62

In realtà, per quanto ho capito una cosa del genere è effettivamente possibile in WPF usando HwndSourcee HwndSourceHook. Vedi questo thread su MSDN come esempio. (Codice pertinente incluso di seguito)

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

Ora, non sono abbastanza sicuro del motivo per cui vorresti gestire i messaggi di messaggistica di Windows in un'applicazione WPF (a meno che non sia la forma più ovvia di interoperabilità per lavorare con un'altra app WinForms). L'ideologia di progettazione e la natura dell'API sono molto diverse in WPF da WinForms, quindi ti suggerirei di familiarizzare di più con WPF per vedere esattamente perché non esiste un equivalente di WndProc.


48
Bene, gli eventi di (dis) connessione del dispositivo USB sembrano arrivare su questo loop di messaggi, quindi non è una brutta cosa sapere come collegarsi da WPF
flq

7
@Noldorin: Puoi per favore fornire riferimenti (articoli / libri) che possono aiutarmi a capire la parte "L'ideologia di progettazione e la natura dell'API è molto diversa in WPF da WinForms, ... perché non esiste un equivalente di WndProc"?
atiyar

2
WM_MOUSEWHEELad esempio, l'unico modo per intercettare in modo affidabile quei messaggi era aggiungere il WndProca una finestra WPF. Questo ha funzionato per me, mentre il funzionario MouseWheelEventHandlersemplicemente non ha funzionato come previsto. Non sono stato in grado di allineare i tachioni WPF corretti per ottenere un comportamento affidabile MouseWheelEventHandler, da qui la necessità di accesso diretto al file WndProc.
Chris O,

4
Il fatto è che molte (la maggior parte?) Applicazioni WPF vengono eseguite su Windows desktop standard. Il fatto che l'architettura WPF scelga di non esporre tutte le funzionalità sottostanti di Win32 è deliberato da parte di Microsoft, ma comunque fastidioso da affrontare. Sto costruendo un'applicazione WPF che si rivolge solo a Windows desktop ma si integra con i dispositivi USB come menzionato @flq e l'unico modo per ricevere le notifiche del dispositivo è accedere al loop dei messaggi. A volte è inevitabile rompere l'astrazione.
NathanAlden,

1
Il monitoraggio degli appunti è uno dei motivi per cui potremmo aver bisogno di un WndProc. Un altro è rilevare che l'applicazione non è inattiva elaborando i messaggi.
user34660

135

Puoi farlo tramite lo System.Windows.Interopspazio dei nomi che contiene una classe denominata HwndSource.

Esempio di utilizzo di questo

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 tratto dall'eccellente post del blog: Utilizzo di un WndProc personalizzato nelle app WPF di Steve Rands


1
Il collegamento è interrotto. Potresti aggiustarlo per favore?
Martin Hennings

1
@ Martin, questo perché il sito web di Steve Rand non esiste più. L'unica soluzione a cui riesco a pensare è rimuoverlo. Penso che aggiunga ancora valore se il sito ritorna in futuro, quindi non lo rimuoverò, ma se non sei d'accordo sentiti libero di modificare.
Robert MacLean

È possibile ricevere messaggi WndProc senza una finestra?
Mo0gles

8
@ Mo0gles - pensa attentamente a ciò che hai chiesto e avrai la tua risposta.
Ian Kemp

1
@ Mo0gles Senza una finestra che viene disegnata sullo schermo e visibile all'utente? Sì. Ecco perché alcuni programmi hanno strane finestre vuote che a volte diventano visibili se lo stato del programma viene danneggiato.
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;
}

3

Se non ti dispiace fare riferimento a WinForms, puoi utilizzare una soluzione più orientata a MVVM che non abbini il servizio alla vista. È necessario creare e inizializzare un System.Windows.Forms.NativeWindow che è una finestra leggera che può ricevere messaggi.

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

Usa SpongeHandle per registrarti per i messaggi che ti interessano e quindi sovrascrivi WndProc per elaborarli:

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

L'unico svantaggio è che devi includere il riferimento System.Windows.Forms, ma per il resto questa è una soluzione molto incapsulata.

Maggiori informazioni su questo possono essere lette qui


1

Ecco un collegamento su come sovrascrivere WindProc utilizzando Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Modifica: meglio tardi che mai] Di seguito è riportata la mia implementazione basata sul collegamento precedente. Anche se rivisitando questo mi piacciono di più le implementazioni AddHook. Potrei passare a quello.

Nel mio caso volevo sapere quando la finestra veniva ridimensionata e un paio di altre cose. Questa implementazione si collega a Window xaml e invia eventi.

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>

Sebbene questo collegamento possa rispondere alla domanda, è meglio includere le parti essenziali della risposta qui e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia.
Max

@max> probabilmente è un po 'tardi per quello ora.
Rook

1
@Rook Penso che il servizio di revisione di StackOverflow si stia comportando in modo strano, avevo appena ricevuto 20 Here is a link...risposte esatte , come sopra.
Max

1
@ Max Un po 'in ritardo ma ho aggiornato la mia risposta per includere il codice pertinente.
Wes

0

Puoi collegarti alla classe 'SystemEvents' della classe Win32 integrata:

using Microsoft.Win32;

in una classe finestra 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

Esistono modi per gestire i messaggi con un WndProc in WPF (ad esempio utilizzando un HwndSource e così via), ma in genere queste tecniche sono riservate all'interoperabilità con i messaggi che non possono essere gestiti direttamente tramite WPF. La maggior parte dei controlli WPF non sono nemmeno finestre nel senso di Win32 (e per estensione Windows.Forms), quindi non avranno WndProcs.


-1 / impreciso. Sebbene sia vero che i moduli WPF non sono WinForm e quindi non sono soggetti WndProca sovrascrittura, System.Windows.Interopconsente di ottenere un HwndSourceoggetto tramite HwndSource.FromHwndo PresentationSource.FromVisual(someForm) as HwndSource, a cui è possibile associare un delegato con schemi speciali. Questo delegato ha molti degli stessi argomenti di un WndProcoggetto Message.
Andrew Gray,

Cito HwndSource nella risposta? Certamente la tua finestra di primo livello avrà un HWND, ma è comunque accurato dire che la maggior parte dei controlli non lo sono.
Logan Capaldo


-13

La risposta breve è che non puoi. WndProc funziona passando i messaggi a un HWND a un livello Win32. Le finestre WPF non hanno HWND e quindi non possono partecipare ai messaggi WndProc. Il ciclo di messaggi WPF di base si trova sopra WndProc ma li astrae dalla logica WPF di base.

Puoi usare un HWndHost e ottenere un WndProc per questo. Tuttavia questo non è quasi certamente quello che vuoi fare. Per la maggior parte degli scopi, WPF non funziona su HWND e WndProc. La tua soluzione quasi certamente si basa su una modifica in WPF non in WndProc.


13
"Le finestre WPF non hanno HWND" - Questo è semplicemente falso.
Scott Solmer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.