Browse Source

Merge pull request #11442 from AvaloniaUI/feature/bringintoviewonfocuschange

Bring controls into view when focused
pull/11456/head
Max Katz 3 years ago
committed by GitHub
parent
commit
cdefb0c9d6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      src/Avalonia.Controls/ScrollViewer.cs
  2. 3
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  3. 3
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  4. 3
      src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
  5. 1
      src/Avalonia.Themes.Simple/Controls/ListBox.xaml
  6. 1
      src/Avalonia.Themes.Simple/Controls/TextBox.xaml
  7. 1
      src/Avalonia.Themes.Simple/Controls/TreeView.xaml
  8. 71
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

54
src/Avalonia.Controls/ScrollViewer.cs

@ -16,6 +16,12 @@ namespace Avalonia.Controls
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))] [TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider 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> /// <summary>
/// Defines the <see cref="Extent"/> property. /// Defines the <see cref="Extent"/> property.
/// </summary> /// </summary>
@ -174,6 +180,26 @@ namespace Avalonia.Controls
remove => RemoveHandler(ScrollChangedEvent, value); 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>
/// <value>
/// 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.
/// </value>
/// <remarks>
/// <see cref="BringIntoViewOnFocusChange"/> can either be set explicitly on a
/// <see cref="ScrollViewer"/>, or a the attached
/// <code>ScrollViewer.BringIntoViewOnFocusChange</code> property can be set on an element
/// that hosts a <see cref="ScrollViewer"/>.
/// </remarks>
public bool BringIntoViewOnFocusChange
{
get => GetValue(BringIntoViewOnFocusChangeProperty);
set => SetValue(BringIntoViewOnFocusChangeProperty, value);
}
/// <summary> /// <summary>
/// Gets the extent of the scrollable content. /// Gets the extent of the scrollable content.
/// </summary> /// </summary>
@ -400,6 +426,26 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public void ScrollToEnd() => SetCurrentValue(OffsetProperty, new Vector(double.NegativeInfinity, double.PositiveInfinity)); 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> /// <summary>
/// Gets the value of the HorizontalScrollBarVisibility attached property. /// Gets the value of the HorizontalScrollBarVisibility attached property.
/// </summary> /// </summary>
@ -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) protected override void OnKeyDown(KeyEventArgs e)
{ {
if (e.Key == Key.PageUp) if (e.Key == Key.PageUp)

3
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -36,7 +36,8 @@
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}" IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}" AreVerticalSnapPointsRegular="{TemplateBinding AreVerticalSnapPointsRegular}"
AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}" AreHorizontalSnapPointsRegular="{TemplateBinding AreHorizontalSnapPointsRegular}"

3
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -136,7 +136,8 @@
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<Panel> <Panel>
<TextBlock Name="PART_Watermark" <TextBlock Name="PART_Watermark"
Opacity="0.5" Opacity="0.5"

3
src/Avalonia.Themes.Fluent/Controls/TreeView.xaml

@ -30,7 +30,8 @@
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}" VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"> AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
Margin="{TemplateBinding Padding}" /> Margin="{TemplateBinding Padding}" />

1
src/Avalonia.Themes.Simple/Controls/ListBox.xaml

@ -17,6 +17,7 @@
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer Name="PART_ScrollViewer" <ScrollViewer Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"

1
src/Avalonia.Themes.Simple/Controls/TextBox.xaml

@ -126,6 +126,7 @@
<ScrollViewer Grid.Column="1" <ScrollViewer Grid.Column="1"
Grid.ColumnSpan="1" Grid.ColumnSpan="1"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">

1
src/Avalonia.Themes.Simple/Controls/TreeView.xaml

@ -15,6 +15,7 @@
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"> CornerRadius="{TemplateBinding CornerRadius}">
<ScrollViewer AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}" <ScrollViewer AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}" IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"

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

@ -358,6 +358,77 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(100, thumb.Bounds.Top); 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) private Point GetRootPoint(Visual control, Point p)
{ {
if (control.GetVisualRoot() is Visual root && if (control.GetVisualRoot() is Visual root &&

Loading…
Cancel
Save