From 26ca4c15bab482d234ce1b099f34ac62ac90d32a Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 28 Dec 2021 17:56:36 +0100 Subject: [PATCH 1/4] Implement `BubbleUpScrollOnEndReachedProperty` - Add property as AttachedProperty, so it can be opt-in or opt-out - Only bubble up the scroll event if the end is reached - Make use of this Property in: o ListBox o TextBox o TreeView --- .../Presenters/ScrollContentPresenter.cs | 30 +++++++++++++-- src/Avalonia.Controls/ScrollViewer.cs | 37 +++++++++++++++++++ .../Controls/ListBox.xaml | 2 + .../Controls/ScrollViewer.xaml | 3 +- .../Controls/TextBox.xaml | 2 + .../Controls/TreeView.xaml | 2 + .../Controls/ListBox.xaml | 2 + .../Controls/ScrollViewer.xaml | 3 +- .../Controls/TextBox.xaml | 2 + .../Controls/TreeView.xaml | 2 + 10 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index a62ba306ab..9fc5f08986 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -60,6 +60,13 @@ namespace Avalonia.Controls.Presenters o => o.Viewport, (o, v) => o.Viewport = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty BubbleUpScrollOnEndReachedProperty = + AvaloniaProperty.Register( + nameof(BubbleUpScrollOnEndReached)); + private bool _canHorizontallyScroll; private bool _canVerticallyScroll; private bool _arranging; @@ -138,6 +145,15 @@ namespace Avalonia.Controls.Presenters private set { SetAndRaise(ViewportProperty, ref _viewport, value); } } + /// + /// Gets a value that indicates whether the scroll event should be bubbled up to the parent scroll viewer when the end is reached. + /// + public bool BubbleUpScrollOnEndReached + { + get => GetValue(BubbleUpScrollOnEndReachedProperty); + set => SetValue(BubbleUpScrollOnEndReachedProperty, value); + } + /// IControl? IScrollAnchorProvider.CurrentAnchor { @@ -405,8 +421,11 @@ namespace Avalonia.Controls.Presenters _activeLogicalGestureScrolls[e.Id] = delta; } - Offset = new Vector(x, y); - e.Handled = true; + Vector newOffset = new Vector(x, y); + bool offsetChanged = newOffset != Offset; + Offset = newOffset; + + e.Handled = !BubbleUpScrollOnEndReached || offsetChanged; } } @@ -440,8 +459,11 @@ namespace Avalonia.Controls.Presenters x = Math.Min(x, Extent.Width - Viewport.Width); } - Offset = new Vector(x, y); - e.Handled = true; + Vector newOffset = new Vector(x, y); + bool offsetChanged = newOffset != Offset; + Offset = newOffset; + + e.Handled = !BubbleUpScrollOnEndReached || offsetChanged; } } diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index eee6216587..afa4301ae4 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -181,6 +181,14 @@ namespace Avalonia.Controls nameof(AllowAutoHide), true); + /// + /// Defines the property. + /// + public static readonly AttachedProperty BubbleUpScrollOnEndReachedProperty = + AvaloniaProperty.RegisterAttached( + nameof(BubbleUpScrollOnEndReached), + false); + /// /// Defines the event. /// @@ -418,6 +426,15 @@ namespace Avalonia.Controls set => SetValue(AllowAutoHideProperty, value); } + /// + /// Gets a value that indicates whether the scroll event should be bubbled up to the parent scroll viewer when the end is reached. + /// + public bool BubbleUpScrollOnEndReached + { + get => GetValue(BubbleUpScrollOnEndReachedProperty); + set => SetValue(BubbleUpScrollOnEndReachedProperty, value); + } + /// /// Scrolls the content up one line. /// @@ -548,6 +565,26 @@ namespace Avalonia.Controls return control.GetValue(AllowAutoHideProperty); } + /// + /// Gets the value of the BubbleUpScrollOnEndReachedProperty attached property. + /// + /// The control to set the value on. + /// The value of the property. + public static void SetBubbleUpScrollOnEndReached(Control control, bool value) + { + control.SetValue(BubbleUpScrollOnEndReachedProperty, value); + } + + /// + /// Gets the value of the BubbleUpScrollOnEndReachedProperty attached property. + /// + /// The control to read the value from. + /// The value of the property. + public static bool GetBubbleUpScrollOnEndReached(Control control) + { + return control.GetValue(BubbleUpScrollOnEndReachedProperty); + } + /// /// Gets the value of the VerticalScrollBarVisibility attached property. /// diff --git a/src/Avalonia.Themes.Default/Controls/ListBox.xaml b/src/Avalonia.Themes.Default/Controls/ListBox.xaml index e3417aa086..1d653a34b5 100644 --- a/src/Avalonia.Themes.Default/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/ListBox.xaml @@ -6,6 +6,7 @@ + + Viewport="{TemplateBinding Viewport, Mode=TwoWay}" + BubbleUpScrollOnEndReached="{TemplateBinding BubbleUpScrollOnEndReached}"> + + + @@ -28,6 +29,7 @@ + Viewport="{TemplateBinding Viewport, Mode=TwoWay}" + BubbleUpScrollOnEndReached="{TemplateBinding BubbleUpScrollOnEndReached}"> + @@ -68,6 +69,7 @@ DockPanel.Dock="Top" /> + Date: Mon, 3 Jan 2022 19:23:07 +0100 Subject: [PATCH 2/4] use `ScrollViewer.GetBubbleUpScrollOnEndReached` in `DataGrid` DataGrid handles the scrolling by it's own and not via ScrollViewer, so we need handle it there as well. --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 95ee73be4e..d61685932d 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2215,7 +2215,14 @@ namespace Avalonia.Controls /// PointerWheelEventArgs protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { - e.Handled = e.Handled || UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta); + if(UpdateScroll(e.Delta * DATAGRID_mouseWheelDelta)) + { + e.Handled = true; + } + else + { + e.Handled = e.Handled || !ScrollViewer.GetBubbleUpScrollOnEndReached(this); + } } internal bool UpdateScroll(Vector delta) From 2941c3f63563d772bc3a25b7c2864239952f5ee3 Mon Sep 17 00:00:00 2001 From: Tim U Date: Thu, 27 Jan 2022 15:59:30 +0100 Subject: [PATCH 3/4] Rename BubbleUpScrollOnEndReached to IsScrollChainingEnabled --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 2 +- .../Presenters/ScrollContentPresenter.cs | 24 +++++++---- src/Avalonia.Controls/ScrollViewer.cs | 43 +++++++++++++------ .../Controls/ListBox.xaml | 4 +- .../Controls/ScrollViewer.xaml | 2 +- .../Controls/TextBox.xaml | 4 +- .../Controls/TreeView.xaml | 4 +- .../Controls/ListBox.xaml | 4 +- .../Controls/ScrollViewer.xaml | 2 +- .../Controls/TextBox.xaml | 4 +- .../Controls/TreeView.xaml | 4 +- 11 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index d61685932d..5d71a499e3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2221,7 +2221,7 @@ namespace Avalonia.Controls } else { - e.Handled = e.Handled || !ScrollViewer.GetBubbleUpScrollOnEndReached(this); + e.Handled = e.Handled || !ScrollViewer.GetIsScrollChainingEnabled(this); } } diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 9fc5f08986..6ba6d1e6d6 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -61,11 +61,12 @@ namespace Avalonia.Controls.Presenters (o, v) => o.Viewport = v); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty BubbleUpScrollOnEndReachedProperty = + public static readonly StyledProperty IsScrollChainingEnabledProperty = AvaloniaProperty.Register( - nameof(BubbleUpScrollOnEndReached)); + nameof(IsScrollChainingEnabled), + defaultValue: true); private bool _canHorizontallyScroll; private bool _canVerticallyScroll; @@ -146,12 +147,17 @@ namespace Avalonia.Controls.Presenters } /// - /// Gets a value that indicates whether the scroll event should be bubbled up to the parent scroll viewer when the end is reached. + /// Gets or sets if scroll chaining is enabled. The default value is true. /// - public bool BubbleUpScrollOnEndReached + /// + /// After a user hits a scroll limit on an element that has been nested within another scrollable element, + /// you can specify whether that parent element should continue the scrolling operation begun in its child element. + /// This is called scroll chaining. + /// + public bool IsScrollChainingEnabled { - get => GetValue(BubbleUpScrollOnEndReachedProperty); - set => SetValue(BubbleUpScrollOnEndReachedProperty, value); + get => GetValue(IsScrollChainingEnabledProperty); + set => SetValue(IsScrollChainingEnabledProperty, value); } /// @@ -425,7 +431,7 @@ namespace Avalonia.Controls.Presenters bool offsetChanged = newOffset != Offset; Offset = newOffset; - e.Handled = !BubbleUpScrollOnEndReached || offsetChanged; + e.Handled = !IsScrollChainingEnabled || offsetChanged; } } @@ -463,7 +469,7 @@ namespace Avalonia.Controls.Presenters bool offsetChanged = newOffset != Offset; Offset = newOffset; - e.Handled = !BubbleUpScrollOnEndReached || offsetChanged; + e.Handled = !IsScrollChainingEnabled || offsetChanged; } } diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index afa4301ae4..10a014e81d 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -182,12 +182,12 @@ namespace Avalonia.Controls true); /// - /// Defines the property. + /// Defines the property. /// - public static readonly AttachedProperty BubbleUpScrollOnEndReachedProperty = + public static readonly AttachedProperty IsScrollChainingEnabledProperty = AvaloniaProperty.RegisterAttached( - nameof(BubbleUpScrollOnEndReached), - false); + nameof(IsScrollChainingEnabled), + defaultValue: true); /// /// Defines the event. @@ -427,12 +427,17 @@ namespace Avalonia.Controls } /// - /// Gets a value that indicates whether the scroll event should be bubbled up to the parent scroll viewer when the end is reached. + /// Gets or sets if scroll chaining is enabled. The default value is true. /// - public bool BubbleUpScrollOnEndReached + /// + /// After a user hits a scroll limit on an element that has been nested within another scrollable element, + /// you can specify whether that parent element should continue the scrolling operation begun in its child element. + /// This is called scroll chaining. + /// + public bool IsScrollChainingEnabled { - get => GetValue(BubbleUpScrollOnEndReachedProperty); - set => SetValue(BubbleUpScrollOnEndReachedProperty, value); + get => GetValue(IsScrollChainingEnabledProperty); + set => SetValue(IsScrollChainingEnabledProperty, value); } /// @@ -566,23 +571,33 @@ namespace Avalonia.Controls } /// - /// Gets the value of the BubbleUpScrollOnEndReachedProperty attached property. + /// Sets the value of the IsScrollChainingEnabled attached property. /// /// The control to set the value on. /// The value of the property. - public static void SetBubbleUpScrollOnEndReached(Control control, bool value) + /// + /// After a user hits a scroll limit on an element that has been nested within another scrollable element, + /// you can specify whether that parent element should continue the scrolling operation begun in its child element. + /// This is called scroll chaining. + /// + public static void SetIsScrollChainingEnabled(Control control, bool value) { - control.SetValue(BubbleUpScrollOnEndReachedProperty, value); + control.SetValue(IsScrollChainingEnabledProperty, value); } /// - /// Gets the value of the BubbleUpScrollOnEndReachedProperty attached property. + /// Gets the value of the IsScrollChainingEnabled attached property. /// /// The control to read the value from. /// The value of the property. - public static bool GetBubbleUpScrollOnEndReached(Control control) + /// + /// After a user hits a scroll limit on an element that has been nested within another scrollable element, + /// you can specify whether that parent element should continue the scrolling operation begun in its child element. + /// This is called scroll chaining. + /// + public static bool GetIsScrollChainingEnabled(Control control) { - return control.GetValue(BubbleUpScrollOnEndReachedProperty); + return control.GetValue(IsScrollChainingEnabledProperty); } /// diff --git a/src/Avalonia.Themes.Default/Controls/ListBox.xaml b/src/Avalonia.Themes.Default/Controls/ListBox.xaml index 1d653a34b5..b1fcb830b3 100644 --- a/src/Avalonia.Themes.Default/Controls/ListBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/ListBox.xaml @@ -6,7 +6,7 @@ - + + IsScrollChainingEnabled="{TemplateBinding IsScrollChainingEnabled}"> - + - + - + @@ -29,7 +29,7 @@ + IsScrollChainingEnabled="{TemplateBinding IsScrollChainingEnabled}"> - + @@ -69,7 +69,7 @@ DockPanel.Dock="Top" /> - + Date: Fri, 28 Jan 2022 15:58:45 +0100 Subject: [PATCH 4/4] Use AddOwner for ScrollContentPresenter --- src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 6ba6d1e6d6..8c5e644851 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -64,9 +64,7 @@ namespace Avalonia.Controls.Presenters /// Defines the property. /// public static readonly StyledProperty IsScrollChainingEnabledProperty = - AvaloniaProperty.Register( - nameof(IsScrollChainingEnabled), - defaultValue: true); + ScrollViewer.IsScrollChainingEnabledProperty.AddOwner(); private bool _canHorizontallyScroll; private bool _canVerticallyScroll;