diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index a7188c6226..1b9cb7de34 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -16,6 +16,12 @@ namespace Avalonia.Controls
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider
{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly AttachedProperty BringIntoViewOnFocusChangeProperty =
+ AvaloniaProperty.RegisterAttached(nameof(BringIntoViewOnFocusChange), true);
+
///
/// Defines the property.
///
@@ -174,6 +180,26 @@ namespace Avalonia.Controls
remove => RemoveHandler(ScrollChangedEvent, value);
}
+ ///
+ /// Gets or sets a value that determines whether the uses a
+ /// bring-into-view scroll behavior when an item in the view gets focus.
+ ///
+ ///
+ /// true to use a behavior that brings focused items into view. false to use a behavior
+ /// that focused items do not automatically scroll into view. The default is true.
+ ///
+ ///
+ /// can either be set explicitly on a
+ /// , or a the attached
+ /// ScrollViewer.BringIntoViewOnFocusChange property can be set on an element
+ /// that hosts a .
+ ///
+ public bool BringIntoViewOnFocusChange
+ {
+ get => GetValue(BringIntoViewOnFocusChangeProperty);
+ set => SetValue(BringIntoViewOnFocusChangeProperty, value);
+ }
+
///
/// Gets the extent of the scrollable content.
///
@@ -400,6 +426,26 @@ namespace Avalonia.Controls
///
public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity));
+ ///
+ /// Gets the value of the attached property.
+ ///
+ /// The control to read the value from.
+ /// The value of the property.
+ public static bool GetBringIntoViewOnFocusChange(Control control)
+ {
+ return control.GetValue(BringIntoViewOnFocusChangeProperty);
+ }
+
+ ///
+ /// Gets the value of the attached property.
+ ///
+ /// The control to set the value on.
+ /// The value of the property.
+ public static void SetBringIntoViewOnFocusChange(Control control, bool value)
+ {
+ control.SetValue(BringIntoViewOnFocusChangeProperty, value);
+ }
+
///
/// Gets the value of the HorizontalScrollBarVisibility attached property.
///
@@ -696,6 +742,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)
diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
index e2273cd487..05bbbe9558 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
@@ -36,7 +36,8 @@
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}"
- AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
+ BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
+ BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
+ BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
diff --git a/src/Avalonia.Themes.Simple/Controls/ListBox.xaml b/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
index eaa1f914ca..a51fcbf72e 100644
--- a/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ListBox.xaml
@@ -17,6 +17,7 @@
CornerRadius="{TemplateBinding CornerRadius}">
diff --git a/src/Avalonia.Themes.Simple/Controls/TreeView.xaml b/src/Avalonia.Themes.Simple/Controls/TreeView.xaml
index 8630c14aa2..f2f451fe6f 100644
--- a/src/Avalonia.Themes.Simple/Controls/TreeView.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TreeView.xaml
@@ -15,6 +15,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
(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(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 &&