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

21

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

5个回答

30

在 WPF 中没有只在调整大小过程结束时触发的事件。 SizeChanged 是与窗口调整大小相关的唯一事件,并且在调整大小过程中会多次触发。

一个完全的 hack(技巧)是在 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
}

7
在Winforms中,Form.ResizeBegin/End表示窗体调整大小的开始和结束。在WPF中,这个通知仍然存在,但被忽略了。可以说这是两步向前,一步向后。 - Hans Passant
2
@Martin,请解释一下为什么您在停止-启动之前放置了_resizeTimer.IsEnabled = true;?对我来说这看起来毫无意义。 - nuclear sweet
1
我喜欢这个机制,因为它允许在用户暂停调整大小时进行一些处理。有一种情况需要在用户调整大小时重新布局画布。采用计时器方法,当用户停止移动鼠标(但未释放)时,可以执行重新布局并查看新尺寸的影响。我的测试团队喜欢这个机制,而不是以前的——仅在鼠标释放时发生重新布局,即WM_EXITSIZEMOVE方法。我将计时器间隔设置为200毫秒,而不是此示例代码中使用的1500值。 - pjm

16

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

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事件,使得直到200毫秒(或者您想要等待的时间)没有其他SizeChanged事件被触发,您的Size_Changed方法(在其中可以执行自定义代码)才会被执行。

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

6

可以使用@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;
            }

    }

5
你可以精确地检测到 WPF 窗口的大小调整结束,而且不需要使用计时器。当用户在窗口大小调整或移动操作结束时释放左键时,本机窗口会接收到 WM_EXITSIZEMOVE 消息。WPF 窗口无法接收此消息,因此我们需要连接一个 WndProc 函数来接收它。我们可以使用 HwndSourceWindowInteropHelper 来获取我们的窗口句柄。然后我们将在窗口的Loaded事件(vb.net代码)中添加钩子到我们的 WndProc 函数中。请注意,您需要保留 HTML 标签。
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

就是这样。


我认为我们应该处理 WM_ENTERSIZEMOVE 而不是 WM_SIZING - yumetodo

0

对于使用 Rx (System.Reactive) 的 UWP

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接