Browse Source

Merge branch 'master' into datagridcolumn-sortcomparer

Dan Walmsley 4 years ago
parent
commit
2f0724acc8

+ 1 - 0
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@@ -31,6 +31,7 @@
                                 <CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
                             </MenuItem.Icon>
                         </MenuItem>
+                        <MenuItem Header="Menu Item that won't close on click" StaysOpenOnClick="True" />
                     </ContextMenu>
                 </Border.ContextMenu>
                 <TextBlock Text="Defined in XAML"/>

+ 14 - 0
src/Avalonia.Base/Data/Optional.cs

@@ -153,4 +153,18 @@ namespace Avalonia.Data
         /// </summary>
         public static Optional<T> Empty => default;
     }
+
+    public static class OptionalExtensions
+    {
+        /// <summary>
+        /// Casts the type of an <see cref="Optional{T}"/> using only the C# cast operator.
+        /// </summary>
+        /// <typeparam name="T">The target type.</typeparam>
+        /// <param name="value">The binding value.</param>
+        /// <returns>The cast value.</returns>
+        public static Optional<T> Cast<T>(this Optional<object> value)
+        {
+            return value.HasValue ? new Optional<T>((T)value.Value) : Optional<T>.Empty;
+        }
+    }
 }

+ 2 - 2
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@@ -127,8 +127,8 @@ namespace Avalonia.PropertyStore
             sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
-                oldValue.GetValueOrDefault<T>(),
-                newValue.GetValueOrDefault<T>(),
+                oldValue.Cast<T>(),
+                newValue.Cast<T>(),
                 Priority));
         }
 

+ 2 - 2
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@@ -65,8 +65,8 @@ namespace Avalonia.PropertyStore
             sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
-                oldValue.GetValueOrDefault<T>(),
-                newValue.GetValueOrDefault<T>(),
+                oldValue.Cast<T>(),
+                newValue.Cast<T>(),
                 Priority));
         }
     }

+ 2 - 2
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@@ -36,8 +36,8 @@ namespace Avalonia.PropertyStore
             sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
-                oldValue.GetValueOrDefault<T>(),
-                newValue.GetValueOrDefault<T>(),
+                oldValue.Cast<T>(),
+                newValue.Cast<T>(),
                 BindingPriority.LocalValue));
         }
     }

+ 2 - 2
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@@ -197,8 +197,8 @@ namespace Avalonia.PropertyStore
             sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                 owner,
                 (AvaloniaProperty<T>)property,
-                oldValue.GetValueOrDefault<T>(),
-                newValue.GetValueOrDefault<T>(),
+                oldValue.Cast<T>(),
+                newValue.Cast<T>(),
                 Priority));
         }
 

+ 4 - 1
src/Avalonia.Controls/ApiCompatBaseline.txt

@@ -1,4 +1,7 @@
 Compat issues with assembly Avalonia.Controls:
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Controls.IMenuItem.StaysOpenOnClick.get()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
 MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
@@ -7,4 +10,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
 MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
-Total Issues: 7
+Total Issues: 11

+ 6 - 0
src/Avalonia.Controls/IMenuItem.cs

@@ -23,6 +23,12 @@ namespace Avalonia.Controls
         /// </summary>
         bool IsSubMenuOpen { get; set; }
 
+        /// <summary>
+        /// Gets or sets a value that indicates the submenu that this <see cref="MenuItem"/> is
+        /// within should not close when this item is clicked.
+        /// </summary>
+        bool StaysOpenOnClick { get; set; }
+
         /// <summary>
         /// Gets a value that indicates whether the <see cref="MenuItem"/> is a top-level main menu item.
         /// </summary>

+ 16 - 0
src/Avalonia.Controls/MenuItem.cs

@@ -69,6 +69,12 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<bool> IsSubMenuOpenProperty =
             AvaloniaProperty.Register<MenuItem, bool>(nameof(IsSubMenuOpen));
 
+        /// <summary>
+        /// Defines the <see cref="StaysOpenOnClick"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> StaysOpenOnClickProperty =
+            AvaloniaProperty.Register<MenuItem, bool>(nameof(StaysOpenOnClick));
+
         /// <summary>
         /// Defines the <see cref="Click"/> event.
         /// </summary>
@@ -265,6 +271,16 @@ namespace Avalonia.Controls
             set { SetValue(IsSubMenuOpenProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets a value that indicates the submenu that this <see cref="MenuItem"/> is
+        /// within should not close when this item is clicked.
+        /// </summary>
+        public bool StaysOpenOnClick
+        {
+            get { return GetValue(StaysOpenOnClickProperty); }
+            set { SetValue(StaysOpenOnClickProperty, value); }
+        }
+
         /// <summary>
         /// Gets or sets a value that indicates whether the <see cref="MenuItem"/> has a submenu.
         /// </summary>

+ 5 - 1
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@@ -449,7 +449,11 @@ namespace Avalonia.Controls.Platform
         protected void Click(IMenuItem item)
         {
             item.RaiseClick();
-            CloseMenu(item);
+
+            if (!item.StaysOpenOnClick)
+            {
+                CloseMenu(item);
+            }
         }
 
         protected void CloseMenu(IMenuItem item)

+ 49 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs

@@ -5,6 +5,7 @@ using System.Reactive.Disposables;
 using System.Reactive.Linq;
 using System.Text;
 using Avalonia.Data;
+using Avalonia.Layout;
 using Xunit;
 
 namespace Avalonia.Base.UnitTests
@@ -104,6 +105,25 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("baz", target.Foo);
         }
 
+        [Fact]
+        public void SetValue_Change_Should_Be_Raised_After_Batch_Update_3()
+        {
+            var target = new TestClass();
+            var raised = new List<AvaloniaPropertyChangedEventArgs>();
+
+            target.PropertyChanged += (s, e) => raised.Add(e);
+
+            target.BeginBatchUpdate();
+            target.SetValue(TestClass.BazProperty, Orientation.Horizontal, BindingPriority.LocalValue);
+            target.EndBatchUpdate();
+
+            Assert.Equal(1, raised.Count);
+            Assert.Equal(TestClass.BazProperty, raised[0].Property);
+            Assert.Equal(Orientation.Vertical, raised[0].OldValue);
+            Assert.Equal(Orientation.Horizontal, raised[0].NewValue);
+            Assert.Equal(Orientation.Horizontal, target.Baz);
+        }
+
         [Fact]
         public void SetValue_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update()
         {
@@ -234,6 +254,26 @@ namespace Avalonia.Base.UnitTests
             Assert.Equal("baz", raised[0].NewValue);
         }
 
+        [Fact]
+        public void Binding_Change_Should_Be_Raised_After_Batch_Update_3()
+        {
+            var target = new TestClass();
+            var observable = new TestObservable<Orientation>(Orientation.Horizontal);
+            var raised = new List<AvaloniaPropertyChangedEventArgs>();
+
+            target.PropertyChanged += (s, e) => raised.Add(e);
+
+            target.BeginBatchUpdate();
+            target.Bind(TestClass.BazProperty, observable, BindingPriority.LocalValue);
+            target.EndBatchUpdate();
+
+            Assert.Equal(1, raised.Count);
+            Assert.Equal(TestClass.BazProperty, raised[0].Property);
+            Assert.Equal(Orientation.Vertical, raised[0].OldValue);
+            Assert.Equal(Orientation.Horizontal, raised[0].NewValue);
+            Assert.Equal(Orientation.Horizontal, target.Baz);
+        }
+
         [Fact]
         public void Binding_Completion_Should_Be_Raised_After_Batch_Update()
         {
@@ -579,6 +619,9 @@ namespace Avalonia.Base.UnitTests
             public static readonly StyledProperty<string> BarProperty =
                 AvaloniaProperty.Register<TestClass, string>(nameof(Bar));
 
+            public static readonly StyledProperty<Orientation> BazProperty =
+                AvaloniaProperty.Register<TestClass, Orientation>(nameof(Bar), Orientation.Vertical);
+
             public string Foo
             {
                 get => GetValue(FooProperty);
@@ -590,6 +633,12 @@ namespace Avalonia.Base.UnitTests
                 get => GetValue(BarProperty);
                 set => SetValue(BarProperty, value);
             }
+
+            public Orientation Baz
+            {
+                get => GetValue(BazProperty);
+                set => SetValue(BazProperty, value);
+            }
         }
 
         public class TestObservable<T> : ObservableBase<BindingValue<T>>