From c3a5c48d6fcc11903339c95597b9137c7a40b69f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 5 Jun 2020 14:52:50 +0200 Subject: [PATCH] Raise ScrollChanged when layout updated. To be consistent with WPF/UWP `ScrollChanged` should be raised when layout finishes updating, rather than when the individual properties are changed. --- src/Avalonia.Controls/ScrollViewer.cs | 65 +++++++++++-------- .../ScrollViewerTests.cs | 25 ++++++- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index c3f0dc0056..058c11c9ee 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -181,6 +181,9 @@ namespace Avalonia.Controls private Size _extent; private Vector _offset; private Size _viewport; + private Size _oldExtent; + private Vector _oldOffset; + private Size _oldViewport; private Size _largeChange; private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange); @@ -198,6 +201,7 @@ namespace Avalonia.Controls /// public ScrollViewer() { + LayoutUpdated += OnLayoutUpdated; } /// @@ -221,11 +225,9 @@ namespace Avalonia.Controls private set { - var old = _extent; - if (SetAndRaise(ExtentProperty, ref _extent, value)) { - CalculatedPropertiesChanged(extentDelta: value - old); + CalculatedPropertiesChanged(); } } } @@ -242,13 +244,11 @@ namespace Avalonia.Controls set { - var old = _offset; - value = ValidateOffset(this, value); if (SetAndRaise(OffsetProperty, ref _offset, value)) { - CalculatedPropertiesChanged(offsetDelta: value - old); + CalculatedPropertiesChanged(); } } } @@ -265,11 +265,9 @@ namespace Avalonia.Controls private set { - var old = _viewport; - if (SetAndRaise(ViewportProperty, ref _viewport, value)) { - CalculatedPropertiesChanged(viewportDelta: value - old); + CalculatedPropertiesChanged(); } } } @@ -549,10 +547,7 @@ namespace Avalonia.Controls } } - private void CalculatedPropertiesChanged( - Size extentDelta = default, - Vector offsetDelta = default, - Size viewportDelta = default) + private void CalculatedPropertiesChanged() { // Pass old values of 0 here because we don't have the old values at this point, // and it shouldn't matter as only the template uses these properies. @@ -573,20 +568,6 @@ namespace Avalonia.Controls SetAndRaise(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange)); SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport); } - - if (extentDelta != default || offsetDelta != default || viewportDelta != default) - { - using var route = BuildEventRoute(ScrollChangedEvent); - - if (route.HasHandlers) - { - var e = new ScrollChangedEventArgs( - new Vector(extentDelta.Width, extentDelta.Height), - offsetDelta, - new Vector(viewportDelta.Width, viewportDelta.Height)); - route.RaiseEvent(this, e); - } - } } protected override void OnKeyDown(KeyEventArgs e) @@ -602,5 +583,35 @@ namespace Avalonia.Controls e.Handled = true; } } + + /// + /// Called when a change in scrolling state is detected, such as a change in scroll + /// position, extent, or viewport size. + /// + /// The event args. + /// + /// If you override this method, call `base.OnScrollChanged(ScrollChangedEventArgs)` to + /// ensure that this event is raised. + /// + protected virtual void OnScrollChanged(ScrollChangedEventArgs e) + { + RaiseEvent(e); + } + + private void OnLayoutUpdated(object sender, EventArgs e) => RaiseScrollChanged(); + + private void RaiseScrollChanged() + { + var e = new ScrollChangedEventArgs( + new Vector(Extent.Width - _oldExtent.Width, Extent.Height - _oldExtent.Height), + Offset - _oldOffset, + new Vector(Viewport.Width - _oldViewport.Width, Viewport.Height - _oldViewport.Height)); + + OnScrollChanged(e); + + _oldExtent = Extent; + _oldOffset = Offset; + _oldViewport = Viewport; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index 8da1e26f0d..deca3cfb75 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Layout; +using Avalonia.UnitTests; using Moq; using Xunit; @@ -150,12 +151,15 @@ namespace Avalonia.Controls.UnitTests public void Changing_Extent_Should_Raise_ScrollChanged() { var target = new ScrollViewer(); + var root = new TestRoot(target); var raised = 0; target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); + root.LayoutManager.ExecuteInitialLayoutPass(root); + target.ScrollChanged += (s, e) => { Assert.Equal(new Vector(11, 12), e.ExtentDelta); @@ -166,20 +170,26 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ExtentProperty, new Size(111, 112)); - Assert.Equal(1, raised); + Assert.Equal(0, raised); + + root.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(1, raised); } [Fact] public void Changing_Offset_Should_Raise_ScrollChanged() { var target = new ScrollViewer(); + var root = new TestRoot(target); var raised = 0; target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); + root.LayoutManager.ExecuteInitialLayoutPass(root); + target.ScrollChanged += (s, e) => { Assert.Equal(default, e.ExtentDelta); @@ -190,20 +200,26 @@ namespace Avalonia.Controls.UnitTests target.Offset = new Vector(22, 24); - Assert.Equal(1, raised); + Assert.Equal(0, raised); + root.LayoutManager.ExecuteLayoutPass(); + + Assert.Equal(1, raised); } [Fact] public void Changing_Viewport_Should_Raise_ScrollChanged() { var target = new ScrollViewer(); + var root = new TestRoot(target); var raised = 0; target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); + root.LayoutManager.ExecuteInitialLayoutPass(root); + target.ScrollChanged += (s, e) => { Assert.Equal(default, e.ExtentDelta); @@ -214,8 +230,11 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(56, 58)); - Assert.Equal(1, raised); + Assert.Equal(0, raised); + + root.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(1, raised); } private Control CreateTemplate(ScrollViewer control, INameScope scope)