Bagaimana cara menangani pesan WndProc di WPF?

112

Di Windows Forms, saya baru saja mengganti WndProc, dan mulai menangani pesan saat mereka masuk.

Bisakah seseorang menunjukkan kepada saya contoh bagaimana mencapai hal yang sama di WPF?

Shuft
sumber

Jawaban:

62

Sebenarnya, sejauh yang saya pahami hal seperti itu memang mungkin di WPF menggunakan HwndSourcedan HwndSourceHook. Lihat utas ini di MSDN sebagai contoh. (Kode yang relevan disertakan di bawah)

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

Sekarang, saya tidak begitu yakin mengapa Anda ingin menangani pesan Perpesanan Windows dalam aplikasi WPF (kecuali itu adalah bentuk interop yang paling jelas untuk bekerja dengan aplikasi WinForms lain). Ideologi desain dan sifat API sangat berbeda di WPF dari WinForms, jadi saya sarankan Anda membiasakan diri dengan WPF lebih banyak untuk melihat dengan tepat mengapa tidak ada yang setara dengan WndProc.

Noldorin
sumber
48
Nah, peristiwa koneksi Perangkat USB (dis) tampaknya datang melalui lingkaran pesan ini, jadi bukan hal yang buruk untuk mengetahui cara menghubungkan dari WPF
flq
7
@Noldorin: Bisakah Anda memberikan referensi (artikel / buku) yang dapat membantu saya memahami bagian "Ideologi desain dan sifat API sangat berbeda di WPF dari WinForms, ... mengapa tidak ada yang setara dengan WndProc"?
atiyar
2
WM_MOUSEWHEELmisalnya, satu-satunya cara untuk menjebak pesan tersebut adalah dengan menambahkan WndProcke jendela WPF. Ini berhasil untuk saya, sedangkan pejabat MouseWheelEventHandlertidak bekerja seperti yang diharapkan. Saya tidak bisa mendapatkan tachyons WPF yang benar yang berbaris tepat untuk mendapatkan perilaku yang dapat diandalkan MouseWheelEventHandler, oleh karena itu perlu akses langsung ke WndProc.
Chris O
4
Faktanya adalah, banyak (kebanyakan?) Aplikasi WPF dijalankan pada Windows desktop standar. Bahwa arsitektur WPF memilih untuk tidak mengekspos semua kemampuan yang mendasari Win32 disengaja dari pihak Microsoft, tetapi masih mengganggu untuk ditangani. Saya sedang membangun aplikasi WPF yang hanya menargetkan Windows desktop tetapi terintegrasi dengan perangkat USB seperti yang disebutkan @flq dan satu-satunya cara untuk menerima pemberitahuan perangkat adalah dengan mengakses loop pesan. Terkadang melanggar abstraksi tidak dapat dihindari.
NathanAldenSr
1
Memantau papan klip adalah salah satu alasan kami mungkin membutuhkan WndProc. Cara lainnya adalah mendeteksi bahwa aplikasi tidak menganggur dengan memproses pesan.
pengguna34660
135

Anda dapat melakukan ini melalui System.Windows.Interopnamespace yang berisi kelas bernama HwndSource.

Contoh penggunaan ini

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

Sepenuhnya diambil dari posting blog yang luar biasa: Menggunakan WndProc khusus di aplikasi WPF oleh Steve Rands

Robert MacLean
sumber
1
Tautannya rusak. Bisakah Anda memperbaikinya?
Martin Hennings
1
@Martin, itu karena situs Steve Rand sudah tidak ada lagi. Satu-satunya perbaikan yang dapat saya pikirkan adalah menghapusnya. Saya pikir itu masih menambah nilai jika situs kembali di masa mendatang jadi saya tidak menghapusnya - tetapi jika Anda tidak setuju silakan mengedit.
Robert MacLean
Apakah mungkin menerima pesan WndProc tanpa jendela?
Mo0gles
8
@ Mo0gles - pikirkan baik-baik apa yang Anda tanyakan, dan Anda akan mendapatkan jawabannya.
Ian Kemp
1
@ Mo0gles Tanpa jendela yang digambar di layar dan terlihat oleh pengguna? Iya. Itulah sebabnya beberapa program memiliki Windows kosong yang aneh yang terkadang terlihat jika status program rusak.
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.dll
sumber
3

Jika Anda tidak keberatan merujuk WinForms, Anda dapat menggunakan solusi yang lebih berorientasi MVVM yang tidak memasangkan layanan dengan tampilan. Anda perlu membuat dan menginisialisasi System.Windows.Forms.NativeWindow yang merupakan jendela ringan yang dapat menerima pesan.

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

Gunakan SpongeHandle untuk mendaftar pesan yang Anda minati dan kemudian ganti WndProc untuk memprosesnya:

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

Satu-satunya downside adalah Anda harus menyertakan referensi System.Windows.Forms, tetapi selain itu ini adalah solusi yang sangat dikemas.

Lebih lanjut tentang ini bisa dibaca di sini

Tyrrrz
sumber
1

Berikut ini tautan untuk mengganti WindProc menggunakan Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Sunting: lebih baik terlambat daripada tidak sama sekali] Di bawah ini adalah implementasi saya berdasarkan tautan di atas. Meskipun mengunjungi kembali ini, saya lebih menyukai implementasi AddHook. Saya mungkin beralih ke itu.

Dalam kasus saya, saya ingin tahu kapan jendela diubah ukurannya dan beberapa hal lainnya. Implementasi ini terhubung ke Window xaml dan mengirimkan event.

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
sumber
Meskipun tautan ini mungkin menjawab pertanyaan, lebih baik menyertakan bagian penting dari jawaban di sini dan menyediakan tautan untuk referensi. Jawaban link saja bisa menjadi tidak valid jika halaman tertaut berubah.
Maksimal
@max> mungkin agak terlambat untuk itu sekarang.
Benteng
1
@Rook Saya pikir layanan ulasan StackOverflow bertingkah aneh, saya hanya punya 20 Here is a link...jawaban tepat , seperti di atas.
Maksimal
1
@Max Agak terlambat tetapi saya memperbarui jawaban saya untuk memasukkan kode yang relevan.
Wes
0

Anda dapat melampirkan ke kelas 'SystemEvents' dari kelas Win32 bawaan:

using Microsoft.Win32;

di kelas jendela 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
sumber
-1

Ada cara untuk menangani pesan dengan WndProc di WPF (misalnya menggunakan HwndSource, dll.), Tetapi umumnya teknik tersebut dicadangkan untuk interop dengan pesan yang tidak dapat ditangani secara langsung melalui WPF. Kebanyakan kontrol WPF bahkan bukan windows dalam pengertian Win32 (dan dengan ekstensi Windows.Forms), jadi mereka tidak akan memiliki WndProcs.

Logan Capaldo
sumber
-1 / Tidak akurat. Meskipun benar bahwa formulir WPF bukanlah WinForms, dan oleh karena itu tidak terekspos WndProcuntuk ditimpa, namun System.Windows.Interopmemungkinkan Anda untuk mendapatkan HwndSourceobjek dengan cara HwndSource.FromHwndatau PresentationSource.FromVisual(someForm) as HwndSource, yang dapat Anda ikat ke delegasi berpola khusus. Delegasi ini memiliki banyak argumen yang sama dengan WndProcobjek Message.
Andrew Grey
Saya menyebutkan HwndSource dalam jawabannya? Tentu saja jendela tingkat atas Anda akan memiliki HWND, tetapi masih akurat untuk mengatakan bahwa sebagian besar kontrol tidak.
Logan Capaldo
-13

Jawaban singkatnya adalah Anda tidak bisa. WndProc bekerja dengan meneruskan pesan ke HWND pada level Win32. Jendela WPF tidak memiliki HWND dan karenanya tidak dapat berpartisipasi dalam pesan WndProc. Loop pesan WPF dasar tidak berada di atas WndProc tetapi memisahkannya dari logika inti WPF.

Anda dapat menggunakan HWndHost dan mendapatkan WndProc untuk itu. Namun ini hampir pasti bukan yang ingin Anda lakukan. Untuk sebagian besar tujuan, WPF tidak beroperasi di HWND dan WndProc. Solusi Anda hampir pasti bergantung pada membuat perubahan di WPF, bukan di WndProc.

JaredPar
sumber
13
"Jendela WPF tidak memiliki HWND" - Ini tidak benar.
Scott Solmer