Jak obsługiwać komunikaty WndProc w WPF?

112

W Windows Forms po prostu przesłoniłbym WndProci zacząłem obsługiwać wiadomości w miarę ich pojawiania się.

Czy ktoś może pokazać mi przykład, jak osiągnąć to samo w WPF?

Shuft
źródło

Odpowiedzi:

62

Właściwie, o ile rozumiem, taka rzecz jest rzeczywiście możliwa w WPF przy użyciu HwndSourcei HwndSourceHook. Zobacz ten wątek w witrynie MSDN jako przykład. (Odpowiedni kod zawarty poniżej)

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

Teraz nie jestem do końca pewien, dlaczego chcesz obsługiwać wiadomości systemu Windows Messaging w aplikacji WPF (chyba że jest to najbardziej oczywista forma współpracy przy pracy z inną aplikacją WinForms). Ideologia projektowania i natura API jest bardzo różna w WPF od WinForms, więc sugerowałbym po prostu zapoznanie się z WPF, aby zobaczyć dokładnie, dlaczego nie ma odpowiednika WndProc.

Noldorin
źródło
48
Cóż, zdarzenia USB Device (dis) connect wydają się przechodzić przez tę pętlę komunikatów, więc nie jest źle wiedzieć, jak podłączyć się do WPF
flq
7
@Noldorin: Czy możesz podać odniesienia (artykuły / książki), które pomogą mi zrozumieć część „Ideologia projektowania i natura API są bardzo różne w WPF od WinForms, ... dlaczego nie ma odpowiednika WndProc”?
atiyar
2
WM_MOUSEWHEELna przykład jedynym sposobem niezawodnego przechwytywania tych komunikatów było dodanie WndProcdo okna WPF. To zadziałało dla mnie, podczas gdy urzędnik MouseWheelEventHandlerpo prostu nie działał zgodnie z oczekiwaniami. Nie udało mi się uzyskać poprawnych tachionów WPF ustawionych w odpowiedniej linii, aby uzyskać niezawodne zachowanie MouseWheelEventHandler, stąd potrzeba bezpośredniego dostępu do WndProc.
Chris O
4
Faktem jest, że wiele (większość?) Aplikacji WPF jest uruchamianych na standardowym pulpicie Windows. To, że architektura WPF decyduje się nie ujawniać wszystkich podstawowych możliwości Win32, jest celowe ze strony firmy Microsoft, ale nadal jest irytujące. Buduję aplikację WPF, która jest przeznaczona tylko dla komputerów stacjonarnych z systemem Windows, ale integruje się z urządzeniami USB, jak wspomniano @flq, a jedynym sposobem na otrzymywanie powiadomień z urządzeń jest dostęp do pętli komunikatów. Czasami złamanie abstrakcji jest nieuniknione.
NathanAldenSr
1
Monitorowanie schowka jest jednym z powodów, dla których możemy potrzebować WndProc. Innym jest wykrywanie, że aplikacja nie jest bezczynna, poprzez przetwarzanie komunikatów.
user34660
135

Możesz to zrobić za pośrednictwem System.Windows.Interopprzestrzeni nazw, która zawiera klasę o nazwie HwndSource.

Przykład użycia tego

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

Całkowicie zaczerpnięte z doskonałego wpisu na blogu: Używanie niestandardowego WndProc w aplikacjach WPF autorstwa Steve'a Randsa

Robert MacLean
źródło
1
Link jest uszkodzony. Czy mógłbyś to naprawić?
Martin Hennings
1
@Martin, to dlatego, że strona internetowa Steve'a Randa już nie istnieje. Jedyną poprawką, jaką przychodzi mi do głowy, jest usunięcie go. Myślę, że to nadal będzie wartościowe, jeśli witryna powróci w przyszłości, więc nie usuwam jej - ale jeśli się nie zgadzasz, możesz edytować.
Robert MacLean
Czy można odbierać wiadomości WndProc bez okna?
Mo0gles
8
@ Mo0gles - zastanów się dokładnie, o co pytałeś, a otrzymasz odpowiedź.
Ian Kemp
1
@ Mo0gles Bez okna, które jest rysowane na ekranie i widoczne dla użytkownika? Tak. Dlatego niektóre programy mają dziwnie pusty system Windows, który czasami staje się widoczny, jeśli stan programu zostanie uszkodzony.
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
źródło
3

Jeśli nie masz nic przeciwko odwołaniu się do WinForms, możesz użyć rozwiązania bardziej zorientowanego na MVVM, które nie łączy usługi z widokiem. Musisz utworzyć i zainicjować System.Windows.Forms.NativeWindow, który jest lekkim oknem, które może odbierać wiadomości.

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

Użyj SpongeHandle, aby zarejestrować wiadomości, które Cię interesują, a następnie zastąp WndProc, aby je przetworzyć:

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

Jedynym minusem jest to, że musisz dołączyć odniesienie do System.Windows.Forms, ale poza tym jest to bardzo zamknięte rozwiązanie.

Więcej na ten temat można przeczytać tutaj

Tyrrrz
źródło
1

Oto link do zastępowania WindProc za pomocą Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Edycja: lepiej późno niż wcale] Poniżej znajduje się moja realizacja na podstawie powyższego linku. Chociaż wracam do tego, bardziej podobają mi się implementacje AddHook. Mógłbym przejść na to.

W moim przypadku chciałem wiedzieć, kiedy zmieniano rozmiar okna i kilka innych rzeczy. Ta implementacja łączy się z xaml okna i wysyła zdarzenia.

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
źródło
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
Maksymalnie
@max> teraz jest na to prawdopodobnie trochę za późno.
Rook
1
@Rook Myślę, że usługa recenzji StackOverflow działa dziwnie, miałem tylko 20 dokładnych: Here is a link...odpowiedzi, jak wyżej.
Max
1
@Max Trochę za późno, ale zaktualizowałem odpowiedź, dodając odpowiedni kod.
Wes
0

Możesz dołączyć do klasy „SystemEvents” wbudowanej klasy Win32:

using Microsoft.Win32;

w klasie okna 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
źródło
-1

Istnieją sposoby obsługi komunikatów za pomocą WndProc w WPF (np. Przy użyciu HwndSource itp.), Ale ogólnie te techniki są zarezerwowane do współdziałania z komunikatami, których nie można bezpośrednio obsłużyć przez WPF. Większość kontrolek WPF nie jest nawet oknami w sensie Win32 (i przez rozszerzenie Windows.Forms), więc nie będą miały WndProcs.

Logan Capaldo
źródło
-1 / Niedokładne. Chociaż prawdą jest, że formularze WPF nie są formularzami WinForm i w związku z tym nie są narażone WndProcna przesłonięcie, System.Windows.Interoppozwala uzyskać HwndSourceobiekt za pomocą HwndSource.FromHwndlub PresentationSource.FromVisual(someForm) as HwndSource, z którym można powiązać delegata ze specjalnym wzorcem. Ten delegat ma wiele takich samych argumentów, jak WndProcobiekt Message.
Andrew Gray,
W odpowiedzi wspominam o HwndSource? Z pewnością twoje okno najwyższego poziomu będzie miało HWND, ale nadal można powiedzieć, że większość elementów sterujących nie jest.
Logan Capaldo
-13

Krótka odpowiedź brzmi: nie możesz. WndProc działa poprzez przekazywanie komunikatów do HWND na poziomie Win32. Okna WPF nie mają HWND i dlatego nie mogą uczestniczyć w komunikatach WndProc. Podstawowa pętla komunikatów WPF znajduje się na szczycie WndProc, ale usuwa je z podstawowej logiki WPF.

Możesz użyć HWndHost i uzyskać za to WndProc. Jednak prawie na pewno nie jest to to, co chcesz robić. W większości przypadków WPF nie działa na HWND i WndProc. Twoje rozwiązanie prawie na pewno polega na wprowadzeniu zmiany w WPF, a nie w WndProc.

JaredPar
źródło
13
„Okna WPF nie mają HWND” - to jest po prostu nieprawda.
Scott Solmer,