diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 95ee73be4e..5d71a499e3 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.GetIsScrollChainingEnabled(this); + } } internal bool UpdateScroll(Vector delta) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index a62ba306ab..8c5e644851 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -60,6 +60,12 @@ namespace Avalonia.Controls.Presenters o => o.Viewport, (o, v) => o.Viewport = v); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsScrollChainingEnabledProperty = + ScrollViewer.IsScrollChainingEnabledProperty.AddOwner(); + private bool _canHorizontallyScroll; private bool _canVerticallyScroll; private bool _arranging; @@ -138,6 +144,20 @@ namespace Avalonia.Controls.Presenters private set { SetAndRaise(ViewportProperty, ref _viewport, value); } } + /// + /// Gets or sets if scroll chaining is enabled. The default value is true. + /// + /// + /// 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(IsScrollChainingEnabledProperty); + set => SetValue(IsScrollChainingEnabledProperty, value); + } + /// IControl? IScrollAnchorProvider.CurrentAnchor { @@ -405,8 +425,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 = !IsScrollChainingEnabled || offsetChanged; } } @@ -440,8 +463,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 = !IsScrollChainingEnabled || offsetChanged; } } diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index eee6216587..10a014e81d 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 IsScrollChainingEnabledProperty = + AvaloniaProperty.RegisterAttached( + nameof(IsScrollChainingEnabled), + defaultValue: true); + /// /// Defines the event. /// @@ -418,6 +426,20 @@ namespace Avalonia.Controls set => SetValue(AllowAutoHideProperty, value); } + /// + /// Gets or sets if scroll chaining is enabled. The default value is true. + /// + /// + /// 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(IsScrollChainingEnabledProperty); + set => SetValue(IsScrollChainingEnabledProperty, value); + } + /// /// Scrolls the content up one line. /// @@ -548,6 +570,36 @@ namespace Avalonia.Controls return control.GetValue(AllowAutoHideProperty); } + /// + /// Sets the value of the IsScrollChainingEnabled attached property. + /// + /// The control to set the value on. + /// The value of the property. + /// + /// 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(IsScrollChainingEnabledProperty, value); + } + + /// + /// Gets the value of the IsScrollChainingEnabled attached property. + /// + /// The control to read the value from. + /// The value of the property. + /// + /// 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(IsScrollChainingEnabledProperty); + } + /// /// 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..b1fcb830b3 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}" + IsScrollChainingEnabled="{TemplateBinding IsScrollChainingEnabled}"> + + + @@ -28,6 +29,7 @@ + Viewport="{TemplateBinding Viewport, Mode=TwoWay}" + IsScrollChainingEnabled="{TemplateBinding IsScrollChainingEnabled}"> + @@ -68,6 +69,7 @@ DockPanel.Dock="Top" /> +