Как обрабатывать сообщения WndProc в WPF?

В Windows Forms я бы просто переопределил WndProc и начал обрабатывать сообщения по мере их поступления.

Может ли кто-нибудь показать мне пример того, как добиться того же в WPF?


person Shuft    schedule 08.03.2009    source источник


Ответы (8)


На самом деле, насколько я понимаю, такое возможно в 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.

person Noldorin    schedule 08.03.2009
comment
Что ж, события подключения USB-устройства (dis), похоже, проходят через этот цикл сообщений, поэтому неплохо знать, как подключиться из WPF - person flq; 14.03.2011
comment
@Noldorin: Не могли бы вы предоставить ссылки (статьи / книги), которые могут помочь мне понять часть. Идеология дизайна и природа API сильно отличаются в WPF от WinForms, ... почему нет эквивалента WndProc? - person atiyar; 19.06.2013
comment
WM_MOUSEWHEEL, например, единственный способ надежно перехватить эти сообщения - это добавить WndProc в окно WPF. У меня это сработало, тогда как официальная MouseWheelEventHandler просто не сработала, как ожидалось. Мне не удалось выстроить правильные тахионы WPF, чтобы обеспечить надежное поведение с MouseWheelEventHandler, отсюда и необходимость прямого доступа к WndProc. - person Chris O; 07.01.2016
comment
Дело в том, что многие (большинство?) Приложений WPF запускаются на стандартной настольной Windows. То, что архитектура WPF предпочитает не раскрывать все базовые возможности Win32, является преднамеренным со стороны Microsoft, но все еще раздражает. Я создаю приложение WPF, которое нацелено только на настольную Windows, но интегрируется с USB-устройствами, как упоминалось в @flq, и единственный способ получать уведомления устройства - это получить доступ к циклу сообщений. Иногда нарушение абстракции неизбежно. - person NathanAldenSr; 21.06.2016
comment
Мониторинг буфера обмена - одна из причин, по которой нам может понадобиться WndProc. Другой - определить, что приложение не простаивает, путем обработки сообщений. - person user34660; 12.02.2018
comment
Еще одна причина - выделение кнопки [X]: stackoverflow.com/a/13855608/3195477 В принципе, WPF не может сделай сам. - person StayOnTarget; 23.04.2020
comment
обработка WM_POWERBROADCAST например - person Boppity Bop; 03.11.2020

Вы можете сделать это через пространство имен 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, Стив Рэндс

person Robert MacLean    schedule 18.12.2009
comment
Ссылка не работает. Не могли бы вы это исправить? - person Martin Hennings; 26.01.2011
comment
@Martin, это потому, что веб-сайт Стива Рэнда больше не существует. Единственное, что я могу придумать, - это удалить его. Я думаю, что это все равно будет приносить пользу, если сайт вернется в будущем, поэтому я не удаляю его, но если вы не согласны, не стесняйтесь редактировать. - person Robert MacLean; 27.01.2011
comment
Можно ли получать сообщения WndProc без окна? - person Mo0gles; 03.09.2013
comment
@ Mo0gles - хорошенько подумайте о том, что вы спросили, и вы получите свой ответ. - person Ian Kemp; 12.04.2014
comment
@ Mo0gles Без окна, которое нарисовано на экране и видно пользователю? да. Вот почему у некоторых программ есть странные пустые окна, которые иногда становятся видимыми, если состояние программы нарушается. - person Peter; 02.09.2014
comment
Спасибо за ссылку на веб-архив тому, кто ее добавил. - person Amadeus Sánchez; 30.06.2016
comment
@ Mo0gles они не являются сообщениями WndProc (оконная процедура), если окна нет. Консольная программа может иметь цикл сообщений и получать в нем некоторые сообщения. - person user34660; 12.02.2018
comment
@ user34660: Вам нужно окно. Питер говорил о том, что вам не нужно окно, видимое пользователю, вы можете использовать то, которое скрыто. - person Ben Voigt; 19.09.2018
comment
@BenVoigt то, что я сказал, верно, как в PostThreadMessage в консольном приложении. Комментарий не имеет отношения к этому вопросу, поэтому я не публиковал подробностей, чтобы избежать ненужного обсуждения, такого как это. Комментарий неуместный, верно? - person user34660; 25.09.2018
comment
@ user34660: Извините, я неправильно понял вашу мысль. К сожалению, OP не сказал, какие именно сообщения он пытается получить, но, поскольку есть целая куча, которые отправляются только в окна, никогда не отправляются напрямую в поток, вполне вероятно, что OP действительно нужно (возможно, скрытое) окно . Если он сам отправляет сообщения, а не подписывается на системные уведомления, то, возможно, цикл сообщений без окна может работать, как вы говорите. - person Ben Voigt; 25.09.2018
comment
Я использую dotnet core 3.1. Этот ответ сработал для меня. Я попытался вызвать HwndSource source = ... source.AddHook в конструкторе приложения, но это не удалось (исключение с нулевой ссылкой), он работал только в переопределении OnSourceInitialized, как указано в этом ответе. - person BurnsBA; 22.12.2019

Если вы не против ссылаться на 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, но в остальном это очень инкапсулированное решение.

Подробнее об этом можно прочитать здесь

person Tyrrrz    schedule 02.02.2017

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

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

В моем случае я хотел знать, когда меняли размер окна, и еще пару вещей. Эта реализация подключается к Window xaml и отправляет события.

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>
person Wes    schedule 08.04.2011

Вы можете присоединиться к классу 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();
    }
}
person AndresRohrAtlasInformatik    schedule 08.09.2017

Существуют способы обработки сообщений с помощью WndProc в WPF (например, с использованием HwndSource и т. Д.), Но обычно эти методы зарезервированы для взаимодействия с сообщениями, которые не могут быть обработаны напрямую через WPF. Большинство элементов управления WPF даже не являются окнами в смысле Win32 (и расширением Windows.Forms), поэтому у них не будет WndProcs.

person Logan Capaldo    schedule 08.03.2009
comment
-1 / Неточно. Хотя это правда, что формы WPF не являются WinForms и, следовательно, не имеют открытого WndProc для переопределения, System.Windows.Interop позволяет вам получить объект HwndSource посредством HwndSource.FromHwnd или PresentationSource.FromVisual(someForm) as HwndSource, к которому вы можете привязать делегат с особым шаблоном. Этот делегат имеет многие из тех же аргументов, что и объект WndProc Message. - person Andrew Gray; 04.06.2020
comment
Я упоминаю HwndSource в ответе? Конечно, ваше окно верхнего уровня будет иметь HWND, но все же будет правильным сказать, что большинство элементов управления им не соответствует. - person Logan Capaldo; 08.06.2020

WPF не работает с типом WinForms wndprocs

Вы можете разместить HWndHost в соответствующем элементе WPF, а затем переопределить wndproc Hwndhost, но, AFAIK, это настолько близко, насколько вы собираетесь получить.

http://msdn.microsoft.com/en-us/library/ms742522.aspx

http://blogs.msdn.com/nickkramer/archive/2006/03/18/554235.aspx

person user72491    schedule 08.03.2009

Короткий ответ: вы не можете. WndProc работает, передавая сообщения HWND на уровне Win32. Окна WPF не имеют HWND и, следовательно, не могут участвовать в сообщениях WndProc. Базовый цикл сообщений WPF находится поверх WndProc, но абстрагирует их от базовой логики WPF.

Вы можете использовать HWndHost и получить для него WndProc. Однако это почти наверняка не то, что вам нужно. В большинстве случаев WPF не работает с HWND и WndProc. Ваше решение почти наверняка зависит от внесения изменений в WPF, а не в WndProc.

person JaredPar    schedule 08.03.2009
comment
Окна WPF не имеют HWND - это просто неправда. - person Scott Solmer; 03.02.2014
comment
Окна Wpf имеют HWND. Доступ к окну HWND можно получить с помощью этого: var hwnd = new WindowInteropHelper (window) .Handle; и это очень просто. - person trickymind; 28.11.2020