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 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
/// </summary>
public ScrollViewer()
{
LayoutUpdated += OnLayoutUpdated;
}
/// <summary>
@ -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;
}
}
/// <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.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)

Loading…
Cancel
Save