浏览代码

Merge branch 'master' into fixes/3796-set-window-size

danwalmsley 5 年之前
父节点
当前提交
a1a3b55542

+ 45 - 0
src/Avalonia.Controls/ScrollChangedEventArgs.cs

@@ -0,0 +1,45 @@
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Describes a change in scrolling state.
+    /// </summary>
+    public class ScrollChangedEventArgs : RoutedEventArgs
+    {
+        public ScrollChangedEventArgs(
+            Vector extentDelta,
+            Vector offsetDelta,
+            Vector viewportDelta)
+            : this(ScrollViewer.ScrollChangedEvent, extentDelta, offsetDelta, viewportDelta)
+        {
+        }
+
+        public ScrollChangedEventArgs(
+            RoutedEvent routedEvent,
+            Vector extentDelta,
+            Vector offsetDelta,
+            Vector viewportDelta)
+            : base(routedEvent)
+        {
+            ExtentDelta = extentDelta;
+            OffsetDelta = offsetDelta;
+            ViewportDelta = viewportDelta;
+        }
+
+        /// <summary>
+        /// Gets the change to the value of <see cref="ScrollViewer.Extent"/>.
+        /// </summary>
+        public Vector ExtentDelta { get; }
+
+        /// <summary>
+        /// Gets the change to the value of <see cref="ScrollViewer.Offset"/>.
+        /// </summary>
+        public Vector OffsetDelta { get; }
+
+        /// <summary>
+        /// Gets the change to the value of <see cref="ScrollViewer.Viewport"/>.
+        /// </summary>
+        public Vector ViewportDelta { get; }
+    }
+}

+ 45 - 4
src/Avalonia.Controls/ScrollViewer.cs

@@ -2,6 +2,7 @@ using System;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Input;
+using Avalonia.Interactivity;
 
 namespace Avalonia.Controls
 {
@@ -165,6 +166,14 @@ namespace Avalonia.Controls
                 nameof(VerticalScrollBarVisibility),
                 ScrollBarVisibility.Auto);
 
+        /// <summary>
+        /// Defines the <see cref="ScrollChanged"/> event.
+        /// </summary>
+        public static readonly RoutedEvent<ScrollChangedEventArgs> ScrollChangedEvent =
+            RoutedEvent.Register<ScrollViewer, ScrollChangedEventArgs>(
+                nameof(ScrollChanged),
+                RoutingStrategies.Bubble);
+
         internal const double DefaultSmallChange = 16;
 
         private IDisposable _childSubscription;
@@ -191,6 +200,15 @@ namespace Avalonia.Controls
         {
         }
 
+        /// <summary>
+        /// Occurs when changes are detected to the scroll position, extent, or viewport size.
+        /// </summary>
+        public event EventHandler<ScrollChangedEventArgs> ScrollChanged
+        {
+            add => AddHandler(ScrollChangedEvent, value);
+            remove => RemoveHandler(ScrollChangedEvent, value);
+        }
+
         /// <summary>
         /// Gets the extent of the scrollable content.
         /// </summary>
@@ -203,9 +221,11 @@ namespace Avalonia.Controls
 
             private set
             {
+                var old = _extent;
+
                 if (SetAndRaise(ExtentProperty, ref _extent, value))
                 {
-                    CalculatedPropertiesChanged();
+                    CalculatedPropertiesChanged(extentDelta: value - old);
                 }
             }
         }
@@ -222,11 +242,13 @@ namespace Avalonia.Controls
 
             set
             {
+                var old = _offset;
+
                 value = ValidateOffset(this, value);
 
                 if (SetAndRaise(OffsetProperty, ref _offset, value))
                 {
-                    CalculatedPropertiesChanged();
+                    CalculatedPropertiesChanged(offsetDelta: value - old);
                 }
             }
         }
@@ -243,9 +265,11 @@ namespace Avalonia.Controls
 
             private set
             {
+                var old = _viewport;
+
                 if (SetAndRaise(ViewportProperty, ref _viewport, value))
                 {
-                    CalculatedPropertiesChanged();
+                    CalculatedPropertiesChanged(viewportDelta: value - old);
                 }
             }
         }
@@ -525,7 +549,10 @@ namespace Avalonia.Controls
             }
         }
 
-        private void CalculatedPropertiesChanged()
+        private void CalculatedPropertiesChanged(
+            Size extentDelta = default,
+            Vector offsetDelta = default,
+            Size viewportDelta = default)
         {
             // Pass old values of 0 here because we don't have the old values at this point,
             // and it shouldn't matter as only the template uses these properies.
@@ -546,6 +573,20 @@ namespace Avalonia.Controls
                 SetAndRaise(SmallChangeProperty, ref _smallChange, new Size(DefaultSmallChange, DefaultSmallChange));
                 SetAndRaise(LargeChangeProperty, ref _largeChange, Viewport);
             }
+
+            if (extentDelta != default || offsetDelta != default || viewportDelta != default)
+            {
+                using var route = BuildEventRoute(ScrollChangedEvent);
+
+                if (route.HasHandlers)
+                {
+                    var e = new ScrollChangedEventArgs(
+                        new Vector(extentDelta.Width, extentDelta.Height),
+                        offsetDelta,
+                        new Vector(viewportDelta.Width, viewportDelta.Height));
+                    route.RaiseEvent(this, e);
+                }
+            }
         }
 
         protected override void OnKeyDown(KeyEventArgs e)

+ 1 - 1
src/Avalonia.Dialogs/ManagedFileChooser.xaml

@@ -58,7 +58,7 @@
             <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
                 <StackPanel.Styles>
                     <Style Selector="Button">
-                        <Setter Property="Margin">4</Setter>
+                        <Setter Property="Margin" Value="4"/>
                     </Style>
                 </StackPanel.Styles>
                 <Button Command="{Binding Ok}">OK</Button>

+ 72 - 1
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@@ -4,7 +4,6 @@ using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Layout;
-using Avalonia.LogicalTree;
 using Moq;
 using Xunit;
 
@@ -147,6 +146,78 @@ namespace Avalonia.Controls.UnitTests
             Assert.Equal(new Size(45, 67), target.LargeChange);
         }
 
+        [Fact]
+        public void Changing_Extent_Should_Raise_ScrollChanged()
+        {
+            var target = new ScrollViewer();
+            var raised = 0;
+
+            target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
+            target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
+            target.Offset = new Vector(10, 10);
+
+            target.ScrollChanged += (s, e) =>
+            {
+                Assert.Equal(new Vector(11, 12), e.ExtentDelta);
+                Assert.Equal(default, e.OffsetDelta);
+                Assert.Equal(default, e.ViewportDelta);
+                ++raised;
+            };
+
+            target.SetValue(ScrollViewer.ExtentProperty, new Size(111, 112));
+
+            Assert.Equal(1, raised);
+
+        }
+
+        [Fact]
+        public void Changing_Offset_Should_Raise_ScrollChanged()
+        {
+            var target = new ScrollViewer();
+            var raised = 0;
+
+            target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
+            target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
+            target.Offset = new Vector(10, 10);
+
+            target.ScrollChanged += (s, e) =>
+            {
+                Assert.Equal(default, e.ExtentDelta);
+                Assert.Equal(new Vector(12, 14), e.OffsetDelta);
+                Assert.Equal(default, e.ViewportDelta);
+                ++raised;
+            };
+
+            target.Offset = new Vector(22, 24);
+
+            Assert.Equal(1, raised);
+
+        }
+
+        [Fact]
+        public void Changing_Viewport_Should_Raise_ScrollChanged()
+        {
+            var target = new ScrollViewer();
+            var raised = 0;
+
+            target.SetValue(ScrollViewer.ExtentProperty, new Size(100, 100));
+            target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50));
+            target.Offset = new Vector(10, 10);
+
+            target.ScrollChanged += (s, e) =>
+            {
+                Assert.Equal(default, e.ExtentDelta);
+                Assert.Equal(default, e.OffsetDelta);
+                Assert.Equal(new Vector(6, 8), e.ViewportDelta);
+                ++raised;
+            };
+
+            target.SetValue(ScrollViewer.ViewportProperty, new Size(56, 58));
+
+            Assert.Equal(1, raised);
+
+        }
+
         private Control CreateTemplate(ScrollViewer control, INameScope scope)
         {
             return new Grid