วิธีจัดการข้อความ 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 (dis) จะมาเกินลูปข้อความนี้ดังนั้นจึงไม่ใช่เรื่องเลวร้ายที่จะรู้วิธีเชื่อมต่อจาก WPF
flq

7
@Noldorin: โปรดให้ข้อมูลอ้างอิง (บทความ / หนังสือ) ที่ช่วยให้ฉันเข้าใจส่วนที่ "อุดมการณ์การออกแบบและลักษณะของ API แตกต่างกันมากใน WPF จาก WinForms, ... ทำไมจึงไม่มี WndProc เทียบเท่า"
atiyar

2
WM_MOUSEWHEELตัวอย่างเช่นวิธีเดียวที่จะดักจับข้อความเหล่านั้นได้อย่างน่าเชื่อถือคือการเพิ่มWndProcหน้าต่าง WPF สิ่งนี้ใช้ได้ผลสำหรับฉันในขณะที่เจ้าหน้าที่MouseWheelEventHandlerไม่ได้ผลตามที่คาดไว้ ฉันไม่สามารถรับ tachyons WPF ที่ถูกต้องเรียงกันเพื่อให้ได้พฤติกรรมที่เชื่อถือได้MouseWheelEventHandlerดังนั้นจึงจำเป็นต้องเข้าถึงโดยตรงไปยังไฟล์WndProc.
Chris O

4
ความจริงก็คือแอปพลิเคชั่น WPF จำนวนมาก (ส่วนใหญ่?) ทำงานบน Windows เดสก์ท็อปมาตรฐาน สถาปัตยกรรม WPF เลือกที่จะไม่เปิดเผยความสามารถพื้นฐานทั้งหมดของ Win32 นั้นเป็นการพิจารณาในส่วนของ Microsoft แต่ก็ยังคงน่ารำคาญในการจัดการ ฉันกำลังสร้างแอปพลิเคชัน WPF ที่กำหนดเป้าหมายเฉพาะ Windows บนเดสก์ท็อป แต่ทำงานร่วมกับอุปกรณ์ USB ตามที่ @flq กล่าวถึงและวิธีเดียวที่จะรับการแจ้งเตือนอุปกรณ์คือการเข้าถึงลูปข้อความ บางครั้งการทำลายสิ่งที่เป็นนามธรรมเป็นสิ่งที่หลีกเลี่ยงไม่ได้
NathanAldenSr

1
การตรวจสอบคลิปบอร์ดเป็นเหตุผลหนึ่งที่เราอาจต้องใช้ WndProc อีกประการหนึ่งคือการตรวจสอบว่าแอปพลิเคชันไม่ได้ใช้งานโดยการประมวลผลข้อความ
user34660

135

คุณสามารถทำเช่นนี้ผ่านทางSystem.Windows.Interopnamespace 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 โดย Steve Rands


1
ลิงค์เสีย คุณช่วยแก้ไขได้ไหม
Martin Hennings

1
@Martin นั่นเป็นเพราะเว็บไซต์ของ Steve Rand ไม่มีอยู่แล้ว การแก้ไขเพียงอย่างเดียวที่ฉันคิดได้คือการลบออก ฉันคิดว่ามันยังคงเพิ่มมูลค่าหากไซต์กลับมาในอนาคตดังนั้นฉันจึงไม่ได้ลบมันออก แต่ถ้าคุณไม่เห็นด้วยอย่าลังเลที่จะแก้ไข
Robert MacLean

เป็นไปได้ไหมที่จะรับข้อความ WndProc โดยไม่มีหน้าต่าง?
Mo0gles

8
@ Mo0gles - คิดอย่างรอบคอบเกี่ยวกับสิ่งที่คุณถามแล้วคุณจะมีคำตอบ
Ian Kemp

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 โดยใช้ Behaviors: 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>

แม้ว่าลิงก์นี้อาจตอบคำถามได้ แต่ควรรวมส่วนสำคัญของคำตอบไว้ที่นี่และระบุลิงก์เพื่อการอ้างอิง คำตอบแบบลิงก์เท่านั้นอาจไม่ถูกต้องหากหน้าที่เชื่อมโยงเปลี่ยนไป
สูงสุด

@max> ตอนนี้อาจจะสายไปหน่อย
Rook

1
@Rook ฉันคิดว่าบริการตรวจสอบของ StackOverflow นั้นดูแปลก ๆ ฉันมีเหมือน 20 ที่แน่นอน: Here is a link...คำตอบเหมือนข้างบน
สูงสุด

1
@Max ช้าไปหน่อย แต่ฉันอัปเดตคำตอบเพื่อรวมรหัสที่เกี่ยวข้อง
Wes

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วัตถุข้อความ
Andrew Gray

ฉันพูดถึง HwndSource ในคำตอบ? แน่นอนว่าหน้าต่างระดับบนสุดของคุณจะมี HWND แต่ก็ยังบอกได้ว่าการควบคุมส่วนใหญ่ไม่ถูกต้อง
Logan Capaldo

-5

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


-13

คำตอบสั้น ๆ คือคุณทำไม่ได้ WndProc ทำงานโดยส่งข้อความไปยัง HWND ในระดับ Win32 หน้าต่าง WPF ไม่มี HWND และด้วยเหตุนี้จึงไม่สามารถเข้าร่วมในข้อความ WndProc ลูปข้อความ WPF พื้นฐานนั่งอยู่ด้านบนของ WndProc แต่มันแยกส่วนออกจากตรรกะ WPF หลัก

คุณสามารถใช้ HWndHost และรับที่ WndProc ได้ อย่างไรก็ตามนี่ไม่ใช่สิ่งที่คุณต้องการทำ สำหรับวัตถุประสงค์ส่วนใหญ่ WPF ไม่ทำงานบน HWND และ WndProc โซลูชันของคุณเกือบจะแน่นอนว่าต้องอาศัยการเปลี่ยนแปลง WPF ที่ไม่ใช่ใน WndProc


13
"หน้าต่าง WPF ไม่มี HWND" - นี่ไม่เป็นความจริง
Scott Solmer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.