Browse Source

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.
pull/4080/head
Steven Kirk 6 years ago
parent
commit
c3a5c48d6f
  1. 65
      src/Avalonia.Controls/ScrollViewer.cs
  2. 25
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

65
src/Avalonia.Controls/ScrollViewer.cs

@ -181,6 +181,9 @@ namespace Avalonia.Controls
private Size _extent; private Size _extent;
private Vector _offset; private Vector _offset;
private Size _viewport; private Size _viewport;
private Size _oldExtent;
private Vector _oldOffset;
private Size _oldViewport;
private Size _largeChange; private Size _largeChange;
private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange); private Size _smallChange = new Size(DefaultSmallChange, DefaultSmallChange);
@ -198,6 +201,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public ScrollViewer() public ScrollViewer()
{ {
LayoutUpdated += OnLayoutUpdated;
} }
/// <summary> /// <summary>
@ -221,11 +225,9 @@ namespace Avalonia.Controls
private set private set
{ {
var old = _extent;
if (SetAndRaise(ExtentProperty, ref _extent, value)) if (SetAndRaise(ExtentProperty, ref _extent, value))
{ {
CalculatedPropertiesChanged(extentDelta: value - old); CalculatedPropertiesChanged();
} }
} }
} }
@ -242,13 +244,11 @@ namespace Avalonia.Controls
set set
{ {
var old = _offset;
value = ValidateOffset(this, value); value = ValidateOffset(this, value);
if (SetAndRaise(OffsetProperty, ref _offset, value)) if (SetAndRaise(OffsetProperty, ref _offset, value))
{ {
CalculatedPropertiesChanged(offsetDelta: value - old); CalculatedPropertiesChanged();
} }
} }
} }
@ -265,11 +265,9 @@ namespace Avalonia.Controls
private set private set
{ {
var old = _viewport;
if (SetAndRaise(ViewportProperty, ref _viewport, value)) if (SetAndRaise(ViewportProperty, ref _viewport, value))
{ {
CalculatedPropertiesChanged(viewportDelta: value - old); CalculatedPropertiesChanged();
} }
} }
} }
@ -549,10 +547,7 @@ namespace Avalonia.Controls
} }
} }
private void CalculatedPropertiesChanged( private void CalculatedPropertiesChanged()
Size extentDelta = default,
Vector offsetDelta = default,
Size viewportDelta = default)
{ {
// Pass old values of 0 here because we don't have the old values at this point, // 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. // 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(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange));
SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport); 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) protected override void OnKeyDown(KeyEventArgs e)
@ -602,5 +583,35 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
} }
} }
/// <summary>
/// Called when a change in scrolling state is detected, such as a change in scroll
/// position, extent, or viewport size.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// If you override this method, call `base.OnScrollChanged(ScrollChangedEventArgs)` to
/// ensure that this event is raised.
/// </remarks>
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;
}
} }
} }

25
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -4,6 +4,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.UnitTests;
using Moq; using Moq;
using Xunit; using Xunit;
@ -150,12 +151,15 @@ namespace Avalonia.Controls.UnitTests
public void Changing_Extent_Should_Raise_ScrollChanged() public void Changing_Extent_Should_Raise_ScrollChanged()
{ {
var target = new ScrollViewer(); var target = new ScrollViewer();
var root = new TestRoot(target);
var raised = 0; var raised = 0;
target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10); target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root);
target.ScrollChanged += (s, e) => target.ScrollChanged += (s, e) =>
{ {
Assert.Equal(new Vector(11, 12), e.ExtentDelta); Assert.Equal(new Vector(11, 12), e.ExtentDelta);
@ -166,20 +170,26 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ExtentProperty, new Size(111, 112)); target.SetValue(ScrollViewer.ExtentProperty, new Size(111, 112));
Assert.Equal(1, raised); Assert.Equal(0, raised);
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(1, raised);
} }
[Fact] [Fact]
public void Changing_Offset_Should_Raise_ScrollChanged() public void Changing_Offset_Should_Raise_ScrollChanged()
{ {
var target = new ScrollViewer(); var target = new ScrollViewer();
var root = new TestRoot(target);
var raised = 0; var raised = 0;
target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10); target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root);
target.ScrollChanged += (s, e) => target.ScrollChanged += (s, e) =>
{ {
Assert.Equal(default, e.ExtentDelta); Assert.Equal(default, e.ExtentDelta);
@ -190,20 +200,26 @@ namespace Avalonia.Controls.UnitTests
target.Offset = new Vector(22, 24); target.Offset = new Vector(22, 24);
Assert.Equal(1, raised); Assert.Equal(0, raised);
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(1, raised);
} }
[Fact] [Fact]
public void Changing_Viewport_Should_Raise_ScrollChanged() public void Changing_Viewport_Should_Raise_ScrollChanged()
{ {
var target = new ScrollViewer(); var target = new ScrollViewer();
var root = new TestRoot(target);
var raised = 0; var raised = 0;
target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100)); target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
target.Offset = new Vector(10, 10); target.Offset = new Vector(10, 10);
root.LayoutManager.ExecuteInitialLayoutPass(root);
target.ScrollChanged += (s, e) => target.ScrollChanged += (s, e) =>
{ {
Assert.Equal(default, e.ExtentDelta); Assert.Equal(default, e.ExtentDelta);
@ -214,8 +230,11 @@ namespace Avalonia.Controls.UnitTests
target.SetValue(ScrollViewer.ViewportProperty, new Size(56, 58)); 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) private Control CreateTemplate(ScrollViewer control, INameScope scope)

Loading…
Cancel
Save