Comment gérer les messages WndProc dans WPF?

112

Dans Windows Forms, je remplacerais simplement WndProcet commencerais à gérer les messages au fur et à mesure qu'ils entraient.

Quelqu'un peut-il me montrer un exemple de la façon de réaliser la même chose dans WPF?

Shuft
la source

Réponses:

62

En fait, pour autant que je sache, une telle chose est en effet possible dans WPF en utilisant HwndSourceet HwndSourceHook. Voir ce fil sur MSDN à titre d'exemple. (Code pertinent inclus ci-dessous)

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

Maintenant, je ne sais pas trop pourquoi vous voudriez gérer les messages Windows Messaging dans une application WPF (à moins que ce ne soit la forme d'interopérabilité la plus évidente pour travailler avec une autre application WinForms). L'idéologie de conception et la nature de l'API sont très différentes dans WPF de WinForms, je vous suggère donc de vous familiariser davantage avec WPF pour voir exactement pourquoi il n'y a pas d'équivalent de WndProc.

Noldorin
la source
48
Eh bien, les événements de (dis) connexion de périphérique USB semblent venir sur cette boucle de message, donc ce n'est pas une mauvaise chose de savoir comment se connecter à partir de WPF
flq
7
@Noldorin: Pouvez-vous s'il vous plaît fournir des références (articles / livres) qui peuvent m'aider à comprendre la partie "L'idéologie du design et la nature de l'API sont très différentes dans WPF de WinForms, ... pourquoi il n'y a pas d'équivalent de WndProc"?
atiyar
2
WM_MOUSEWHEELpar exemple, le seul moyen de capturer ces messages de manière fiable était d'ajouter le WndProcà une fenêtre WPF. Cela a fonctionné pour moi, alors que le fonctionnaire MouseWheelEventHandlern'a tout simplement pas fonctionné comme prévu. Je n'ai pas pu obtenir les bons tachyons WPF alignés juste pour obtenir un comportement fiable MouseWheelEventHandler, d'où la nécessité d'un accès direct au WndProc.
Chris O
4
Le fait est que de nombreuses applications WPF (la plupart?) Sont exécutées sur Windows de bureau standard. Le fait que l'architecture WPF choisisse de ne pas exposer toutes les capacités sous-jacentes de Win32 est délibéré de la part de Microsoft, mais reste ennuyeux à gérer. Je construis une application WPF qui cible uniquement Windows de bureau mais s'intègre aux périphériques USB comme @flq mentionné et le seul moyen de recevoir des notifications de périphérique est d'accéder à la boucle de message. Il est parfois inévitable de casser l'abstraction.
NathanAldenSr
1
La surveillance du presse-papiers est l'une des raisons pour lesquelles nous pourrions avoir besoin d'un WndProc. Une autre consiste à détecter que l'application n'est pas inactive en traitant les messages.
user34660
135

Vous pouvez le faire via l' System.Windows.Interopespace de noms qui contient une classe nommée HwndSource.

Exemple d'utilisation de ceci

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

Complètement extrait de l'excellent article de blog: Utilisation d'un WndProc personnalisé dans les applications WPF par Steve Rands

Robert MacLean
la source
1
Le lien est rompu. Pourriez-vous le réparer?
Martin Hennings
1
@Martin, c'est parce que le site Web de Steve Rand n'existe plus. Le seul correctif auquel je pense est de le supprimer. Je pense que cela ajoute encore de la valeur si le site revient dans le futur, donc je ne le supprime pas - mais si vous n'êtes pas d'accord, n'hésitez pas à le modifier.
Robert MacLean
Est-il possible de recevoir des messages WndProc sans fenêtre?
Mo0gles
8
@ Mo0gles - réfléchissez bien à ce que vous avez demandé et vous aurez votre réponse.
Ian Kemp
1
@ Mo0gles Sans une fenêtre dessinée à l'écran et visible par l'utilisateur? Oui. C'est pourquoi certains programmes ont des fenêtres vides étranges qui deviennent parfois visibles si l'état du programme est corrompu.
Peter le
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
la source
3

Si cela ne vous dérange pas de faire référence à WinForms, vous pouvez utiliser une solution plus orientée MVVM qui ne couple pas le service avec la vue. Vous devez créer et initialiser un System.Windows.Forms.NativeWindow qui est une fenêtre légère qui peut recevoir des 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;
    }
}

Utilisez SpongeHandle pour vous inscrire aux messages qui vous intéressent, puis remplacez WndProc pour les traiter:

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

Le seul inconvénient est que vous devez inclure la référence System.Windows.Forms, mais sinon c'est une solution très encapsulée.

Plus d'informations à ce sujet peuvent être lues ici

Tyrrrz
la source
1

Voici un lien sur le remplacement de WindProc à l'aide de Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Edit: mieux vaut tard que jamais] Voici mon implémentation basée sur le lien ci-dessus. Bien que revisitant cela, j'aime mieux les implémentations AddHook. Je pourrais passer à ça.

Dans mon cas, je voulais savoir quand la fenêtre était redimensionnée et quelques autres choses. Cette implémentation se connecte au xaml Windows et envoie des événements.

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>
Nous s
la source
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change.
Max
@max> c'est probablement un peu tard pour ça maintenant.
Tour
1
@Rook Je pense que le service de révision de StackOverflow agit de manière étrange, j'ai juste eu 20 Here is a link...réponses exactes , comme ci-dessus.
Max
1
@Max Un peu en retard mais j'ai mis à jour ma réponse pour inclure le code correspondant.
Wes
0

Vous pouvez vous attacher à la classe 'SystemEvents' de la classe Win32 intégrée:

using Microsoft.Win32;

dans une classe de fenêtre 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
la source
-1

Il existe des moyens de gérer les messages avec un WndProc dans WPF (par exemple en utilisant un HwndSource, etc.), mais généralement ces techniques sont réservées à l'interopérabilité avec des messages qui ne peuvent pas être traités directement via WPF. La plupart des contrôles WPF ne sont même pas des fenêtres au sens Win32 (et par extension Windows.Forms), ils n'auront donc pas WndProcs.

Logan Capaldo
la source
-1 / Inexact. S'il est vrai que les formulaires WPF ne sont pas des WinForms et ne sont donc pas exposés WndProcau remplacement, le System.Windows.Interopvous permet d'obtenir un HwndSourceobjet via HwndSource.FromHwndou PresentationSource.FromVisual(someForm) as HwndSource, auquel vous pouvez lier un délégué spécialement conçu. Ce délégué a plusieurs des mêmes arguments qu'un WndProcobjet Message.
Andrew Gray le
Je mentionne HwndSource dans la réponse? Votre fenêtre de niveau supérieur aura certainement un HWND, mais il est toujours exact de dire que la plupart des contrôles ne le sont pas.
Logan Capaldo
-13

La réponse courte est que vous ne pouvez pas. WndProc fonctionne en passant des messages à un HWND au niveau Win32. Les fenêtres WPF n'ont pas de HWND et ne peuvent donc pas participer aux messages WndProc. La boucle de message WPF de base se trouve au-dessus de WndProc mais elle les fait abstraction de la logique WPF principale.

Vous pouvez utiliser un HWndHost et obtenir un WndProc pour cela. Cependant, ce n'est certainement pas ce que vous voulez faire. Dans la plupart des cas, WPF ne fonctionne pas sur HWND et WndProc. Votre solution repose presque certainement sur la modification de WPF et non de WndProc.

JaredPar
la source
13
"Les fenêtres WPF n'ont pas de HWND" - C'est tout simplement faux.
Scott Solmer