Como lidar com mensagens WndProc em WPF?

112

No Windows Forms, eu apenas substituiria WndProce começaria a lidar com as mensagens assim que chegassem

Alguém pode me mostrar um exemplo de como conseguir a mesma coisa no WPF?

Shuft
fonte

Respostas:

62

Na verdade, tanto quanto eu entendo tal coisa é realmente possível em WPF usando HwndSourcee HwndSourceHook. Veja este tópico no MSDN como um exemplo. (Código relevante incluído abaixo)

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

Agora, não tenho certeza de por que você deseja lidar com mensagens do Windows Messaging em um aplicativo WPF (a menos que seja a forma mais óbvia de interoperabilidade para trabalhar com outro aplicativo WinForms). A ideologia de design e a natureza da API são muito diferentes no WPF e no WinForms, então eu sugiro que você se familiarize mais com o WPF para ver exatamente por que não há equivalente do WndProc.

Noldorin
fonte
48
Bem, os eventos de (des) conexão de dispositivo USB parecem estar vindo desse loop de mensagens, então não é uma coisa ruim saber como conectar a partir do WPF
flq
7
@Noldorin: Você pode fornecer referências (artigos / livros) que podem me ajudar a entender a parte "A ideologia do design e a natureza da API são muito diferentes no WPF do WinForms, ... por que não há equivalente do WndProc"?
atiyar
2
WM_MOUSEWHEELpor exemplo, a única maneira de interceptar essas mensagens de maneira confiável era adicionando o WndProca uma janela do WPF. Isso funcionou para mim, enquanto o oficial MouseWheelEventHandlersimplesmente não funcionou conforme o esperado. Não consegui alinhar os táquions WPF corretos para obter um comportamento confiável MouseWheelEventHandler, daí a necessidade de acesso direto ao WndProc.
Chris O de
4
O fato é que muitos (a maioria?) Aplicativos WPF são executados no Windows desktop padrão. O fato de a arquitetura WPF optar por não expor todos os recursos básicos do Win32 é deliberado da parte da Microsoft, mas ainda assim é um problema de lidar. Estou construindo um aplicativo WPF que se destina apenas ao Windows desktop, mas integra-se a dispositivos USB como @flq mencionou e a única maneira de receber notificações de dispositivo é acessar o loop de mensagem. Às vezes, quebrar a abstração é inevitável.
NathanAldenSr de
1
Monitorar a área de transferência é um dos motivos pelos quais podemos precisar de um WndProc. Outra é detectar se o aplicativo não está ocioso processando mensagens.
user34660
135

Você pode fazer isso por meio do System.Windows.Interopnamespace que contém uma classe chamada HwndSource.

Exemplo de uso

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

Completamente retirado da excelente postagem do blog: Usando um WndProc personalizado em aplicativos WPF por Steve Rands

Robert MacLean
fonte
1
O link está quebrado. Você poderia consertar isso?
Martin Hennings
1
@Martin, é porque o site de Steve Rand não existe mais. A única solução que posso pensar é removê-lo. Acho que ainda agrega valor se o site retornar no futuro, então não o removerei - mas se você discordar, sinta-se à vontade para editar.
Robert MacLean
É possível receber mensagens WndProc sem janela?
Mo0gles,
8
@ Mo0gles - pense cuidadosamente sobre o que perguntou e você terá sua resposta.
Ian Kemp,
1
@ Mo0gles Sem uma janela desenhada na tela e visível para o usuário? Sim. É por isso que alguns programas têm janelas estranhas e vazias que às vezes se tornam visíveis se o estado do programa for corrompido.
Pedro,
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
fonte
3

Se você não se importa em fazer referência a WinForms, pode usar uma solução mais orientada para MVVM que não acople o serviço à visualização. Você precisa criar e inicializar um System.Windows.Forms.NativeWindow, que é uma janela leve que pode receber mensagens.

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

Use SpongeHandle para registrar as mensagens nas quais você está interessado e, em seguida, substitua WndProc para processá-las:

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

A única desvantagem é que você precisa incluir a referência System.Windows.Forms, mas caso contrário, esta é uma solução muito encapsulada.

Mais sobre isso pode ser lido aqui

Tyrrrz
fonte
1

Aqui está um link sobre como substituir WindProc usando Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Editar: antes tarde do que nunca] Abaixo está minha implementação com base no link acima. Apesar de revisitar isso, eu gosto mais das implementações AddHook. Eu posso mudar para isso.

No meu caso, eu queria saber quando a janela estava sendo redimensionada e algumas outras coisas. Essa implementação conecta-se ao xaml do Windows e envia eventos.

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
fonte
Embora este link possa responder à pergunta, é melhor incluir as partes essenciais da resposta aqui e fornecer o link para referência. As respostas somente com link podem se tornar inválidas se a página vinculada mudar.
Máx.
@max> provavelmente é um pouco tarde para isso agora.
Torre de
1
@Rook Acho que o serviço de revisão do StackOverflow está agindo de forma estranha, acabei de receber umas 20 respostas exatas Here is a link..., como acima.
Máx.
1
@Max Um pouco tarde, mas atualizei minha resposta para incluir o código relevante.
Wes
0

Você pode anexar à classe 'SystemEvents' da classe Win32 integrada:

using Microsoft.Win32;

em uma classe de janela 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
fonte
-1

Existem maneiras de lidar com mensagens com um WndProc no WPF (por exemplo, usando um HwndSource, etc.), mas geralmente essas técnicas são reservadas para interoperabilidade com mensagens que não podem ser tratadas diretamente através do WPF. A maioria dos controles WPF nem mesmo são janelas no sentido Win32 (e, por extensão, Windows.Forms), portanto, não terão WndProcs.

Logan Capaldo
fonte
-1 / Impreciso. Embora seja verdade que os formulários WPF não são WinForms e, portanto, não têm exposição WndProcpara substituição, o System.Windows.Interoppermite obter um HwndSourceobjeto por meio de HwndSource.FromHwndou PresentationSource.FromVisual(someForm) as HwndSource, ao qual você pode vincular um delegado especialmente padronizado. Este delegado tem muitos dos mesmos argumentos de um WndProcobjeto Message.
Andrew Gray
Menciono HwndSource na resposta? Certamente sua janela de nível superior terá um HWND, mas ainda é preciso dizer que a maioria dos controles não tem.
Logan Capaldo
-13

A resposta curta é que você não pode. WndProc funciona passando mensagens para um HWND em um nível Win32. As janelas WPF não têm HWND e, portanto, não podem participar de mensagens WndProc. O loop de mensagem básico do WPF fica sobre o WndProc, mas os abstrai da lógica principal do WPF.

Você pode usar um HWndHost e obter um WndProc para isso. No entanto, quase certamente não é isso que você deseja fazer. Para a maioria dos propósitos, o WPF não opera em HWND e WndProc. Sua solução quase certamente depende de fazer uma alteração no WPF e não no WndProc.

JaredPar
fonte
13
"As janelas WPF não têm HWND" - Isso é simplesmente falso.
Scott Solmer