Як обробити повідомлення WndProc у WPF?


112

У Windows Forms я просто переосмислив WndProcі почав обробляти повідомлення, коли вони надходили.

Чи може хтось показати мені приклад того, як досягти того ж в WPF?

Відповіді:


62

Насправді, наскільки я розумію, таке дійсно можливо в рамках використання WPF HwndSourceта HwndSourceHook. Дивіться цю тему на MSDN як приклад. (Відповідний код включений нижче)

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

Зараз я не зовсім впевнений, чому ви хочете обробляти повідомлення Windows Messaging у програмі WPF (якщо це не найочевидніша форма інтеропу для роботи з іншим додатком WinForms). Ідеологія дизайну та характер API дуже відрізняються у WPF від WinForms, тому я б запропонував вам просто ознайомитися з WPF докладніше, щоб точно зрозуміти, чому немає еквіваленту WndProc.


48
Добре, що події підключення USB-пристроїв (не) начебто надходять через цей цикл повідомлень, тож непогано знати, як підключитися з WPF
flq

7
@Noldorin: Чи можете ви надати посилання (статті / книги), які можуть допомогти мені зрозуміти частину "Ідеологія дизайну та природа API дуже відрізняються у WPF від WinForms, ... чому немає еквівалента WndProc"?
атіяр

2
WM_MOUSEWHEELнаприклад, єдиним способом надійно захопити ці повідомлення було додавання WndProcдо вікна WPF. Це працювало для мене, тоді як чиновник MouseWheelEventHandlerпросто не працював так, як очікувалося. Мені не вдалося отримати правильні тахіони WPF, які вишикувалися правильно, щоб отримати надійну поведінку MouseWheelEventHandler, отже, необхідність прямого доступу до WndProc.
Кріс О

4
Справа в тому, що багато (більшість?) Програм WPF запускаються на стандартних настільних Windows. Те, що архітектура WPF вирішує не викривати всі основні можливості Win32, навмисно з боку Microsoft, але все ще прикро для вирішення. Я будую програму WPF, яка націлена лише на настільні Windows, але інтегрується з USB-пристроями, як згадується @flq, і єдиний спосіб отримувати сповіщення про пристрої - це доступ до циклу повідомлень. Іноді порушення абстракції неминуче.
NathanAldenSr

1
Моніторинг буфера обміну - одна з причин, що нам може знадобитися WndProc. Інша - виявити, що програма не працює в режимі очікування, обробляючи повідомлення.
користувач34660

135

Це можна зробити за допомогою System.Windows.Interopпростору імен, що містить клас з назвою HwndSource.

Приклад використання цього

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

Повністю взяті з чудової публікації в блозі: Використання користувальницького WndProc у програмах WPF Стівом Рандсом


1
Посилання розірвано. Не могли б ви це виправити?
Мартін Геннінгс

1
@Martin, це тому, що веб-сайту Стіва Ранда більше не існує. Єдине виправлення, про яке я можу придумати - це видалити його. Я думаю, що це все ще додасть цінності, якщо сайт повернеться в майбутньому, тому я його не видаляю - але якщо ви не згодні, не соромтесь редагувати.
Роберт Маклін

Чи можна отримувати повідомлення WndProc без вікна?
Mo0gles

8
@ Mo0gles - добре подумай над тим, що ти запитував, і ти отримаєш свою відповідь.
Ян Кемп

1
@ Mo0gles Без вікна, яке намальоване на екрані та видиме користувачеві? Так. Ось чому деякі програми мають дивні порожні Windows, які іноді стають видимими, якщо стан програми пошкоджується.
Петро,

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

Якщо ви не проти посилатися на WinForms, ви можете скористатись більш орієнтованим на MVVM рішенням, яке не поєднує сервіс із представленням даних. Вам потрібно створити та ініціалізувати System.Windows.Forms.NativeWindow, яке є легким вікном, яке може приймати повідомлення.

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

Використовуйте SpongeHandle для реєстрації повідомлень, які вас цікавлять, а потім замініть WndProc, щоб обробити їх:

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

Єдиним недоліком є ​​те, що ви повинні включити посилання System.Windows.Forms, але в іншому випадку це дуже інкапсульоване рішення.

Більше про це можна прочитати тут


1

Ось посилання на переважання WindProc за допомогою поведінки: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Редагувати: краще пізніше ніж ніколи] Нижче наводиться моя реалізація на основі вищезазначеного посилання. Хоча переглянувши це, мені більше подобаються реалізації AddHook. Я можу перейти до цього.

У моєму випадку я хотів дізнатися, коли змінюється розмір вікна та ще кілька речей. Ця реалізація підключається до Xaml Window та надсилає події.

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>

Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться.
Макс

@max> це, мабуть, трохи пізно для цього зараз.
Грак

1
@Rook Я думаю, що служба перегляду StackOverflow діється дивно, мені просто сподобалось 20 точних: Here is a link...відповідей, як вище.
Макс

1
@Max Трохи запізнився, але я оновив свою відповідь, щоб включити відповідний код.
Уес

0

Ви можете приєднатися до класу 'SystemEvents' вбудованого класу Win32:

using Microsoft.Win32;

у класі вікон 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

Існують способи обробки повідомлень із WndProc у WPF (наприклад, використання HwndSource тощо), але зазвичай ці методи зарезервовані для взаємодії з повідомленнями, які не можуть безпосередньо оброблятися через WPF. Більшість елементів керування WPF - це навіть не вікна в сенсі Win32 (і за допомогою розширення Windows.Forms), тому WndProcs у них не буде.


-1 / Неточний. Хоча це правда, що форми WPF не є WinForms, і тому вони не піддаються WndProcпереопрацюванню, це System.Windows.Interopдозволяє вам отримати HwndSourceоб'єкт за допомогою HwndSource.FromHwndабо PresentationSource.FromVisual(someForm) as HwndSource, до якого ви можете зв'язати спеціально з малюнком делегата. Цей делегат має багато тих же аргументів, що й об’єкт WndProcповідомлення.
Ендрю Грей

Я згадую HwndSource у відповіді? Звичайно, у вашому вікні верхнього рівня буде HWND, але все одно точно сказати, що більшість елементів контролю не є.
Логан Капальдо


-13

Коротка відповідь - ви не можете. WndProc працює, передаючи повідомлення до HWND на рівні Win32. Вікна WPF не мають HWND, а отже, не можуть брати участь у повідомленнях WndProc. Базовий цикл повідомлень WPF дійсно знаходиться на вершині WndProc, але він абстрагує їх від основної логіки WPF.

Ви можете використовувати HWndHost і потрапити на WndProc для цього. Однак це майже точно не те, що ви хочете робити. Здебільшого WPF не працює на HWND та WndProc. Ваше рішення майже напевно покладається на те, щоб змінити WPF не у WndProc.


13
"Вікна WPF не мають HWND" - Це просто неправда.
Скотт Солмер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.