如何捕捉结束调整大小窗口?

发布于 2024-12-02 17:21:25 字数 33 浏览 1 评论 0原文

我需要捕获 WPF 中的 endresize 事件。

I need catch the event endresize in WPF.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

冷清清 2024-12-09 17:21:25

WPF 不提供仅在调整大小过程结束时触发的事件。 SizeChanged 是与窗口大小调整相关的唯一事件 - 在调整大小过程中它将多次触发。

一个彻底的技巧是在 SizeChanged 事件触发时不断设置一个计时器。然后计时器将没有机会计时,直到调整大小结束,然后进行一次性处理。

public MyUserControl()
{
    _resizeTimer.Tick += _resizeTimer_Tick;
}

DispatcherTimer _resizeTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 1500), IsEnabled = false };

private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
    _resizeTimer.IsEnabled = true;
    _resizeTimer.Stop();
    _resizeTimer.Start();
}

void _resizeTimer_Tick(object sender, EventArgs e)
{
    _resizeTimer.IsEnabled = false;    

    //Do end of resize processing
}

WPF doesn't provide an event that solely fires at the end of the resize process. SizeChanged is the only event associated with Window resizing - and it will fire multiple times during the resizing process.

A total hack would be to constantly set a timer ticking when the SizeChanged event fires. Then timer will not get a chance to tick until resizing ends and at that point do your one time processing.

public MyUserControl()
{
    _resizeTimer.Tick += _resizeTimer_Tick;
}

DispatcherTimer _resizeTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 1500), IsEnabled = false };

private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
    _resizeTimer.IsEnabled = true;
    _resizeTimer.Stop();
    _resizeTimer.Start();
}

void _resizeTimer_Tick(object sender, EventArgs e)
{
    _resizeTimer.IsEnabled = false;    

    //Do end of resize processing
}
盛夏尉蓝 2024-12-09 17:21:25

.NET 的反应式扩展提供了一些非常酷的功能来处理标准事件模式,包括能够限制事件。我在处理大小更改事件时遇到了类似的问题,虽然解决方案仍然有点“hacky”,但我认为反应式扩展提供了一种更优雅的实现方式。这是我的实现:

IObservable<SizeChangedEventArgs> ObservableSizeChanges = Observable
    .FromEventPattern<SizeChangedEventArgs>(this, "SizeChanged")
    .Select(x => x.EventArgs)
    .Throttle(TimeSpan.FromMilliseconds(200));

IDisposable SizeChangedSubscription = ObservableSizeChanges
    .ObserveOn(SynchronizationContext.Current)
    .Subscribe(x => {
        Size_Changed(x);
    });

这将有效地限制 SizeChanged 事件,这样您的 Size_Changed 方法(您可以在其中执行自定义代码)直到 200 毫秒(或您想要等待的时间)后才会执行在没有触发另一个 SizeChanged 事件的情况下通过。

private void Size_Changed(SizeChangedEventArgs e) {
    // custom code for dealing with end of size changed here
}

Reactive Extensions for .NET provides some really cool capabilities for dealing with standard event patterns including being able to throttle events. I had a similar problem in dealing with size changed events and while the solution is still somewhat "hacky" I think that Reactive Extensions provides a much more elegant way of implementing it. Here is my implementation:

IObservable<SizeChangedEventArgs> ObservableSizeChanges = Observable
    .FromEventPattern<SizeChangedEventArgs>(this, "SizeChanged")
    .Select(x => x.EventArgs)
    .Throttle(TimeSpan.FromMilliseconds(200));

IDisposable SizeChangedSubscription = ObservableSizeChanges
    .ObserveOn(SynchronizationContext.Current)
    .Subscribe(x => {
        Size_Changed(x);
    });

This will effectively throttle the SizeChanged event such that your Size_Changed method (where you can execute custom code) will not be executed until 200 milliseconds (or however long you would like to wait) have passed without another SizeChanged event being fired.

private void Size_Changed(SizeChangedEventArgs e) {
    // custom code for dealing with end of size changed here
}
笑脸一如从前 2024-12-09 17:21:25

不需要计时器,@Bohoo 提供的非常干净的解决方案,我只是将他的代码从 vb.net 改编为 c#

     public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.Loaded += MainWindow_Loaded;
            }

            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                // this two line have to be exactly onload
                HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
                source.AddHook(new HwndSourceHook(WndProc));
            }


            const int WM_SIZING = 0x214;
            const int WM_EXITSIZEMOVE = 0x232;
            private static bool WindowWasResized = false;


            private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                if (msg == WM_SIZING)
                {

                    if (WindowWasResized == false)
                    {

                        //    'indicate the the user is resizing and not moving the window
                        WindowWasResized = true;
                    }
                }

                if (msg == WM_EXITSIZEMOVE)
                {

                    // 'check that this is the end of resize and not move operation          
                    if (WindowWasResized == true)
                    {

                        // your stuff to do 
                        Console.WriteLine("End");

                        // 'set it back to false for the next resize/move
                        WindowWasResized = false;
                    }
                }

                return IntPtr.Zero;
            }

    }

NO Timer Needed with very clean solution that have given by @Bohoo up, i just adapted his code from vb.net to c#

     public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.Loaded += MainWindow_Loaded;
            }

            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                // this two line have to be exactly onload
                HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
                source.AddHook(new HwndSourceHook(WndProc));
            }


            const int WM_SIZING = 0x214;
            const int WM_EXITSIZEMOVE = 0x232;
            private static bool WindowWasResized = false;


            private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                if (msg == WM_SIZING)
                {

                    if (WindowWasResized == false)
                    {

                        //    'indicate the the user is resizing and not moving the window
                        WindowWasResized = true;
                    }
                }

                if (msg == WM_EXITSIZEMOVE)
                {

                    // 'check that this is the end of resize and not move operation          
                    if (WindowWasResized == true)
                    {

                        // your stuff to do 
                        Console.WriteLine("End");

                        // 'set it back to false for the next resize/move
                        WindowWasResized = false;
                    }
                }

                return IntPtr.Zero;
            }

    }
<逆流佳人身旁 2024-12-09 17:21:25

您可以准确检测 WPF 窗口调整大小何时结束,并且不需要计时器。当用户在窗口调整大小或移动操作结束时释放鼠标左键时,本机窗口会收到 WM_EXITSIZEMOVE 消息。 WPF 窗口不会接收此消息,因此我们需要连接一个 WndProc 函数来接收它。我们可以使用 HwndSourceWindowInteropHelper 来获取窗口句柄。然后我们将钩子添加到我们的 WndProc 函数中。我们将在窗口 Loaded 事件(vb.net 代码)中完成所有这些操作:

Dim WinSource As HwndSource    

Private Sub WindowLoaded_(sender As Object, e As RoutedEventArgs)

    WinSource = HwndSource.FromHwnd(New WindowInteropHelper(Me).Handle)
    WinSource.AddHook(New HwndSourceHook(AddressOf WndProc))
End Sub

现在,在我们的 WndProc 中,我们将监听 WM_EXITSIZEMOVE消息:

Const WM_EXITSIZEMOVE As Integer = &H232

Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr

    If msg = WM_EXITSIZEMOVE Then

        DoWhatYouNeed()
    End If

    Return IntPtr.Zero
End Function

对此和类似的技术进行了解释此处此处

请注意,该函数应返回 IntPtr.Zero。另外,除了处理您感兴趣的特定消息之外,不要在此函数中执行任何操作。

现在,WM_EXITSIZEMOVE 也会在移动操作结束时发送,并且我们只对调整大小感兴趣。有多种方法可以确定调整大小操作是否结束。我通过监听 WM_SIZING 消息(在调整大小期间发送了多次)并结合一个标志来做到这一点。整个解决方案如下所示:(

注意:不要与此处突出显示的代码混淆,因为它对于 vb.net 是错误的)

Dim WinSource As HwndSource
Const WM_SIZING As Integer = &H214
Const WM_EXITSIZEMOVE As Integer = &H232

Dim WindowWasResized As Boolean = False

Private Sub WindowLoaded_(sender As Object, e As RoutedEventArgs)

    WinSource = HwndSource.FromHwnd(New WindowInteropHelper(Me).Handle)
    WinSource.AddHook(New HwndSourceHook(AddressOf WndProc))
End Sub

Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr

    If msg = WM_SIZING Then

        If WindowWasResized = False Then

            'indicate the the user is resizing and not moving the window
            WindowWasResized = True
        End If
    End If

    If msg = WM_EXITSIZEMOVE Then

        'check that this is the end of resize and not move operation          
        If WindowWasResized = True Then

             DoWhatYouNeed()

             'set it back to false for the next resize/move
             WindowWasResized = False
        End If            
    End If

    Return IntPtr.Zero
End Function

就是这样。

You can detect exactly when a WPF window resize ended, and you don't need a timer. A native window receive the WM_EXITSIZEMOVE message when the user release the left mouse button at the end of a window resize or move operation. A WPF window doesn't receive this message, so we need to hook up a WndProc function which will receive it. We can use HwndSource with WindowInteropHelper to get our window handle. Then we will add the hook to our WndProc function. We will do all that in the window Loaded event (vb.net code):

Dim WinSource As HwndSource    

Private Sub WindowLoaded_(sender As Object, e As RoutedEventArgs)

    WinSource = HwndSource.FromHwnd(New WindowInteropHelper(Me).Handle)
    WinSource.AddHook(New HwndSourceHook(AddressOf WndProc))
End Sub

Now, in our WndProc, we will listen to the WM_EXITSIZEMOVE message:

Const WM_EXITSIZEMOVE As Integer = &H232

Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr

    If msg = WM_EXITSIZEMOVE Then

        DoWhatYouNeed()
    End If

    Return IntPtr.Zero
End Function

This and a similar technique is explained here and here.

Notice that the function should return IntPtr.Zero. Also, don't do in this func anything except handling the specific messages you are interested in.

Now, WM_EXITSIZEMOVE is also sent at the end of a move operation, and we are interested only in resize. There are several ways to determine that this was the end of resize operation. I did it by listening to the WM_SIZING message (which sent many times during resize), combined with a flag. The whole solution looks like this:

(Note: Don't get confused with the code highlighting here, cause its wrong for vb.net)

Dim WinSource As HwndSource
Const WM_SIZING As Integer = &H214
Const WM_EXITSIZEMOVE As Integer = &H232

Dim WindowWasResized As Boolean = False

Private Sub WindowLoaded_(sender As Object, e As RoutedEventArgs)

    WinSource = HwndSource.FromHwnd(New WindowInteropHelper(Me).Handle)
    WinSource.AddHook(New HwndSourceHook(AddressOf WndProc))
End Sub

Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr

    If msg = WM_SIZING Then

        If WindowWasResized = False Then

            'indicate the the user is resizing and not moving the window
            WindowWasResized = True
        End If
    End If

    If msg = WM_EXITSIZEMOVE Then

        'check that this is the end of resize and not move operation          
        If WindowWasResized = True Then

             DoWhatYouNeed()

             'set it back to false for the next resize/move
             WindowWasResized = False
        End If            
    End If

    Return IntPtr.Zero
End Function

That's it.

2024-12-09 17:21:25

对于带有 Rx 的 UWP (System.Reactive)

            //Stop window updates
            rootFrame = new Frame
            {
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
                Width = Window.Current.Bounds.Width,
                Height = Window.Current.Bounds.Height
            };

            //updates after throttling
            var sizeChangedObservable = Observable.FromEventPattern<WindowSizeChangedEventHandler, WindowSizeChangedEventArgs>(
                      handler => Window.Current.SizeChanged += handler,
                      handler => Window.Current.SizeChanged -= handler);

            sizeChangedObservable.Throttle(TimeSpan.FromSeconds(0.35)).ObserveOnDispatcher(CoreDispatcherPriority.Normal).Subscribe(x =>
            {
                rootFrame.Width = x.EventArgs.Size.Width;
                rootFrame.Height = x.EventArgs.Size.Height;
            });

For UWP with Rx (System.Reactive)

            //Stop window updates
            rootFrame = new Frame
            {
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
                Width = Window.Current.Bounds.Width,
                Height = Window.Current.Bounds.Height
            };

            //updates after throttling
            var sizeChangedObservable = Observable.FromEventPattern<WindowSizeChangedEventHandler, WindowSizeChangedEventArgs>(
                      handler => Window.Current.SizeChanged += handler,
                      handler => Window.Current.SizeChanged -= handler);

            sizeChangedObservable.Throttle(TimeSpan.FromSeconds(0.35)).ObserveOnDispatcher(CoreDispatcherPriority.Normal).Subscribe(x =>
            {
                rootFrame.Width = x.EventArgs.Size.Width;
                rootFrame.Height = x.EventArgs.Size.Height;
            });
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文