From e2f7d683b5ad73285db2459c38f40f9714018d12 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 25 Nov 2024 00:00:02 +0000 Subject: [PATCH] Create a breadcrumb Navigation integration --- Version.json | 2 +- src/CrissCross.WPF.UI.Gallery/MainWindow.xaml | 3 + .../MainWindow.xaml.cs | 16 +- .../ViewModels/AllControlsViewModel.cs | 24 +-- .../Controls/AutoSuggestBox/AutoSuggestBox.cs | 6 +- .../Controls/BreadcrumbBar/BreadcrumbBar.cs | 95 +++++++++- .../Controls/BreadcrumbBar/BreadcrumbBar.xaml | 2 +- .../BreadcrumbBar/BreadcrumbBarItem.cs | 49 +++++ .../Controls/ContentDialog/ContentDialog.cs | 14 +- .../FluentNavigationWindow.cs | 2 +- .../Controls/InfoBar/InfoBar.cs | 10 +- .../Controls/MessageBox/MessageBox.cs | 15 +- .../Controls/Snackbar/Snackbar.cs | 12 +- .../Controls/TextBox/TextBox.cs | 12 +- .../Controls/ThumbRate/ThumbRate.cs | 12 +- .../Controls/TitleBar/TitleBar.cs | 12 +- src/CrissCross.WPF.UI/Input/IRelayCommand.cs | 19 -- .../Input/IRelayCommand{T}.cs | 29 --- .../Input/RelayCommand{T}.cs | 179 ------------------ 19 files changed, 222 insertions(+), 291 deletions(-) delete mode 100644 src/CrissCross.WPF.UI/Input/IRelayCommand.cs delete mode 100644 src/CrissCross.WPF.UI/Input/IRelayCommand{T}.cs delete mode 100644 src/CrissCross.WPF.UI/Input/RelayCommand{T}.cs diff --git a/Version.json b/Version.json index 787c13a..3d19ca0 100644 --- a/Version.json +++ b/Version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.2", + "version": "2.2.1", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/heads/main$" diff --git a/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml b/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml index 5b1fa80..80dccda 100644 --- a/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml +++ b/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml @@ -13,6 +13,9 @@ d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}" x:TypeArguments="local:MainWindowViewModel" mc:Ignorable="d"> + + + diff --git a/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml.cs b/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml.cs index 548c777..8722b5c 100644 --- a/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml.cs +++ b/src/CrissCross.WPF.UI.Gallery/MainWindow.xaml.cs @@ -5,6 +5,7 @@ using System.Reactive.Disposables; using System.Windows; using CrissCross.WPF.UI.Appearance; +using CrissCross.WPF.UI.Controls; using CrissCross.WPF.UI.Gallery.ViewModels; using ReactiveUI; using Splat; @@ -33,6 +34,7 @@ public MainWindow() // Watch for system theme changes SystemThemeWatcher.Watch(this); InitializeComponent(); + Navigation = NavBreadcrumb; // Set the data context DataContext = ViewModel = new(); @@ -47,11 +49,23 @@ public MainWindow() this.OneWayBind(ViewModel, vm => vm.ApplicationTitle, v => v.Title).DisposeWith(d); this.OneWayBind(ViewModel, vm => vm.NavigationModels, v => v.NavigationLeft.ItemsSource).DisposeWith(d); + NavBreadcrumb.SetupNavigation("mainWindow"); + // Navigate to the main view - this.NavigateToView(); + NavBreadcrumb.NavigateTo(breadcrumbItemContent: "Main"); + + ////this.NavigateToView(); }); // Dispose the view model on close Closing += (s, e) => ViewModel.Dispose(); } + + /// + /// Gets the nav breadcrumb. + /// + /// + /// The nav breadcrumb. + /// + public static BreadcrumbBar? Navigation { get; private set; } } diff --git a/src/CrissCross.WPF.UI.Gallery/ViewModels/AllControlsViewModel.cs b/src/CrissCross.WPF.UI.Gallery/ViewModels/AllControlsViewModel.cs index 58faefd..fb5180a 100644 --- a/src/CrissCross.WPF.UI.Gallery/ViewModels/AllControlsViewModel.cs +++ b/src/CrissCross.WPF.UI.Gallery/ViewModels/AllControlsViewModel.cs @@ -15,72 +15,72 @@ public partial class AllControlsViewModel : RxObject [ReactiveCommand] private void Buttons() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "Buttons"); } [ReactiveCommand] private void CheckBox() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "CheckBox"); } [ReactiveCommand] private void ComboBox() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "ComboBox"); } [ReactiveCommand] private void DatePicker() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "DatePicker"); } [ReactiveCommand] private void Image() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "Image"); } [ReactiveCommand] private void NumericPushButton() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "NumericPushButton"); } [ReactiveCommand] private void PasswordBox() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "PasswordBox"); } [ReactiveCommand] private void RadioButton() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "RadioButton"); } [ReactiveCommand] private void Slider() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "Slider"); } [ReactiveCommand] private void TextBlock() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "TextBlock"); } [ReactiveCommand] private void TextBox() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "TextBox"); } [ReactiveCommand] private void ToggleButton() { - this.NavigateToView("mainWindow"); + MainWindow.Navigation?.NavigateTo(breadcrumbItemContent: "ToggleButton"); } } diff --git a/src/CrissCross.WPF.UI/Controls/AutoSuggestBox/AutoSuggestBox.cs b/src/CrissCross.WPF.UI/Controls/AutoSuggestBox/AutoSuggestBox.cs index 885a038..72e5334 100644 --- a/src/CrissCross.WPF.UI/Controls/AutoSuggestBox/AutoSuggestBox.cs +++ b/src/CrissCross.WPF.UI/Controls/AutoSuggestBox/AutoSuggestBox.cs @@ -7,7 +7,7 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; -using CrissCross.WPF.UI.Input; +using ReactiveUI; namespace CrissCross.WPF.UI.Controls; @@ -30,7 +30,7 @@ namespace CrissCross.WPF.UI.Controls; [TemplatePart(Name = ElementTextBox, Type = typeof(TextBox))] [TemplatePart(Name = ElementSuggestionsPopup, Type = typeof(Popup))] [TemplatePart(Name = ElementSuggestionsList, Type = typeof(ListView))] -public class AutoSuggestBox : ItemsControl, IIconControl +public partial class AutoSuggestBox : ItemsControl, IIconControl { /// /// Property for . @@ -190,7 +190,7 @@ public AutoSuggestBox() self.ReleaseTemplateResources(); }; - SetValue(FocusCommandProperty, new RelayCommand(_ => Focus())); + SetValue(FocusCommandProperty, ReactiveCommand.Create(Focus)); } /// diff --git a/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.cs b/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.cs index 3194a23..e38a9e6 100644 --- a/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.cs +++ b/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.cs @@ -5,7 +5,8 @@ using System.Collections.Specialized; using System.Windows.Controls.Primitives; using System.Windows.Input; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; @@ -18,7 +19,7 @@ namespace CrissCross.WPF.UI.Controls; /// /// [StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(BreadcrumbBarItem))] -public class BreadcrumbBar : System.Windows.Controls.ItemsControl +public partial class BreadcrumbBar : System.Windows.Controls.ItemsControl, IUseHostedNavigation { /// /// Property for . @@ -34,7 +35,7 @@ public class BreadcrumbBar : System.Windows.Controls.ItemsControl /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(BreadcrumbBar), new PropertyMetadata(null)); @@ -47,13 +48,14 @@ public class BreadcrumbBar : System.Windows.Controls.ItemsControl typeof(TypedEventHandler), typeof(BreadcrumbBar)); + private string? _hostName; + /// /// Initializes a new instance of the class. /// public BreadcrumbBar() { - SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); - + SetValue(TemplateButtonCommandProperty, OnTemplateButtonClickCommand); Loaded += OnLoaded; Unloaded += OnUnloaded; } @@ -68,9 +70,9 @@ public event TypedEventHandler } /// - /// Gets the triggered after clicking. + /// Gets the triggered after clicking. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// /// Gets or sets custom command executed after selecting the item. @@ -84,6 +86,67 @@ public ICommand Command set => SetValue(CommandProperty, value); } + /// + /// Setups the navigation. + /// + /// Name of the host. + public void SetupNavigation(string hostName) + { + _hostName = hostName; + ItemClicked += (s, e) => + { + if (e.Item is BreadcrumbBarItem item) + { + NavigateTo(item.NavigationType); + } + }; + } + + /// + /// Navigates to the specified view as registered to the viewmodel and updates the Breadcrumb. + /// + /// Type of ViewModel. + /// The viewmodel contract. + /// The navigation parameter. + /// Content of the breadcrumb item. + public void NavigateTo(string? contract = null, object? parameter = null, string? breadcrumbItemContent = null) + where T : class, IRxObject + { + if (string.IsNullOrEmpty(_hostName)) + { + throw new InvalidOperationException("Host name is not set. Call SetupNavigation and pass the Host Name of the Navigation host."); + } + + UpdateItems(typeof(T), breadcrumbItemContent); + + this.NavigateToView(_hostName, contract, parameter); + } + + /// + /// Navigates to the specified view as registered to the viewmodel and updates the Breadcrumb. + /// + /// Type of ViewModel. + /// The viewmodel contract. + /// The navigation parameter. + /// Content of the breadcrumb item. + /// type. + public void NavigateTo(Type type, string? contract = null, object? parameter = null, string? breadcrumbItemContent = null) + { + if (string.IsNullOrEmpty(_hostName)) + { + throw new InvalidOperationException("Host name is not set. Call SetupNavigation and pass the Host Name of the Navigation host."); + } + + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + UpdateItems(type, breadcrumbItemContent); + + this.NavigateToView(type, _hostName, contract, parameter); + } + /// /// Called when [item clicked]. /// @@ -122,6 +185,23 @@ protected virtual void OnItemClicked(object item, int index) /// protected override DependencyObject GetContainerForItemOverride() => new BreadcrumbBarItem(); + private void UpdateItems(Type typeName, string? content = null) + { + var list = Items.OfType().ToList().Where(x => x.NavigationType == typeName).ToList(); + if (list.Count != 0) + { + var index = Items.IndexOf(list[0]); + for (var i = Items.Count - 1; i > index; i--) + { + Items.RemoveAt(i); + } + } + else + { + Items.Add(new BreadcrumbBarItem { NavigationType = typeName, Content = content ?? typeName.Name }); + } + } + private void OnLoaded(object sender, RoutedEventArgs e) { ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorOnItemsChanged; @@ -166,6 +246,7 @@ private void ItemContainerGeneratorOnItemsChanged(object sender, ItemsChangedEve UpdateLastContainer(); } + [ReactiveCommand] private void OnTemplateButtonClick(object? obj) { if (obj is null) diff --git a/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.xaml b/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.xaml index 117eaca..387b257 100644 --- a/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.xaml +++ b/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.xaml @@ -64,7 +64,7 @@ VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Command="{Binding Path=TemplateButtonCommand, Mode=OneTime, RelativeSource={RelativeSource AncestorType={x:Type controls:BreadcrumbBar}}}" - CommandParameter="{TemplateBinding Content}" + CommandParameter="{TemplateBinding Self}" Content="{TemplateBinding Content}" ContentTemplate="{Binding Path=ItemTemplate, Mode=OneTime, RelativeSource={RelativeSource AncestorType={x:Type controls:BreadcrumbBar}}}" ContentTemplateSelector="{Binding Path=ItemTemplateSelector, Mode=OneTime, RelativeSource={RelativeSource AncestorType={x:Type controls:BreadcrumbBar}}}" diff --git a/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBarItem.cs b/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBarItem.cs index 16ac4e9..0a7d55c 100644 --- a/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBarItem.cs +++ b/src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBarItem.cs @@ -38,6 +38,55 @@ public class BreadcrumbBarItem : System.Windows.Controls.ContentControl typeof(BreadcrumbBarItem), new PropertyMetadata(false)); + /// + /// The self property. + /// + public static readonly DependencyProperty SelfProperty = + DependencyProperty.Register( + nameof(Self), + typeof(BreadcrumbBarItem), + typeof(BreadcrumbBarItem), + new PropertyMetadata(null)); + + /// + /// The navigation type property. + /// + public static readonly DependencyProperty NavigationTypeProperty = + DependencyProperty.Register( + nameof(NavigationType), + typeof(Type), + typeof(BreadcrumbBarItem), + new PropertyMetadata(null)); + + /// + /// Initializes a new instance of the class. + /// + public BreadcrumbBarItem() => Self = this; + + /// + /// Gets or sets the type of the navigation. + /// + /// + /// The type of the navigation. + /// + public Type NavigationType + { + get => (Type)GetValue(NavigationTypeProperty); + set => SetValue(NavigationTypeProperty, value); + } + + /// + /// Gets the self. + /// + /// + /// The self. + /// + public BreadcrumbBarItem Self + { + get => (BreadcrumbBarItem)GetValue(SelfProperty); + private set => SetValue(SelfProperty, value); + } + /// /// Gets or sets displayed . /// diff --git a/src/CrissCross.WPF.UI/Controls/ContentDialog/ContentDialog.cs b/src/CrissCross.WPF.UI/Controls/ContentDialog/ContentDialog.cs index 5a99ab7..4501c44 100644 --- a/src/CrissCross.WPF.UI/Controls/ContentDialog/ContentDialog.cs +++ b/src/CrissCross.WPF.UI/Controls/ContentDialog/ContentDialog.cs @@ -3,7 +3,8 @@ // See the LICENSE file in the project root for full license information. using System.Windows.Controls; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; @@ -39,7 +40,7 @@ namespace CrissCross.WPF.UI.Controls; /// ); /// /// -public class ContentDialog : ContentControl +public partial class ContentDialog : ContentControl { /// /// Property for . @@ -225,7 +226,7 @@ public class ContentDialog : ContentControl /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(ContentDialog), new PropertyMetadata(null)); @@ -277,7 +278,7 @@ public class ContentDialog : ContentControl /// public ContentDialog() { - SetValue(TemplateButtonCommandProperty, new RelayCommand(OnButtonClick)); + SetValue(TemplateButtonCommandProperty, OnButtonClickCommand); Loaded += static (sender, _) => { @@ -294,7 +295,7 @@ public ContentDialog(ContentPresenter contentPresenter) { ContentPresenter = contentPresenter; - SetValue(TemplateButtonCommandProperty, new RelayCommand(OnButtonClick)); + SetValue(TemplateButtonCommandProperty, OnButtonClickCommand); Loaded += static (sender, _) => { @@ -522,7 +523,7 @@ public bool IsFooterVisible /// /// Gets command triggered after clicking the button in the template. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// /// Gets or sets inside of which the dialogue will be placed. The new will replace the current . @@ -605,6 +606,7 @@ protected virtual void OnClosed(ContentDialogResult result) /// Occurs after the is clicked. /// /// The button. + [ReactiveCommand] protected virtual void OnButtonClick(ContentDialogButton button) { var buttonClickEventArgs = new ContentDialogButtonClickEventArgs( diff --git a/src/CrissCross.WPF.UI/Controls/FluentNavigationWindow/FluentNavigationWindow.cs b/src/CrissCross.WPF.UI/Controls/FluentNavigationWindow/FluentNavigationWindow.cs index a13e856..a10046c 100644 --- a/src/CrissCross.WPF.UI/Controls/FluentNavigationWindow/FluentNavigationWindow.cs +++ b/src/CrissCross.WPF.UI/Controls/FluentNavigationWindow/FluentNavigationWindow.cs @@ -14,7 +14,7 @@ namespace CrissCross.WPF.UI.Controls; /// /// /// -public class FluentNavigationWindow : FluentWindow, ISetNavigation, IUseNavigation, IActivatableView +public class FluentNavigationWindow : FluentWindow, ISetNavigation, IUseNavigation, IActivatableView, IAmBuilt { /// /// The navigate back is enabled property. diff --git a/src/CrissCross.WPF.UI/Controls/InfoBar/InfoBar.cs b/src/CrissCross.WPF.UI/Controls/InfoBar/InfoBar.cs index 860d422..a5234a5 100644 --- a/src/CrissCross.WPF.UI/Controls/InfoBar/InfoBar.cs +++ b/src/CrissCross.WPF.UI/Controls/InfoBar/InfoBar.cs @@ -2,7 +2,7 @@ // ReactiveUI Association Incorporated licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. -using CrissCross.WPF.UI.Input; +using ReactiveUI; namespace CrissCross.WPF.UI.Controls; @@ -65,14 +65,14 @@ public class InfoBar : System.Windows.Controls.ContentControl /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(InfoBar), new PropertyMetadata(null)); /// public InfoBar() => SetValue( TemplateButtonCommandProperty, - new RelayCommand(_ => SetCurrentValue(IsOpenProperty, false))); + ReactiveCommand.Create(_ => SetCurrentValue(IsOpenProperty, false))); /// /// Gets or sets a value indicating whether the user can close the @@ -124,8 +124,8 @@ public InfoBarSeverity Severity } /// - /// Gets the triggered after clicking + /// Gets the triggered after clicking /// the close button. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); } diff --git a/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs b/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs index 9a29ff4..10475be 100644 --- a/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs +++ b/src/CrissCross.WPF.UI/Controls/MessageBox/MessageBox.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for full license information. using System.Drawing; -using System.Runtime.CompilerServices; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; @@ -13,7 +13,7 @@ namespace CrissCross.WPF.UI.Controls; /// [ToolboxItem(true)] [ToolboxBitmap(typeof(MessageBox), "MessageBox.bmp")] -public class MessageBox : System.Windows.Window +public partial class MessageBox : System.Windows.Window { /// Identifies the dependency property. public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register( @@ -102,7 +102,7 @@ public class MessageBox : System.Windows.Window /// Identifies the dependency property. public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(MessageBox), new PropertyMetadata(null)); @@ -116,7 +116,7 @@ public class MessageBox : System.Windows.Window public MessageBox() { Topmost = true; - SetValue(TemplateButtonCommandProperty, new RelayCommand(OnButtonClick)); + SetValue(TemplateButtonCommandProperty, OnButtonClickCommand); PreviewMouseDoubleClick += static (_, args) => args.Handled = true; @@ -238,7 +238,7 @@ public bool IsPrimaryButtonEnabled /// /// Gets the command triggered after clicking the button on the Footer. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// /// Gets or sets the TCS. @@ -401,6 +401,7 @@ protected virtual void CenterWindowOnScreen() /// Occurs after the is clicked. /// /// The MessageBox button. + [ReactiveCommand] protected virtual void OnButtonClick(MessageBoxButton button) { var result = button switch @@ -415,7 +416,7 @@ protected virtual void OnButtonClick(MessageBoxButton button) } #if NET8_0_OR_GREATER - [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_CanCenterOverWPFOwner")] + [System.Runtime.CompilerServices.UnsafeAccessor(System.Runtime.CompilerServices.UnsafeAccessorKind.Method, Name = "get_CanCenterOverWPFOwner")] private static extern bool CanCenterOverWPFOwnerAccessor(System.Windows.Window w); #endif diff --git a/src/CrissCross.WPF.UI/Controls/Snackbar/Snackbar.cs b/src/CrissCross.WPF.UI/Controls/Snackbar/Snackbar.cs index 9ea24f4..4eb47aa 100644 --- a/src/CrissCross.WPF.UI/Controls/Snackbar/Snackbar.cs +++ b/src/CrissCross.WPF.UI/Controls/Snackbar/Snackbar.cs @@ -4,14 +4,15 @@ using System.Windows.Controls; using CrissCross.WPF.UI.Converters; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; /// /// Snackbar inform user of a process that an app has performed or will perform. It appears temporarily, towards the bottom of the window. /// -public class Snackbar : ContentControl, IAppearanceControl, IIconControl +public partial class Snackbar : ContentControl, IAppearanceControl, IIconControl { /// /// Property for . @@ -90,7 +91,7 @@ public class Snackbar : ContentControl, IAppearanceControl, IIconControl /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(Snackbar), new PropertyMetadata(null)); @@ -137,7 +138,7 @@ public Snackbar(SnackbarPresenter presenter) { Presenter = presenter; - SetValue(TemplateButtonCommandProperty, new RelayCommand(_ => Hide())); + SetValue(TemplateButtonCommandProperty, HideCommand); } /// @@ -258,7 +259,7 @@ public Brush ContentForeground /// /// Gets command triggered after clicking the button in the template. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// /// Shows the . @@ -309,6 +310,7 @@ public virtual async Task ShowAsync(bool immediately) /// /// Hides the . /// + [ReactiveCommand] protected virtual void Hide() => _ = Presenter.HideCurrent(); /// diff --git a/src/CrissCross.WPF.UI/Controls/TextBox/TextBox.cs b/src/CrissCross.WPF.UI/Controls/TextBox/TextBox.cs index baa3837..011fe6b 100644 --- a/src/CrissCross.WPF.UI/Controls/TextBox/TextBox.cs +++ b/src/CrissCross.WPF.UI/Controls/TextBox/TextBox.cs @@ -4,14 +4,15 @@ using System.Diagnostics; using System.Windows.Controls; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; /// /// Extended with additional parameters like . /// -public class TextBox : System.Windows.Controls.TextBox +public partial class TextBox : System.Windows.Controls.TextBox { /// Identifies the dependency property. public static readonly DependencyProperty IconProperty = DependencyProperty.Register( @@ -72,7 +73,7 @@ public class TextBox : System.Windows.Controls.TextBox /// Identifies the dependency property. public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(TextBox), new PropertyMetadata(null)); @@ -81,7 +82,7 @@ public class TextBox : System.Windows.Controls.TextBox /// public TextBox() { - SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); + SetValue(TemplateButtonCommandProperty, OnTemplateButtonClickCommand); CurrentPlaceholderEnabled = PlaceholderEnabled; } @@ -160,7 +161,7 @@ public bool IsTextSelectionEnabled /// /// Gets the command triggered when clicking the button. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// protected override void OnTextChanged(TextChangedEventArgs e) @@ -250,6 +251,7 @@ protected virtual void OnClearButtonClick() /// Triggered by clicking a button in the control template. /// /// The parameter. + [ReactiveCommand] protected virtual void OnTemplateButtonClick(string? parameter) { Debug.WriteLine($"INFO: {typeof(TextBox)} button clicked", "Wpf.Ui.TextBox"); diff --git a/src/CrissCross.WPF.UI/Controls/ThumbRate/ThumbRate.cs b/src/CrissCross.WPF.UI/Controls/ThumbRate/ThumbRate.cs index 3613ab0..522deab 100644 --- a/src/CrissCross.WPF.UI/Controls/ThumbRate/ThumbRate.cs +++ b/src/CrissCross.WPF.UI/Controls/ThumbRate/ThumbRate.cs @@ -3,7 +3,8 @@ // See the LICENSE file in the project root for full license information. using System.Drawing; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; @@ -12,7 +13,7 @@ namespace CrissCross.WPF.UI.Controls; /// [ToolboxItem(true)] [ToolboxBitmap(typeof(ThumbRate), "ThumbRate.bmp")] -public class ThumbRate : System.Windows.Controls.Control +public partial class ThumbRate : System.Windows.Controls.Control { /// /// Property for . @@ -37,14 +38,14 @@ public class ThumbRate : System.Windows.Controls.Control /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(ThumbRate), new PropertyMetadata(null)); /// /// Initializes a new instance of the class. /// - public ThumbRate() => SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); + public ThumbRate() => SetValue(TemplateButtonCommandProperty, OnTemplateButtonClickCommand); /// /// Occurs when is changed. @@ -67,12 +68,13 @@ public ThumbRateState State /// /// Gets command triggered after clicking the button. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// /// Triggered by clicking a button in the control template. /// /// The parameter. + [ReactiveCommand] protected virtual void OnTemplateButtonClick(ThumbRateState parameter) { if (State == parameter) diff --git a/src/CrissCross.WPF.UI/Controls/TitleBar/TitleBar.cs b/src/CrissCross.WPF.UI/Controls/TitleBar/TitleBar.cs index 5887b2a..edd2373 100644 --- a/src/CrissCross.WPF.UI/Controls/TitleBar/TitleBar.cs +++ b/src/CrissCross.WPF.UI/Controls/TitleBar/TitleBar.cs @@ -8,7 +8,8 @@ using System.Windows.Input; using CrissCross.WPF.UI.Designer; using CrissCross.WPF.UI.Extensions; -using CrissCross.WPF.UI.Input; +using ReactiveUI; +using ReactiveUI.SourceGenerators; namespace CrissCross.WPF.UI.Controls; @@ -21,7 +22,7 @@ namespace CrissCross.WPF.UI.Controls; [TemplatePart(Name = ElementMaximizeButton, Type = typeof(TitleBarButton))] [TemplatePart(Name = ElementRestoreButton, Type = typeof(TitleBarButton))] [TemplatePart(Name = ElementCloseButton, Type = typeof(TitleBarButton))] -public class TitleBar : Control, IThemeControl +public partial class TitleBar : Control, IThemeControl { /// /// Property for . @@ -210,7 +211,7 @@ public class TitleBar : Control, IThemeControl /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), - typeof(IRelayCommand), + typeof(IReactiveCommand), typeof(TitleBar), new PropertyMetadata(null)); @@ -232,7 +233,7 @@ public class TitleBar : Control, IThemeControl public TitleBar() { Content = []; - SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); + SetValue(TemplateButtonCommandProperty, OnTemplateButtonClickCommand); dpiScale ??= VisualTreeHelper.GetDpi(this); Loaded += OnLoaded; @@ -430,7 +431,7 @@ public bool CloseWindowByDoubleClickOnIcon /// /// Gets command triggered after clicking the titlebar button. /// - public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty); /// /// Gets or sets lets you override the behavior of the Maximize/Restore button with an . @@ -584,6 +585,7 @@ private void OnParentWindowStateChanged(object? sender, EventArgs e) } } + [ReactiveCommand] private void OnTemplateButtonClick(TitleBarButtonType buttonType) { switch (buttonType) diff --git a/src/CrissCross.WPF.UI/Input/IRelayCommand.cs b/src/CrissCross.WPF.UI/Input/IRelayCommand.cs deleted file mode 100644 index a221dfe..0000000 --- a/src/CrissCross.WPF.UI/Input/IRelayCommand.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2019-2024 ReactiveUI Association Incorporated. All rights reserved. -// ReactiveUI Association Incorporated licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Windows.Input; - -namespace CrissCross.WPF.UI.Input; - -/// -/// An interface expanding with the ability to raise -/// the event externally. -/// -public interface IRelayCommand : ICommand -{ - /// - /// Notifies that the property has changed. - /// - void NotifyCanExecuteChanged(); -} diff --git a/src/CrissCross.WPF.UI/Input/IRelayCommand{T}.cs b/src/CrissCross.WPF.UI/Input/IRelayCommand{T}.cs deleted file mode 100644 index 19827e0..0000000 --- a/src/CrissCross.WPF.UI/Input/IRelayCommand{T}.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2019-2024 ReactiveUI Association Incorporated. All rights reserved. -// ReactiveUI Association Incorporated licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Windows.Input; - -namespace CrissCross.WPF.UI.Input; - -/// -/// A generic interface representing a more specific version of . -/// -/// The type used as argument for the interface methods. -public interface IRelayCommand : IRelayCommand -{ - /// - /// Provides a strongly-typed variant of . - /// - /// The input parameter. - /// Whether or not the current command can be executed. - /// Use this overload to avoid boxing, if is a value type. - bool CanExecute(T? parameter); - - /// - /// Provides a strongly-typed variant of . - /// - /// The input parameter. - /// Use this overload to avoid boxing, if is a value type. - void Execute(T? parameter); -} diff --git a/src/CrissCross.WPF.UI/Input/RelayCommand{T}.cs b/src/CrissCross.WPF.UI/Input/RelayCommand{T}.cs deleted file mode 100644 index 3d9c48f..0000000 --- a/src/CrissCross.WPF.UI/Input/RelayCommand{T}.cs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2019-2024 ReactiveUI Association Incorporated. All rights reserved. -// ReactiveUI Association Incorporated licenses this file to you under the MIT license. -// See the LICENSE file in the project root for full license information. - -using System.Runtime.CompilerServices; - -namespace CrissCross.WPF.UI.Input; - -/// -/// A generic command whose sole purpose is to relay its functionality to other -/// objects by invoking delegates. The default return value for the CanExecute -/// method is . This class allows you to accept command parameters -/// in the and callback methods. -/// -/// The type of parameter being passed as input to the callbacks. -public class RelayCommand : IRelayCommand -{ - /// - /// The to invoke when is used. - /// - private readonly Action _execute; - - /// - /// The optional action to invoke when is used. - /// - private readonly Predicate? _canExecute; - - /// - /// Initializes a new instance of the class that can always execute. - /// - /// The execution logic. - /// - /// Due to the fact that the interface exposes methods that accept a - /// nullable parameter, it is recommended that if is a reference type, - /// you should always declare it as nullable, and to always perform checks within . - /// - /// Thrown if is . - public RelayCommand(Action execute) - { - if (execute is null) - { - throw new ArgumentNullException(nameof(execute)); - } - - _execute = execute; - } - - /// - /// Initializes a new instance of the class. - /// - /// The execution logic. - /// The execution status logic. - /// Thrown if or are . - public RelayCommand(Action execute, Predicate canExecute) - { - if (execute is null) - { - throw new ArgumentNullException(nameof(execute)); - } - - if (canExecute is null) - { - throw new ArgumentNullException(nameof(canExecute)); - } - - _execute = execute; - _canExecute = canExecute; - } - - /// - public event EventHandler? CanExecuteChanged; - - /// - public void NotifyCanExecuteChanged() - { - CanExecuteChanged?.Invoke(this, EventArgs.Empty); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CanExecute(T? parameter) - { - return _canExecute?.Invoke(parameter) != false; - } - - /// - public bool CanExecute(object? parameter) - { - // Special case a null value for a value type argument type. - // This ensures that no exceptions are thrown during initialization. - if (parameter is null && default(T) is not null) - { - return false; - } - - if (!TryGetCommandArgument(parameter, out var result)) - { - ThrowArgumentExceptionForInvalidCommandArgument(parameter); - } - - return CanExecute(result); - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(T? parameter) - { - _execute(parameter); - } - - /// - public void Execute(object? parameter) - { - if (!TryGetCommandArgument(parameter, out var result)) - { - ThrowArgumentExceptionForInvalidCommandArgument(parameter); - } - - Execute(result); - } - - /// - /// Tries to get a command argument of compatible type from an input . - /// - /// The input parameter. - /// The resulting value, if any. - /// Whether or not a compatible command argument could be retrieved. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TryGetCommandArgument(object? parameter, out T? result) - { - // If the argument is null and the default value of T is also null, then the - // argument is valid. T might be a reference type or a nullable value type. - if (parameter is null && default(T) is null) - { - result = default; - - return true; - } - - // Check if the argument is a T value, so either an instance of a type or a derived - // type of T is a reference type, an interface implementation if T is an interface, - // or a boxed value type in case T was a value type. - if (parameter is T argument) - { - result = argument; - - return true; - } - - result = default; - - return false; - } - - /// - /// Throws an if an invalid command argument is used. - /// - /// The input parameter. - /// Thrown with an error message to give info on the invalid parameter. - internal static void ThrowArgumentExceptionForInvalidCommandArgument(object? parameter) - { - [MethodImpl(MethodImplOptions.NoInlining)] - static Exception GetException(object? parameter) - { - if (parameter is null) - { - return new ArgumentException( - $"Parameter \"{nameof(parameter)}\" (object) must not be null, as the command type requires an argument of type {typeof(T)}.", - nameof(parameter)); - } - - return new ArgumentException( - $"Parameter \"{nameof(parameter)}\" (object) cannot be of type {parameter.GetType()}, as the command type requires an argument of type {typeof(T)}.", - nameof(parameter)); - } - - throw GetException(parameter); - } -}