diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index 96fcb54650..664264956f 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -19,8 +19,8 @@ namespace ControlCatalog.Pages customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); var cancellableContextBorder = this.Get("CancellableContextBorder"); - cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing; - cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening; + cancellableContextBorder.ContextMenu!.Closing += ContextFlyoutPage_Closing; + cancellableContextBorder.ContextMenu!.Opening += ContextFlyoutPage_Opening; } private ContextPageViewModel? _model; diff --git a/src/Avalonia.Base/Data/TemplateBinding.cs b/src/Avalonia.Base/Data/TemplateBinding.cs index 09f04dfe70..0fd40cd09d 100644 --- a/src/Avalonia.Base/Data/TemplateBinding.cs +++ b/src/Avalonia.Base/Data/TemplateBinding.cs @@ -3,18 +3,21 @@ using System.Globalization; using Avalonia.Data.Converters; using Avalonia.Reactive; using Avalonia.Styling; +using Avalonia.Threading; namespace Avalonia.Data { /// /// A XAML binding to a property on a control's templated parent. /// - public class TemplateBinding : SingleSubscriberObservableBase, + public class TemplateBinding : IObservable, IBinding, IDescription, IAvaloniaSubject, - ISetterValue + ISetterValue, + IDisposable { + private IObserver? _observer; private bool _isSetterValue; private StyledElement? _target; private Type? _targetType; @@ -29,6 +32,28 @@ namespace Avalonia.Data Property = property; } + public IDisposable Subscribe(IObserver observer) + { + _ = observer ?? throw new ArgumentNullException(nameof(observer)); + Dispatcher.UIThread.VerifyAccess(); + + if (_observer != null) + { + throw new InvalidOperationException("The observable can only be subscribed once."); + } + + _observer = observer; + Subscribed(); + + return this; + } + + public virtual void Dispose() + { + Unsubscribed(); + _observer = null; + } + /// public InstancedBinding? Initiate( AvaloniaObject target, @@ -111,7 +136,7 @@ namespace Avalonia.Data /// void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true; - protected override void Subscribed() + private void Subscribed() { TemplatedParentChanged(); @@ -121,7 +146,7 @@ namespace Avalonia.Data } } - protected override void Unsubscribed() + private void Unsubscribed() { if (_target?.TemplatedParent is { } templatedParent) { @@ -147,12 +172,12 @@ namespace Avalonia.Data value = Converter.Convert(value, _targetType ?? typeof(object), ConverterParameter, CultureInfo.CurrentCulture); } - PublishNext(value); + _observer?.OnNext(value); _hasProducedValue = true; } else if (_hasProducedValue) { - PublishNext(AvaloniaProperty.UnsetValue); + _observer?.OnNext(AvaloniaProperty.UnsetValue); _hasProducedValue = false; } } diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs index 2b8786089f..2e0268a644 100644 --- a/src/Avalonia.Base/Input/AccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs @@ -65,14 +65,14 @@ namespace Avalonia.Input { if (_mainMenu != null) { - _mainMenu.MenuClosed -= MainMenuClosed; + _mainMenu.Closed -= MainMenuClosed; } _mainMenu = value; if (_mainMenu != null) { - _mainMenu.MenuClosed += MainMenuClosed; + _mainMenu.Closed += MainMenuClosed; } } } diff --git a/src/Avalonia.Base/Input/IMainMenu.cs b/src/Avalonia.Base/Input/IMainMenu.cs index cc1b88d4c3..02437b6367 100644 --- a/src/Avalonia.Base/Input/IMainMenu.cs +++ b/src/Avalonia.Base/Input/IMainMenu.cs @@ -26,6 +26,6 @@ namespace Avalonia.Input /// /// Occurs when the main menu closes. /// - event EventHandler? MenuClosed; + event EventHandler? Closed; } } diff --git a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs index 53a0b43c63..10d42214c2 100644 --- a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs +++ b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs @@ -3,7 +3,7 @@ using Avalonia.Threading; namespace Avalonia.Reactive { - public abstract class SingleSubscriberObservableBase : IObservable, IDisposable + internal abstract class SingleSubscriberObservableBase : IObservable, IDisposable { private Exception? _error; private IObserver? _observer; diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index a9d8e4c9c7..3dc1514667 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -196,14 +196,14 @@ namespace Avalonia.Controls /// /// property is changing from false to true. /// - public event CancelEventHandler? ContextMenuOpening; + public event CancelEventHandler? Opening; /// /// Occurs when the value of the /// /// property is changing from true to false. /// - public event CancelEventHandler? ContextMenuClosing; + public event CancelEventHandler? Closing; /// /// Called when the property changes on a control. @@ -353,7 +353,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuOpenedEvent, + RoutedEvent = OpenedEvent, Source = this, }); } @@ -394,7 +394,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuClosedEvent, + RoutedEvent = ClosedEvent, Source = this, }); @@ -446,14 +446,14 @@ namespace Avalonia.Controls private bool CancelClosing() { var eventArgs = new CancelEventArgs(); - ContextMenuClosing?.Invoke(this, eventArgs); + Closing?.Invoke(this, eventArgs); return eventArgs.Cancel; } private bool CancelOpening() { var eventArgs = new CancelEventArgs(); - ContextMenuOpening?.Invoke(this, eventArgs); + Opening?.Invoke(this, eventArgs); return eventArgs.Cancel; } } diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index fe4d42c603..6a8cf2b515 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuClosedEvent, + RoutedEvent = ClosedEvent, Source = this, }); } @@ -77,7 +77,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuOpenedEvent, + RoutedEvent = OpenedEvent, Source = this, }); } diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs index 7fc804a338..e57fadffee 100644 --- a/src/Avalonia.Controls/MenuBase.cs +++ b/src/Avalonia.Controls/MenuBase.cs @@ -25,16 +25,16 @@ namespace Avalonia.Controls o => o.IsOpen); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent MenuOpenedEvent = - RoutedEvent.Register(nameof(MenuOpened), RoutingStrategies.Bubble); + public static readonly RoutedEvent OpenedEvent = + RoutedEvent.Register(nameof(Opened), RoutingStrategies.Bubble); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent MenuClosedEvent = - RoutedEvent.Register(nameof(MenuClosed), RoutingStrategies.Bubble); + public static readonly RoutedEvent ClosedEvent = + RoutedEvent.Register(nameof(Closed), RoutingStrategies.Bubble); private bool _isOpen; @@ -68,8 +68,8 @@ namespace Avalonia.Controls /// public bool IsOpen { - get { return _isOpen; } - protected set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } + get => _isOpen; + protected set => SetAndRaise(IsOpenProperty, ref _isOpen, value); } /// @@ -105,19 +105,19 @@ namespace Avalonia.Controls /// /// Occurs when a is opened. /// - public event EventHandler? MenuOpened + public event EventHandler? Opened { - add { AddHandler(MenuOpenedEvent, value); } - remove { RemoveHandler(MenuOpenedEvent, value); } + add => AddHandler(OpenedEvent, value); + remove => RemoveHandler(OpenedEvent, value); } /// /// Occurs when a is closed. /// - public event EventHandler? MenuClosed + public event EventHandler? Closed { - add { AddHandler(MenuClosedEvent, value); } - remove { RemoveHandler(MenuClosedEvent, value); } + add => AddHandler(ClosedEvent, value); + remove => RemoveHandler(ClosedEvent, value); } /// diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 79a3038edf..6a76855d85 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -290,7 +290,7 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed += PointerPressed; Menu.PointerReleased += PointerReleased; Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened); + Menu.AddHandler(MenuBase.OpenedEvent, MenuOpened); Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); @@ -326,7 +326,7 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed -= PointerPressed; Menu.PointerReleased -= PointerReleased; Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened); + Menu.RemoveHandler(MenuBase.OpenedEvent, MenuOpened); Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 7f9735cac0..0c24a0d36e 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests int openedCount = 0; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { openedCount++; }; @@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests int openedCount = 0; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { openedCount++; }; @@ -168,7 +168,7 @@ namespace Avalonia.Controls.UnitTests bool opened = false; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { opened = true; }; @@ -221,7 +221,7 @@ namespace Avalonia.Controls.UnitTests int closedCount = 0; - sut.MenuClosed += (sender, args) => + sut.Closed += (sender, args) => { closedCount++; }; @@ -259,7 +259,7 @@ namespace Avalonia.Controls.UnitTests var tracker = 0; var c = new ContextMenu(); - c.ContextMenuClosing += (s, e) => + c.Closing += (s, e) => { tracker++; e.Cancel = true; @@ -431,7 +431,7 @@ namespace Avalonia.Controls.UnitTests }; new Window { Content = target }; - sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; }; + sut.Opening += (c, e) => { eventCalled = true; e.Cancel = true; }; _mouse.Click(target, MouseButton.Right); @@ -575,7 +575,7 @@ namespace Avalonia.Controls.UnitTests var window = PreparedWindow(target); var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); - sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; }; + sut.Closing += (c, e) => { eventCalled = true; e.Cancel = true; }; window.Show();