Browse Source

Added BringIntoViewOnFocusChange.

Added `ScrollViewer.BringIntoViewOnFocusChange` attached property, based on the UWP API.
pull/11442/head
Steven Kirk 3 years ago
parent
commit
e7b61ef002
  1. 44
      src/Avalonia.Controls/ScrollViewer.cs
  2. 71
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

44
src/Avalonia.Controls/ScrollViewer.cs

@ -16,6 +16,12 @@ namespace Avalonia.Controls
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{
/// <summary>
/// Defines the <see cref="BringIntoViewOnFocusChange "/> property.
/// </summary>
public static readonly AttachedProperty<bool> BringIntoViewOnFocusChangeProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, bool>(nameof(BringIntoViewOnFocusChange), true);
/// <summary>
/// Defines the <see cref="Extent"/> property.
/// </summary>
@ -174,6 +180,16 @@ namespace Avalonia.Controls
remove => RemoveHandler(ScrollChangedEvent, value);
}
/// <summary>
/// Gets or sets a value that determines whether the <see cref="ScrollViewer"/> uses a
/// bring-into-view scroll behavior when an item in the view gets focus.
/// </summary>
public bool BringIntoViewOnFocusChange
{
get => GetValue(BringIntoViewOnFocusChangeProperty);
set => SetValue(BringIntoViewOnFocusChangeProperty, value);
}
/// <summary>
/// Gets the extent of the scrollable content.
/// </summary>
@ -400,6 +416,26 @@ namespace Avalonia.Controls
/// </summary>
public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity));
/// <summary>
/// Gets the value of the <see cref="BringIntoViewOnFocusChange"/> attached property.
/// </summary>
/// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns>
public static bool GetBringIntoViewOnFocusChange(Control control)
{
return control.GetValue(BringIntoViewOnFocusChangeProperty);
}
/// <summary>
/// Gets the value of the <see cref="BringIntoViewOnFocusChange"/> attached property.
/// </summary>
/// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param>
public static void SetBringIntoViewOnFocusChange(Control control, bool value)
{
control.SetValue(BringIntoViewOnFocusChangeProperty, value);
}
/// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property.
/// </summary>
@ -696,6 +732,14 @@ namespace Avalonia.Controls
}
}
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
if (e.Source != this && e.Source is Control c && BringIntoViewOnFocusChange)
c.BringIntoView();
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.PageUp)

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

@ -358,6 +358,77 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(100, thumb.Bounds.Top);
}
[Fact]
public void BringIntoViewOnFocusChange_Scrolls_Child_Control_Into_View_When_Focused()
{
using var app = UnitTestApplication.Start(TestServices.RealFocus);
var content = new StackPanel
{
Children =
{
new Button
{
Width = 100,
Height = 900,
},
new Button
{
Width = 100,
Height = 900,
},
}
};
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = content,
};
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
var button = (Button)content.Children[1];
button.Focus();
Assert.Equal(new Vector(0, 800), target.Offset);
}
[Fact]
public void BringIntoViewOnFocusChange_False_Does_Not_Scroll_Child_Control_Into_View_When_Focused()
{
var content = new StackPanel
{
Children =
{
new Button
{
Width = 100,
Height = 900,
},
new Button
{
Width = 100,
Height = 900,
},
}
};
var target = new ScrollViewer
{
Template = new FuncControlTemplate<ScrollViewer>(CreateTemplate),
Content = content,
};
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
var button = (Button)content.Children[1];
button.Focus();
Assert.Equal(new Vector(0, 0), target.Offset);
}
private Point GetRootPoint(Visual control, Point p)
{
if (control.GetVisualRoot() is Visual root &&

Loading…
Cancel
Save