【问题标题】:How to catch the ending resize window?如何捕捉结束的调整大小窗口?
【发布时间】:2011-05-27 08:33:45
【问题描述】:

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

【问题讨论】:

    标签: c# wpf xaml resize window


    【解决方案1】:

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

    总的技巧是在 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
    }
    

    【讨论】:

    • Form.ResizeBegin/End 在 Winforms 中。通知仍然存在,但在 WPF 中被忽略。前进两步,后退一步。
    • @Martin,请解释一下为什么你把 _resizeTimer.IsEnabled = true;在停止开始之前?这对我来说似乎没有意义。
    • 我喜欢这种机制,因为它允许在用户暂停调整大小时进行一些处理。当用户调整大小时,有需要重新布局画布的情况。通过这种计时器方法,当用户停止移动鼠标(但未释放它)时,可以执行重新布局,并且可以看到新尺寸的影响。我的测试团队喜欢它而不是以前的 - 重新布局仅在鼠标释放时发生,即 WM_EXITSIZEMOVE 方法。我确实将计时器间隔设置为 200 毫秒,而不是此示例代码中使用的 1500 值。
    【解决方案2】:

    .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 事件,这样您的 Size_Changed 方法(您可以在其中执行自定义代码)将在没有另一个 SizeChanged 事件的情况下经过 200 毫秒(或您想等待的时间)才会执行被解雇了。

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

    【讨论】:

      【解决方案3】:

      您可以准确检测 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
      

      herehere 解释了这种技术和类似技术。

      请注意,该函数应返回 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
      【解决方案4】:

      没有计时器需要@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】:

        对于带有 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;
                    });
        

        【讨论】:

          猜你喜欢
          • 2012-09-30
          • 2018-02-04
          • 2013-09-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-24
          • 2011-10-18
          • 2014-01-01
          相关资源
          最近更新 更多