Làm thế nào để xử lý các thông báo WndProc trong WPF?


112

Trong Windows Forms, tôi chỉ cần ghi đè WndProcvà bắt đầu xử lý các thông báo khi chúng đến.

Ai đó có thể chỉ cho tôi một ví dụ về cách đạt được điều tương tự trong WPF không?

Câu trả lời:


62

Trên thực tế, theo như tôi hiểu, một điều như vậy thực sự có thể xảy ra trong WPF bằng cách sử dụng HwndSourceHwndSourceHook. Hãy xem chuỗi này trên MSDN làm ví dụ. (Mã liên quan bao gồm bên dưới)

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

Bây giờ, tôi không chắc tại sao bạn lại muốn xử lý tin nhắn Windows Messaging trong ứng dụng WPF (trừ khi đó là dạng tương tác rõ ràng nhất để làm việc với một ứng dụng WinForms khác). Tư tưởng thiết kế và bản chất của API rất khác trong WPF với WinForms, vì vậy tôi khuyên bạn chỉ cần tự làm quen với WPF nhiều hơn để biết chính xác tại sao không có WndProc tương đương.


48
Chà, các sự kiện kết nối Thiết bị USB (dis) dường như đến qua vòng lặp thông báo này, vì vậy không phải là một điều tồi tệ nếu bạn biết cách kết nối từ WPF
flq 14/03

7
@Noldorin: Bạn có thể vui lòng cung cấp tài liệu tham khảo (bài viết / sách) có thể giúp mình hiểu được phần "Tư tưởng thiết kế và bản chất của API rất khác trong WPF với WinForms, ... tại sao không có tương đương với WndProc"?
atiyar

2
WM_MOUSEWHEELví dụ, cách duy nhất để bẫy các thông báo đó một cách đáng tin cậy là thêm WndProcvào cửa sổ WPF. Điều này có hiệu quả với tôi, trong khi viên chức MouseWheelEventHandlerchỉ đơn giản là không hoạt động như mong đợi. Tôi không thể có được các tachyon WPF được sắp xếp vừa phải để có được hành vi đáng tin cậy MouseWheelEventHandler, do đó cần phải truy cập trực tiếp vào WndProc.
Chris O

4
Thực tế là, nhiều (hầu hết?) Ứng dụng WPF được chạy trên Windows máy tính để bàn tiêu chuẩn. Việc kiến ​​trúc WPF chọn không để lộ tất cả các khả năng cơ bản của Win32 là do Microsoft cố ý, nhưng vẫn gây khó chịu khi đối phó. Tôi đang xây dựng một ứng dụng WPF chỉ nhắm mục tiêu đến Windows trên máy tính để bàn nhưng tích hợp với các thiết bị USB như @flq đã đề cập và cách duy nhất để nhận thông báo thiết bị là truy cập vòng lặp thông báo. Đôi khi việc phá vỡ sự trừu tượng là điều khó tránh khỏi.
NathanAldenSr

1
Giám sát khay nhớ tạm là một lý do chúng ta có thể cần một WndProc. Cách khác là phát hiện ứng dụng không hoạt động bằng cách xử lý tin nhắn.
user34660

135

Bạn có thể làm điều này thông qua System.Windows.Interopkhông gian tên có chứa một lớp được đặt tên HwndSource.

Ví dụ về việc sử dụng cái này

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

Hoàn toàn được lấy từ bài đăng trên blog xuất sắc: Sử dụng WndProc tùy chỉnh trong ứng dụng WPF của Steve Rands


1
Liên kết bị hỏng. Bạn có thể vui lòng sửa chữa nó?
Martin Hennings

1
@Martin, đó là vì trang web của Steve Rand không còn tồn tại. Cách khắc phục duy nhất tôi có thể nghĩ đến là loại bỏ nó. Tôi nghĩ rằng nó vẫn tăng thêm giá trị nếu trang web quay trở lại trong tương lai, vì vậy tôi sẽ không xóa nó - nhưng nếu bạn không đồng ý, hãy chỉnh sửa.
Robert MacLean

Có thể nhận tin nhắn WndProc mà không có cửa sổ không?
Mo0gles

8
@ Mo0gles - hãy suy nghĩ kỹ về những gì bạn đã hỏi, và bạn sẽ có câu trả lời cho mình.
Ian Kemp

1
@ Mo0gles Nếu không có cửa sổ được vẽ trên màn hình và hiển thị cho người dùng? Đúng. Đó là lý do tại sao một số chương trình có Windows trống kỳ lạ mà đôi khi có thể nhìn thấy nếu trạng thái của chương trình bị hỏng.
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;
}

3

Nếu bạn không phiền khi tham khảo WinForms, bạn có thể sử dụng giải pháp hướng MVVM hơn mà không kết hợp dịch vụ với chế độ xem. Bạn cần tạo và khởi tạo System.Windows.Forms.NativeWindow, một cửa sổ nhẹ có thể nhận tin nhắn.

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

Sử dụng SpongeHandle để đăng ký các thư bạn quan tâm và sau đó ghi đè WndProc để xử lý chúng:

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

Nhược điểm duy nhất là bạn phải bao gồm tham chiếu System.Windows.Forms, nhưng nếu không thì đây là một giải pháp rất đóng gói.

Có thể đọc thêm về điều này ở đây


1

Đây là liên kết về việc ghi đè WindProc bằng Behaviors: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

[Chỉnh sửa: muộn còn hơn không] Dưới đây là cách thực hiện của tôi dựa trên liên kết trên. Mặc dù xem lại điều này, tôi thích triển khai AddHook hơn. Tôi có thể chuyển sang điều đó.

Trong trường hợp của tôi, tôi muốn biết khi nào cửa sổ được thay đổi kích thước và một số thứ khác. Việc triển khai này kết nối với Window xaml và gửi các sự kiện.

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>

Mặc dù liên kết này có thể trả lời câu hỏi, nhưng tốt hơn hết bạn nên đưa các phần thiết yếu của câu trả lời vào đây và cung cấp liên kết để tham khảo. Các câu trả lời chỉ có liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi.
Tối đa

@max> bây giờ có lẽ hơi muộn.
Rook

1
@Rem Tôi nghĩ dịch vụ đánh giá của StackOverflow hoạt động kỳ lạ, tôi chỉ có 20 Here is a link...câu trả lời chính xác: như trên.
Tối đa

1
@Max Hơi muộn nhưng tôi đã cập nhật câu trả lời của mình để bao gồm mã liên quan.
Wes

0

Bạn có thể đính kèm vào lớp 'SystemEvents' của lớp Win32 cài sẵn:

using Microsoft.Win32;

trong một lớp cửa sổ 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

Có nhiều cách để xử lý thông báo bằng WndProc trong WPF (ví dụ: sử dụng HwndSource, v.v.), nhưng nói chung các kỹ thuật đó được dành riêng cho việc tương tác với các thông báo không thể xử lý trực tiếp thông qua WPF. Hầu hết các điều khiển WPF thậm chí không phải là cửa sổ theo nghĩa Win32 (và theo phần mở rộng là Windows.Forms), vì vậy chúng sẽ không có WndProcs.


-1 / Không chính xác. Mặc dù đúng là các biểu mẫu WPF không phải là WinForms và do đó không có WndProckhả năng ghi đè, nhưng nó System.Windows.Interopcho phép bạn lấy một HwndSourceđối tượng bằng cách HwndSource.FromHwndhoặc PresentationSource.FromVisual(someForm) as HwndSource, mà bạn có thể liên kết một đại biểu có khuôn mẫu đặc biệt. Đại biểu này có nhiều đối số giống như WndProcđối tượng Message.
Andrew Grey

Tôi đề cập đến HwndSource trong câu trả lời? Chắc chắn cửa sổ cấp cao nhất của bạn sẽ có HWND, nhưng vẫn chính xác khi nói rằng hầu hết các điều khiển đều không.
Logan Capaldo


-13

Câu trả lời ngắn gọn là bạn không thể. WndProc hoạt động bằng cách chuyển thông điệp tới HWND ở cấp độ Win32. Cửa sổ WPF không có HWND và do đó không thể tham gia vào các thông báo WndProc. Vòng lặp thông báo WPF cơ sở không nằm trên WndProc nhưng nó tóm tắt chúng khỏi logic WPF cốt lõi.

Bạn có thể sử dụng HWndHost và nhận được tại WndProc cho nó. Tuy nhiên điều này gần như chắc chắn không phải là những gì bạn muốn làm. Đối với đa số các mục đích, WPF không hoạt động trên HWND và WndProc. Giải pháp của bạn gần như chắc chắn dựa vào việc thực hiện thay đổi trong WPF không phải trong WndProc.


13
"Cửa sổ WPF không có HWND" - Điều này đơn giản là không đúng sự thật.
Scott Solmer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.