21

I need catch the event endresize in WPF.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Mediator
  • 14,951
  • 35
  • 113
  • 191

5 Answers5

30

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
}
Martin
  • 39,569
  • 20
  • 99
  • 130
  • 7
    Form.ResizeBegin/End in Winforms. The notification is still there, but ignored in WPF. Two steps forward, one step back. – Hans Passant Dec 17 '10 at 21:46
  • 2
    @Martin, explain please why u put _resizeTimer.IsEnabled = true; before stop-start? That looks like no sense for me. – nuclear sweet May 11 '15 at 07:24
  • 1
    I like this mechanism because it allows doing some processing when user pauses a resize. Had situation where needed to re-layout the canvas when user resized. With this timer approach when user stopped moving the mouse (but not released it) the re-layout can be executed and the affect of the new size can be seen. My test team liked it rather than the previous - re-layout only occurred when mouse released ie WM_EXITSIZEMOVE approach. I did set the timer interval to 200ms instead than the 1500 value used in this example code. – pjm Nov 25 '17 at 18:11
16

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
}
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Jesse Carter
  • 20,062
  • 7
  • 64
  • 101
6

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

    }
Bilal
  • 1,254
  • 13
  • 14
5

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.

Community
  • 1
  • 1
Bohoo
  • 1,099
  • 11
  • 25
0

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;
            });
Pavlo Datsiuk
  • 1,024
  • 9
  • 17