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 &&