2009-03-08 27 views
89

Trovare la WPF una curva di apprendimento ripida.Come gestire i messaggi WndProc in WPF?

In buon vecchio' Windows Forms, avevo appena l'override WndProc, e comincio la gestione dei messaggi come sono venuti in.

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

risposta

45

In realtà, per quanto ho capito una cosa del genere è effettivamente possibile in WPF utilizzando HwndSource e HwndSourceHook. Vedere this thread on MSDN come esempio. (Codice in materia riportato qui 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, io non sono molto sicuro perché ci si vuole per gestire i messaggi di Windows Messaging in un'applicazione WPF (a meno che sia la forma più evidente di interoperabilità per lavorare con un altro WinForms app). L'ideologia del design e la natura dell'API sono molto diverse in WPF da WinForms, quindi ti suggerirei di familiarizzare con WPF in più per vedere esattamente perché non esiste alcun equivalente di WndProc.

+36

Bene, gli eventi USB Device (dis) connect sembrano arrivare su questo loop di messaggi, quindi non è una brutta cosa sapere come collegarsi da WPF – flq

+4

@Noldorin: Puoi fornire riferimenti (articoli/libri) che possono aiutare capisco la parte "L'ideologia del design e la natura dell'API sono molto diverse in WPF da WinForms, ... perché non esiste un equivalente di WndProc"? – atiyar

+1

'WM_MOUSEWHEEL', ad esempio, l'unico modo per intercettare in modo affidabile quei messaggi è stato aggiungendo il' WndProc' a una finestra WPF. Questo ha funzionato per me, mentre il 'MouseWheelEventHandler' ufficiale semplicemente non funzionava come previsto. Non ero in grado di ottenere il giusto numero di tachioni WPF allineati per ottenere un comportamento affidabile con 'MouseWheelEventHandler', da qui la necessità di accedere direttamente a' WndProc'. –

-8

La risposta breve è che non si può. WndProc funziona passando messaggi a un HWND su un livello Win32. Le finestre WPF non hanno HWND e quindi non possono partecipare ai messaggi WndProc. Il loop di messaggi WPF di base si trova in cima a WndProc ma li astrae dalla logica WPF principale.

È possibile utilizzare un HWndHost e ottenere un WndProc per esso. 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.

+7

"Le finestre WPF non hanno HWND" - Questo è semplicemente falso. –

0

Esistono modi per gestire i messaggi con un WndProc in WPF (ad esempio utilizzando una HwndSource, ecc.), Ma generalmente tali tecniche sono riservate per l'interoperabilità con messaggi che non possono essere gestiti direttamente tramite WPF. La maggior parte dei controlli WPF non sono nemmeno Windows nel senso Win32 (e per estensione Windows.Forms), quindi non avranno WndProcs.

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

È possibile farlo tramite il namespace System.Windows.Interop 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 preso dalla eccellente post del blog: Using a custom WndProc in WPF apps by Steve Rands (nota, il collegamento non è più valido)

Questo sito è giù, ma ora si può vedere dal Wayback motore: http://web.archive.org/web/20091019124817/http://www.steverands.com/2009/03/19/custom-wndproc-wpf-apps/

+0

Il collegamento è rotto. Potresti aggiustarlo per favore? –

+1

@ Martin, perché il sito web di Steve Rand non esiste più. L'unica correzione che riesco a pensare è rimuoverla. 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. –

+0

È possibile ricevere messaggi WndProc senza una finestra? – Mo0gles

4

È possibile trovare un'altra spiegazione per il collegamento a WndProc here.

0

Se non ti interessa fare riferimento a WinForms, puoi utilizzare una soluzione MVVM più orientata che non accoppi il servizio alla vista. È necessario creare e inizializzare 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 di registrarsi per i messaggi che ti interessa e poi ignorare WndProc al loro trattamento:

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

L'unico inconveniente è che bisogna includere System.Windows.Forms di riferimento, ma altrimenti questa è una soluzione molto incapsulata.

Maggiori informazioni su questo possono essere letti here

0

È possibile allegare alla classe dei 'SystemEvents' la classe incorporata Win32:

using Microsoft.Win32; 

in una classe di 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(); 
    } 
} 
Problemi correlati