2010-12-17 33 views

回答

24

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 
} 
+6

Form.ResizeBegin/End在Winforms中。通知仍然存在,但在WPF中被忽略。前進兩步,退一步。 – 2010-12-17 21:46:05

+1

@Martin,請解釋一下爲什麼你把_resizeTimer。IsEnabled = true;在開始之前?這對我來說看起來沒有意義。 – 2015-05-11 07:24:36

+0

我喜歡這種機制,因爲它允許在用戶暫停調整大小時進行一些處理。有用戶調整大小時需要重新佈局畫布的情況。當用戶停止移動鼠標(但不釋放它)時,通過這種計時器方法,可以執行重新佈局,並且可以看到新尺寸的影響。 我的測試團隊喜歡它,而不是以前 - 重新佈局只發生在鼠標發佈時,即WM_EXITSIZEMOVE方法。 我確實將計時器時間間隔設置爲200ms,而不是此示例代碼中使用的1500值。 – pjm 2017-11-25 18:11:34

11

.NET的反應式擴展爲處理標準事件模式提供了一些非常酷的功能,包括能夠節制事件。我在處理大小改變的事件時遇到了類似的問題,雖然解決方案仍然有些「黑客」,但我認爲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方法(在這裏您可以執行自定義代碼)不會,直到200毫秒內執行(或但是長期要等待)已沒有通過另一個SizeChanged事件被解僱。

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

真高雅! – MuiBienCarlota 2014-01-28 12:17:43

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 

就是這樣。

+1

非常感謝您的提示!有計時器的解決方案非常糟糕.. – 2016-07-13 13:38:09

+0

我認爲我們應該處理'WM_ENTERSIZEMOVE'而不是'WM_SIZING'。 – yumetodo 2017-09-28 11:47:12