Wie gehe ich mit WndProc-Nachrichten in WPF um?

112

In Windows Forms habe ich nur überschrieben WndProcund angefangen, Nachrichten zu verarbeiten, sobald sie eingegangen sind.

Kann mir jemand ein Beispiel zeigen, wie man in WPF dasselbe erreicht?

Shuft
quelle

Antworten:

62

Soweit ich weiß, ist so etwas in WPF tatsächlich mit HwndSourceund möglich HwndSourceHook. Sehen Sie sich diesen Thread auf MSDN als Beispiel an. (Relevanter Code unten enthalten)

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

Ich bin mir nicht ganz sicher, warum Sie Windows Messaging-Nachrichten in einer WPF-Anwendung verarbeiten möchten (es sei denn, dies ist die offensichtlichste Form der Interop-Funktion für die Arbeit mit einer anderen WinForms-App). Die Designideologie und die Art der API unterscheiden sich in WPF stark von WinForms. Ich würde daher empfehlen, dass Sie sich nur mit WPF vertraut machen, um genau zu sehen, warum es kein Äquivalent zu WndProc gibt.

Noldorin
quelle
48
Nun, USB-Geräte (dis) connect-Ereignisse scheinen über diese Nachrichtenschleife zu kommen, daher ist es keine schlechte Sache zu wissen, wie man sich von WPF
flq
7
@Noldorin: Können Sie bitte Referenzen (Artikel / Bücher) angeben, die mir helfen, den Teil "Die Designideologie und die Art der API unterscheiden sich in WPF stark von WinForms, ... warum gibt es kein Äquivalent zu WndProc" zu verstehen?
Atiyar
2
WM_MOUSEWHEELDie einzige Möglichkeit, diese Nachrichten zuverlässig abzufangen, bestand beispielsweise darin, WndProcsie einem WPF-Fenster hinzuzufügen . Das hat bei mir funktioniert, während der Beamte MouseWheelEventHandlereinfach nicht wie erwartet funktioniert hat. Ich war nicht in der Lage, die richtigen WPF-Tachyonen genau richtig auszurichten, um ein zuverlässiges Verhalten zu MouseWheelEventHandlererzielen, weshalb ein direkter Zugriff auf die erforderlich war WndProc.
Chris O
4
Tatsache ist, dass viele (die meisten?) WPF-Anwendungen unter Standard-Desktop-Windows ausgeführt werden. Dass die WPF-Architektur nicht alle zugrunde liegenden Funktionen von Win32 verfügbar macht, ist von Microsoft beabsichtigt, aber dennoch ärgerlich. Ich erstelle eine WPF-Anwendung, die nur auf Desktop-Windows abzielt, aber wie @ flq in USB-Geräte integriert ist. Die einzige Möglichkeit, Gerätebenachrichtigungen zu erhalten, besteht darin, auf die Nachrichtenschleife zuzugreifen. Manchmal ist es unvermeidlich, die Abstraktion zu brechen.
NathanAldenSr
1
Die Überwachung der Zwischenablage ist ein Grund, warum wir möglicherweise einen WndProc benötigen. Eine andere Möglichkeit besteht darin, durch Verarbeiten von Nachrichten zu erkennen, dass die Anwendung nicht inaktiv ist.
user34660
135

Sie können dies über den System.Windows.InteropNamespace tun, der eine Klasse mit dem Namen enthält HwndSource.

Beispiel für die Verwendung

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

Vollständig aus dem ausgezeichneten Blog-Beitrag entnommen: Verwenden eines benutzerdefinierten WndProc in WPF-Apps von Steve Rands

Robert MacLean
quelle
1
Die Verbindung ist unterbrochen. Könnten Sie es bitte beheben?
Martin Hennings
1
@ Martin, das liegt daran, dass die Website von Steve Rand nicht mehr existiert. Die einzige Lösung, die mir einfällt, ist, sie zu entfernen. Ich denke, es ist immer noch ein Mehrwert, wenn die Site in Zukunft wiederkommt, also entferne ich sie nicht - aber wenn Sie nicht einverstanden sind, können Sie sie gerne bearbeiten.
Robert MacLean
Ist es möglich, WndProc-Nachrichten ohne Fenster zu empfangen?
Mo0gles
8
@ Mo0gles - Überlegen Sie genau, was Sie gefragt haben, und Sie werden Ihre Antwort erhalten.
Ian Kemp
1
@ Mo0gles Ohne ein Fenster, das auf dem Bildschirm gezeichnet und für den Benutzer sichtbar ist? Ja. Aus diesem Grund haben einige Programme seltsame leere Fenster, die manchmal sichtbar werden, wenn der Status des Programms beschädigt wird.
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
quelle
3

Wenn es Ihnen nichts ausmacht, auf WinForms zu verweisen, können Sie eine MVVM-orientierte Lösung verwenden, die den Dienst nicht mit der Ansicht koppelt. Sie müssen ein System.Windows.Forms.NativeWindow erstellen und initialisieren. Dies ist ein kompaktes Fenster, das Nachrichten empfangen kann.

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

Verwenden Sie SpongeHandle, um sich für Nachrichten zu registrieren, an denen Sie interessiert sind, und überschreiben Sie dann WndProc, um sie zu verarbeiten:

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

Der einzige Nachteil ist, dass Sie die System.Windows.Forms-Referenz einschließen müssen, ansonsten ist dies eine sehr gekapselte Lösung.

Mehr dazu lesen Sie hier

Tyrrrz
quelle
1

Hier ist ein Link zum Überschreiben von WindProc mithilfe von Verhalten: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Bearbeiten: besser spät als nie] Unten ist meine Implementierung basierend auf dem obigen Link. Obwohl ich das noch einmal besuche, mag ich die AddHook-Implementierungen besser. Ich könnte dazu wechseln.

In meinem Fall wollte ich wissen, wann die Größe des Fensters geändert wurde und ein paar andere Dinge. Diese Implementierung wird mit dem Fenster xaml verbunden und sendet Ereignisse.

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>
Wir s
quelle
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert.
Max
@max> dafür ist es jetzt wahrscheinlich etwas spät.
Turm
1
@Rook Ich denke, der Überprüfungsservice von StackOverflow verhält sich seltsam, ich hatte gerade 20 exakte Here is a link...Antworten wie oben.
Max
1
@Max Etwas spät, aber ich habe meine Antwort aktualisiert, um den entsprechenden Code aufzunehmen.
Wes
0

Sie können an die 'SystemEvents'-Klasse der integrierten Win32-Klasse anhängen:

using Microsoft.Win32;

in einer WPF-Fensterklasse:

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
quelle
-1

Es gibt Möglichkeiten, Nachrichten mit einem WndProc in WPF zu verarbeiten (z. B. mithilfe einer HwndSource usw.). Im Allgemeinen sind diese Techniken jedoch für die Interaktion mit Nachrichten reserviert, die nicht direkt über WPF verarbeitet werden können. Die meisten WPF-Steuerelemente sind nicht einmal Windows im Sinne von Win32 (und im weiteren Sinne Windows.Forms), daher verfügen sie nicht über WndProcs.

Logan Capaldo
quelle
-1 / ungenau. Zwar handelt es sich bei WPF-Formularen nicht um WinForms und sie sind daher keiner WndProcÜberschreibung ausgesetzt. Mit dieser Option System.Windows.Interopkönnen Sie jedoch ein HwndSourceObjekt über HwndSource.FromHwndoder abrufen, an das Sie PresentationSource.FromVisual(someForm) as HwndSourceeinen speziell strukturierten Delegaten binden können. Dieser Delegat hat viele der gleichen Argumente wie ein WndProcNachrichtenobjekt.
Andrew Gray
Ich erwähne HwndSource in der Antwort? Sicherlich hat Ihr Fenster der obersten Ebene einen HWND, aber es ist immer noch richtig zu sagen, dass die meisten Steuerelemente dies nicht tun .
Logan Capaldo
-13

Die kurze Antwort lautet: Sie können nicht. WndProc übergibt Nachrichten auf Win32-Ebene an einen HWND. WPF-Fenster haben kein HWND und können daher nicht an WndProc-Nachrichten teilnehmen. Die Basis-WPF-Nachrichtenschleife befindet sich zwar über WndProc, abstrahiert sie jedoch von der WPF-Kernlogik.

Sie können einen HWndHost verwenden und einen WndProc dafür erhalten. Dies ist jedoch mit ziemlicher Sicherheit nicht das, was Sie tun möchten. In den meisten Fällen arbeitet WPF nicht mit HWND und WndProc. Ihre Lösung basiert mit ziemlicher Sicherheit auf einer Änderung in WPF, nicht in WndProc.

JaredPar
quelle
13
"WPF-Fenster haben kein HWND" - Dies ist einfach falsch.
Scott Solmer