From 195fde58770e849fcd44f77b3fa10230d5fea2e1 Mon Sep 17 00:00:00 2001 From: koal Date: Tue, 12 Sep 2023 14:20:14 -0700 Subject: [PATCH 1/5] Fix submenu navigation in NavigationViewItem The feature for submenus in NavigationViewTop was previously implemented but left unused prolly because of odd behavior such as stack overflow errors in AutoSuggest. The problem was traced to the NavigationViewItem.MenuItems dependency property, where a new ObservableCollection was incorrectly used as the default value. According to [MS Docs], "dependency property metadata shouldn't include a default reference-type value because that value will be assigned to all instances of the class, creating a singleton class." --- .../ViewModels/Windows/MainWindowViewModel.cs | 23 +- .../Pages/Navigation/NavigationViewPage.xaml | 717 +++++++++--------- .../NavigationView/INavigationViewItem.cs | 9 +- .../NavigationView/NavigationViewItem.cs | 82 +- .../NavigationViewLeftMinimalCompact.xaml | 340 ++++----- .../NavigationView/NavigationViewTop.xaml | 684 ++++++++--------- 6 files changed, 921 insertions(+), 934 deletions(-) diff --git a/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs b/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs index f11a57a21..f97aaf3af 100644 --- a/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs +++ b/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +using System.Collections.ObjectModel; using System.Windows.Controls; using System.Windows.Controls.Primitives; using Wpf.Ui.Controls; @@ -35,7 +36,7 @@ public partial class MainWindowViewModel : ObservableObject { Content = "Design guidance", Icon = new SymbolIcon { Symbol = SymbolRegular.DesignIdeas24 }, - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("Typography", SymbolRegular.TextFont24, typeof(TypographyPage)), new NavigationViewItem("Icons", SymbolRegular.Diversity24, typeof(IconsPage)), @@ -46,7 +47,7 @@ public partial class MainWindowViewModel : ObservableObject new NavigationViewItemSeparator(), new NavigationViewItem("Basic Input", SymbolRegular.CheckboxChecked24, typeof(BasicInputPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(Anchor), typeof(AnchorPage)), new NavigationViewItem(nameof(Wpf.Ui.Controls.Button), typeof(ButtonPage)), @@ -68,7 +69,7 @@ public partial class MainWindowViewModel : ObservableObject Content = "Collections", Icon = new SymbolIcon { Symbol = SymbolRegular.Table24 }, TargetPageType = typeof(CollectionsPage), - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(System.Windows.Controls.DataGrid), typeof(DataGridPage)), new NavigationViewItem(nameof(ListBox), typeof(ListBoxPage)), @@ -81,7 +82,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Date & time", SymbolRegular.CalendarClock24, typeof(DateAndTimePage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(CalendarDatePicker), typeof(CalendarDatePickerPage)), new NavigationViewItem(nameof(System.Windows.Controls.Calendar), typeof(CalendarPage)), @@ -91,7 +92,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Dialogs & flyouts", SymbolRegular.Chat24, typeof(DialogsAndFlyoutsPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(Snackbar), typeof(SnackbarPage)), new NavigationViewItem(nameof(ContentDialog), typeof(ContentDialogPage)), @@ -102,7 +103,7 @@ public partial class MainWindowViewModel : ObservableObject #if DEBUG new NavigationViewItem("Layout", SymbolRegular.News24, typeof(LayoutPage)) { - MenuItems = new object[] { new NavigationViewItem("Expander", typeof(ExpanderPage)) } + MenuItems = new ObservableCollection { new NavigationViewItem("Expander", typeof(ExpanderPage)) } }, #endif new NavigationViewItem @@ -110,7 +111,7 @@ public partial class MainWindowViewModel : ObservableObject Content = "Media", Icon = new SymbolIcon { Symbol = SymbolRegular.PlayCircle24 }, TargetPageType = typeof(MediaPage), - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("Image", typeof(ImagePage)), new NavigationViewItem("Canvas", typeof(CanvasPage)), @@ -120,7 +121,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Navigation", SymbolRegular.Navigation24, typeof(NavigationPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("BreadcrumbBar", typeof(BreadcrumbBarPage)), new NavigationViewItem("NavigationView", typeof(NavigationViewPage)), @@ -131,7 +132,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Status & info", SymbolRegular.ChatBubblesQuestion24, typeof(StatusAndInfoPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("InfoBar", typeof(InfoBarPage)), new NavigationViewItem("ProgressBar", typeof(ProgressBarPage)), @@ -141,7 +142,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Text", SymbolRegular.DrawText24, typeof(TextPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(AutoSuggestBox), typeof(AutoSuggestBoxPage)), new NavigationViewItem(nameof(NumberBox), typeof(NumberBoxPage)), @@ -154,7 +155,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("System", SymbolRegular.Desktop24, typeof(OpSystemPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("Clipboard", typeof(ClipboardPage)), new NavigationViewItem("FilePicker", typeof(FilePickerPage)), diff --git a/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml b/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml index 2b1d18110..df8ed78e5 100644 --- a/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml +++ b/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml @@ -1,362 +1,367 @@ + x:Class="Wpf.Ui.Gallery.Views.Pages.Navigation.NavigationViewPage" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Wpf.Ui.Gallery.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:Wpf.Ui.Gallery.Views.Pages.Navigation" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:samples="clr-namespace:Wpf.Ui.Gallery.Views.Pages.Samples" + xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" + Title="NavigationViewPage" + controls:PageControlDocumentation.DocumentationType="{x:Type ui:NavigationView}" + d:DataContext="{d:DesignInstance local:NavigationViewPage, + IsDesignTimeCreatable=False}" + d:DesignHeight="1650" + d:DesignWidth="1000" + ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}" + ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}" + Foreground="{DynamicResource TextFillColorPrimaryBrush}" + mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView IsBackButtonVisible="Auto" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView IsBackButtonVisible="Auto" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView PaneDisplayMode="LeftFluent" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView PaneDisplayMode="LeftFluent" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView PaneDisplayMode="Top" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView PaneDisplayMode="Top" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView PaneDisplayMode="Bottom" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView PaneDisplayMode="Bottom" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + + diff --git a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs index 1d6681581..ead955c7c 100644 --- a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs @@ -6,7 +6,7 @@ // Based on Windows UI Library // Copyright(c) Microsoft Corporation.All rights reserved. -using System.Collections; +using System.Collections.ObjectModel; using System.Windows.Controls; // ReSharper disable once CheckNamespace @@ -35,12 +35,7 @@ public interface INavigationViewItem /// /// Gets the collection of menu items displayed in the NavigationView. /// - IList MenuItems { get; set; } - - /// - /// Gets or sets an object source used to generate the content of the NavigationView menu. - /// - object? MenuItemsSource { get; set; } + ObservableCollection MenuItems { get; set; } /// /// Gets information whether the current element is active. diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs index 0224a8687..43c7947ca 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs @@ -8,6 +8,7 @@ using System.Collections; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Windows.Controls; using System.Windows.Input; using Wpf.Ui.Converters; @@ -35,30 +36,22 @@ public class NavigationViewItem : System.Windows.Controls.Primitives.ButtonBase, /// public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register( nameof(MenuItems), - typeof(IList), + typeof(ObservableCollection), typeof(NavigationViewItem), - new PropertyMetadata(new ObservableCollection(), OnMenuItemsPropertyChanged) + new PropertyMetadata(null, OnMenuItemsChanged) ); - /// - /// Property for . - /// - public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.Register( - nameof(MenuItemsSource), - typeof(object), + private static readonly DependencyPropertyKey HasMenuItemsPropertyKey = DependencyProperty.RegisterReadOnly( + nameof(HasMenuItems), + typeof(bool), typeof(NavigationViewItem), - new PropertyMetadata(null, OnMenuItemsSourcePropertyChanged) + new PropertyMetadata(false) ); /// /// Property for . /// - public static readonly DependencyProperty HasMenuItemsProperty = DependencyProperty.Register( - nameof(HasMenuItems), - typeof(bool), - typeof(NavigationViewItem), - new PropertyMetadata(false) - ); + public static readonly DependencyProperty HasMenuItemsProperty = HasMenuItemsPropertyKey.DependencyProperty; /// /// Property for . @@ -125,26 +118,12 @@ public class NavigationViewItem : System.Windows.Controls.Primitives.ButtonBase, #region Properties /// - public IList MenuItems + public ObservableCollection MenuItems { - get => (IList)GetValue(MenuItemsProperty); + get => (ObservableCollection)GetValue(MenuItemsProperty); set => SetValue(MenuItemsProperty, value); } - /// - [Bindable(true)] - public object? MenuItemsSource - { - get => GetValue(MenuItemsSourceProperty); - set - { - if (value == null) - ClearValue(MenuItemsSourceProperty); - else - SetValue(MenuItemsSourceProperty, value); - } - } - /// /// Gets a value indicating whether the has . /// @@ -152,7 +131,7 @@ public object? MenuItemsSource public bool HasMenuItems { get => (bool)GetValue(HasMenuItemsProperty); - private set => SetValue(HasMenuItemsProperty, value); + private set => SetValue(HasMenuItemsPropertyKey, value); } /// @@ -225,7 +204,11 @@ public NavigationViewItem() { Id = Guid.NewGuid().ToString("n"); - Unloaded += static (sender, _) => ((NavigationViewItem)sender).NavigationViewItemParent = null; + MenuItems = new ObservableCollection(); + MenuItems.CollectionChanged += OnMenuItemsCollectionChanged; + + Unloaded += static (sender, _) => + ((NavigationViewItem)sender).NavigationViewItemParent = null; } public NavigationViewItem(Type targetPageType) : this() @@ -244,7 +227,7 @@ public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType) SetValue(IconProperty, new SymbolIcon { Symbol = icon }); } - public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType, IList menuItems) + public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType, ObservableCollection menuItems) : this(name, icon, targetPageType) { SetValue(MenuItemsProperty, menuItems); @@ -379,30 +362,33 @@ protected override void OnMouseDown(MouseButtonEventArgs e) e.Handled = true; } - private static void OnMenuItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnMenuItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationViewItem navigationViewItem) return; - navigationViewItem.HasMenuItems = navigationViewItem.MenuItems.Count > 0; - - foreach (var menuItem in navigationViewItem.MenuItems) + if (e.OldValue is ObservableCollection oldCollection) { - if (menuItem is not INavigationViewItem item) - continue; + oldCollection.CollectionChanged -= navigationViewItem.OnMenuItemsCollectionChanged; + } - item.NavigationViewItemParent = navigationViewItem; + if (e.NewValue is ObservableCollection newCollection) + { + newCollection.CollectionChanged += navigationViewItem.OnMenuItemsCollectionChanged; + navigationViewItem.OnMenuItemsCollectionChanged(newCollection, null); } } - private static void OnMenuItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private void OnMenuItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) { - if (d is not NavigationViewItem navigationViewItem || e.NewValue is not IList enumerableNewValue) - return; + HasMenuItems = MenuItems.Count > 0; - navigationViewItem.MenuItems = enumerableNewValue; - - if (navigationViewItem.MenuItems.Count > 0) - navigationViewItem.HasMenuItems = true; + foreach (var menuItem in MenuItems) + { + if (menuItem is INavigationViewItem item) + { + item.NavigationViewItemParent = this; + } + } } } diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml index 4befc8a0d..467b16ec2 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml @@ -1,46 +1,122 @@ - - - + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Wpf.Ui.Controls"> + + + - + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml index 0580b1e89..05a20f08c 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml @@ -1,368 +1,368 @@ - - - - - + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Wpf.Ui.Controls" + xmlns:converters="clr-namespace:Wpf.Ui.Converters"> + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - + + + + + + + - - + + - - + + - - - - - - - - + + + + + + + + + + + + + + - - - - - + + - - + + - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From 92a5a64ed0a631f878ef9aac1ac59fc950fd81ff Mon Sep 17 00:00:00 2001 From: koal Date: Wed, 13 Sep 2023 13:31:33 -0700 Subject: [PATCH 2/5] Use HasMenuItems property for clearer intent and null-safety --- src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs | 2 ++ src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs | 4 ++-- src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs index ead955c7c..8ea810b6a 100644 --- a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs @@ -80,6 +80,8 @@ public interface INavigationViewItem internal bool IsMenuElement { get; set; } + bool HasMenuItems { get; } + /// /// Correctly activates /// diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs index 731aa73df..5522f5c3a 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs @@ -287,7 +287,7 @@ singleNavigationViewItem.TargetPageType is not null singleNavigationViewItem.IsMenuElement = true; - if (singleNavigationViewItem.MenuItems.Count <= 0) + if (!singleNavigationViewItem.HasMenuItems) continue; AddItemsToDictionaries(singleNavigationViewItem.MenuItems); @@ -315,7 +315,7 @@ protected virtual void AddItemsToAutoSuggestBoxItems(IList list) ) _autoSuggestBoxItems.Add(content); - if (singleNavigationViewItem.MenuItems.Count <= 0) + if (!singleNavigationViewItem.HasMenuItems) continue; AddItemsToAutoSuggestBoxItems(singleNavigationViewItem.MenuItems); diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs index 43c7947ca..f2b6b6cdc 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs @@ -367,6 +367,11 @@ private static void OnMenuItemsChanged(DependencyObject d, DependencyPropertyCha if (d is not NavigationViewItem navigationViewItem) return; + if (e.NewValue == null) + { + navigationViewItem.HasMenuItems = false; + } + if (e.OldValue is ObservableCollection oldCollection) { oldCollection.CollectionChanged -= navigationViewItem.OnMenuItemsCollectionChanged; From d825508e0a2fb2512148409fb064b9d9bfcbadfc Mon Sep 17 00:00:00 2001 From: koal Date: Sat, 16 Sep 2023 19:28:35 -0700 Subject: [PATCH 3/5] Formalize indentation settings for XamlStyler --- Settings.XamlStyler | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Settings.XamlStyler b/Settings.XamlStyler index 3b3a21984..7e44ef314 100644 --- a/Settings.XamlStyler +++ b/Settings.XamlStyler @@ -7,7 +7,7 @@ "SeparateByGroups": false, "AttributeIndentation": 0, "AttributeIndentationStyle": 1, - "RemoveDesignTimeReferences": false, + "RemoveDesignTimeReferences": false, "IgnoreDesignTimeReferencePrefix": false, "EnableAttributeReordering": true, "AttributeOrderingRuleGroups": [ @@ -38,5 +38,7 @@ "ThicknessSeparator": 2, "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin", "FormatOnSave": true, - "CommentPadding": 2 + "CommentPadding": 2, + "IndentSize": 4, + "IndentWithTabs": false } \ No newline at end of file From 8c62af866c9f062ffe73daca0147444c82461f70 Mon Sep 17 00:00:00 2001 From: koal Date: Tue, 12 Sep 2023 14:20:14 -0700 Subject: [PATCH 4/5] Fix submenu navigation in NavigationViewItem The feature for submenus in NavigationViewTop was previously implemented but left unused prolly because of odd behavior such as stack overflow errors in AutoSuggest. The problem was traced to the NavigationViewItem.MenuItems dependency property, where a new ObservableCollection was incorrectly used as the default value. According to [MS Docs], "dependency property metadata shouldn't include a default reference-type value because that value will be assigned to all instances of the class, creating a singleton class." --- .../ViewModels/Windows/MainWindowViewModel.cs | 23 +- .../Pages/Navigation/NavigationViewPage.xaml | 717 +++++++++--------- .../NavigationView/INavigationViewItem.cs | 9 +- .../NavigationView/NavigationViewItem.cs | 82 +- .../NavigationViewLeftMinimalCompact.xaml | 340 ++++----- .../NavigationView/NavigationViewTop.xaml | 684 ++++++++--------- 6 files changed, 921 insertions(+), 934 deletions(-) diff --git a/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs b/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs index f11a57a21..f97aaf3af 100644 --- a/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs +++ b/src/Wpf.Ui.Gallery/ViewModels/Windows/MainWindowViewModel.cs @@ -3,6 +3,7 @@ // Copyright (C) Leszek Pomianowski and WPF UI Contributors. // All Rights Reserved. +using System.Collections.ObjectModel; using System.Windows.Controls; using System.Windows.Controls.Primitives; using Wpf.Ui.Controls; @@ -35,7 +36,7 @@ public partial class MainWindowViewModel : ObservableObject { Content = "Design guidance", Icon = new SymbolIcon { Symbol = SymbolRegular.DesignIdeas24 }, - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("Typography", SymbolRegular.TextFont24, typeof(TypographyPage)), new NavigationViewItem("Icons", SymbolRegular.Diversity24, typeof(IconsPage)), @@ -46,7 +47,7 @@ public partial class MainWindowViewModel : ObservableObject new NavigationViewItemSeparator(), new NavigationViewItem("Basic Input", SymbolRegular.CheckboxChecked24, typeof(BasicInputPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(Anchor), typeof(AnchorPage)), new NavigationViewItem(nameof(Wpf.Ui.Controls.Button), typeof(ButtonPage)), @@ -68,7 +69,7 @@ public partial class MainWindowViewModel : ObservableObject Content = "Collections", Icon = new SymbolIcon { Symbol = SymbolRegular.Table24 }, TargetPageType = typeof(CollectionsPage), - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(System.Windows.Controls.DataGrid), typeof(DataGridPage)), new NavigationViewItem(nameof(ListBox), typeof(ListBoxPage)), @@ -81,7 +82,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Date & time", SymbolRegular.CalendarClock24, typeof(DateAndTimePage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(CalendarDatePicker), typeof(CalendarDatePickerPage)), new NavigationViewItem(nameof(System.Windows.Controls.Calendar), typeof(CalendarPage)), @@ -91,7 +92,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Dialogs & flyouts", SymbolRegular.Chat24, typeof(DialogsAndFlyoutsPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(Snackbar), typeof(SnackbarPage)), new NavigationViewItem(nameof(ContentDialog), typeof(ContentDialogPage)), @@ -102,7 +103,7 @@ public partial class MainWindowViewModel : ObservableObject #if DEBUG new NavigationViewItem("Layout", SymbolRegular.News24, typeof(LayoutPage)) { - MenuItems = new object[] { new NavigationViewItem("Expander", typeof(ExpanderPage)) } + MenuItems = new ObservableCollection { new NavigationViewItem("Expander", typeof(ExpanderPage)) } }, #endif new NavigationViewItem @@ -110,7 +111,7 @@ public partial class MainWindowViewModel : ObservableObject Content = "Media", Icon = new SymbolIcon { Symbol = SymbolRegular.PlayCircle24 }, TargetPageType = typeof(MediaPage), - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("Image", typeof(ImagePage)), new NavigationViewItem("Canvas", typeof(CanvasPage)), @@ -120,7 +121,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Navigation", SymbolRegular.Navigation24, typeof(NavigationPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("BreadcrumbBar", typeof(BreadcrumbBarPage)), new NavigationViewItem("NavigationView", typeof(NavigationViewPage)), @@ -131,7 +132,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Status & info", SymbolRegular.ChatBubblesQuestion24, typeof(StatusAndInfoPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("InfoBar", typeof(InfoBarPage)), new NavigationViewItem("ProgressBar", typeof(ProgressBarPage)), @@ -141,7 +142,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("Text", SymbolRegular.DrawText24, typeof(TextPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem(nameof(AutoSuggestBox), typeof(AutoSuggestBoxPage)), new NavigationViewItem(nameof(NumberBox), typeof(NumberBoxPage)), @@ -154,7 +155,7 @@ public partial class MainWindowViewModel : ObservableObject }, new NavigationViewItem("System", SymbolRegular.Desktop24, typeof(OpSystemPage)) { - MenuItems = new object[] + MenuItems = new ObservableCollection { new NavigationViewItem("Clipboard", typeof(ClipboardPage)), new NavigationViewItem("FilePicker", typeof(FilePickerPage)), diff --git a/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml b/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml index 2b1d18110..df8ed78e5 100644 --- a/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml +++ b/src/Wpf.Ui.Gallery/Views/Pages/Navigation/NavigationViewPage.xaml @@ -1,362 +1,367 @@ + x:Class="Wpf.Ui.Gallery.Views.Pages.Navigation.NavigationViewPage" + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Wpf.Ui.Gallery.Controls" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:local="clr-namespace:Wpf.Ui.Gallery.Views.Pages.Navigation" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:samples="clr-namespace:Wpf.Ui.Gallery.Views.Pages.Samples" + xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" + Title="NavigationViewPage" + controls:PageControlDocumentation.DocumentationType="{x:Type ui:NavigationView}" + d:DataContext="{d:DesignInstance local:NavigationViewPage, + IsDesignTimeCreatable=False}" + d:DesignHeight="1650" + d:DesignWidth="1000" + ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}" + ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}" + Foreground="{DynamicResource TextFillColorPrimaryBrush}" + mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView IsBackButtonVisible="Auto" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView IsBackButtonVisible="Auto" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView PaneDisplayMode="LeftFluent" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView PaneDisplayMode="LeftFluent" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView PaneDisplayMode="Top" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView PaneDisplayMode="Top" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <ui:NavigationView PaneDisplayMode="Bottom" >\n - \t<ui:NavigationView.MenuItems>\n - \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n - \t</ui:NavigationView.MenuItems>\n - </ui:NavigationView> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <ui:NavigationView PaneDisplayMode="Bottom" >\n + \t<ui:NavigationView.MenuItems>\n + \t\t<ui:NavigationViewItem Content="Home" Icon="Home24" />\n + \t</ui:NavigationView.MenuItems>\n + </ui:NavigationView> + + + diff --git a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs index 1d6681581..ead955c7c 100644 --- a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs @@ -6,7 +6,7 @@ // Based on Windows UI Library // Copyright(c) Microsoft Corporation.All rights reserved. -using System.Collections; +using System.Collections.ObjectModel; using System.Windows.Controls; // ReSharper disable once CheckNamespace @@ -35,12 +35,7 @@ public interface INavigationViewItem /// /// Gets the collection of menu items displayed in the NavigationView. /// - IList MenuItems { get; set; } - - /// - /// Gets or sets an object source used to generate the content of the NavigationView menu. - /// - object? MenuItemsSource { get; set; } + ObservableCollection MenuItems { get; set; } /// /// Gets information whether the current element is active. diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs index 0224a8687..43c7947ca 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs @@ -8,6 +8,7 @@ using System.Collections; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Windows.Controls; using System.Windows.Input; using Wpf.Ui.Converters; @@ -35,30 +36,22 @@ public class NavigationViewItem : System.Windows.Controls.Primitives.ButtonBase, /// public static readonly DependencyProperty MenuItemsProperty = DependencyProperty.Register( nameof(MenuItems), - typeof(IList), + typeof(ObservableCollection), typeof(NavigationViewItem), - new PropertyMetadata(new ObservableCollection(), OnMenuItemsPropertyChanged) + new PropertyMetadata(null, OnMenuItemsChanged) ); - /// - /// Property for . - /// - public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.Register( - nameof(MenuItemsSource), - typeof(object), + private static readonly DependencyPropertyKey HasMenuItemsPropertyKey = DependencyProperty.RegisterReadOnly( + nameof(HasMenuItems), + typeof(bool), typeof(NavigationViewItem), - new PropertyMetadata(null, OnMenuItemsSourcePropertyChanged) + new PropertyMetadata(false) ); /// /// Property for . /// - public static readonly DependencyProperty HasMenuItemsProperty = DependencyProperty.Register( - nameof(HasMenuItems), - typeof(bool), - typeof(NavigationViewItem), - new PropertyMetadata(false) - ); + public static readonly DependencyProperty HasMenuItemsProperty = HasMenuItemsPropertyKey.DependencyProperty; /// /// Property for . @@ -125,26 +118,12 @@ public class NavigationViewItem : System.Windows.Controls.Primitives.ButtonBase, #region Properties /// - public IList MenuItems + public ObservableCollection MenuItems { - get => (IList)GetValue(MenuItemsProperty); + get => (ObservableCollection)GetValue(MenuItemsProperty); set => SetValue(MenuItemsProperty, value); } - /// - [Bindable(true)] - public object? MenuItemsSource - { - get => GetValue(MenuItemsSourceProperty); - set - { - if (value == null) - ClearValue(MenuItemsSourceProperty); - else - SetValue(MenuItemsSourceProperty, value); - } - } - /// /// Gets a value indicating whether the has . /// @@ -152,7 +131,7 @@ public object? MenuItemsSource public bool HasMenuItems { get => (bool)GetValue(HasMenuItemsProperty); - private set => SetValue(HasMenuItemsProperty, value); + private set => SetValue(HasMenuItemsPropertyKey, value); } /// @@ -225,7 +204,11 @@ public NavigationViewItem() { Id = Guid.NewGuid().ToString("n"); - Unloaded += static (sender, _) => ((NavigationViewItem)sender).NavigationViewItemParent = null; + MenuItems = new ObservableCollection(); + MenuItems.CollectionChanged += OnMenuItemsCollectionChanged; + + Unloaded += static (sender, _) => + ((NavigationViewItem)sender).NavigationViewItemParent = null; } public NavigationViewItem(Type targetPageType) : this() @@ -244,7 +227,7 @@ public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType) SetValue(IconProperty, new SymbolIcon { Symbol = icon }); } - public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType, IList menuItems) + public NavigationViewItem(string name, SymbolRegular icon, Type targetPageType, ObservableCollection menuItems) : this(name, icon, targetPageType) { SetValue(MenuItemsProperty, menuItems); @@ -379,30 +362,33 @@ protected override void OnMouseDown(MouseButtonEventArgs e) e.Handled = true; } - private static void OnMenuItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private static void OnMenuItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not NavigationViewItem navigationViewItem) return; - navigationViewItem.HasMenuItems = navigationViewItem.MenuItems.Count > 0; - - foreach (var menuItem in navigationViewItem.MenuItems) + if (e.OldValue is ObservableCollection oldCollection) { - if (menuItem is not INavigationViewItem item) - continue; + oldCollection.CollectionChanged -= navigationViewItem.OnMenuItemsCollectionChanged; + } - item.NavigationViewItemParent = navigationViewItem; + if (e.NewValue is ObservableCollection newCollection) + { + newCollection.CollectionChanged += navigationViewItem.OnMenuItemsCollectionChanged; + navigationViewItem.OnMenuItemsCollectionChanged(newCollection, null); } } - private static void OnMenuItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private void OnMenuItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs? e) { - if (d is not NavigationViewItem navigationViewItem || e.NewValue is not IList enumerableNewValue) - return; + HasMenuItems = MenuItems.Count > 0; - navigationViewItem.MenuItems = enumerableNewValue; - - if (navigationViewItem.MenuItems.Count > 0) - navigationViewItem.HasMenuItems = true; + foreach (var menuItem in MenuItems) + { + if (menuItem is INavigationViewItem item) + { + item.NavigationViewItemParent = this; + } + } } } diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml index 4befc8a0d..467b16ec2 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewLeftMinimalCompact.xaml @@ -1,46 +1,122 @@ - - - + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Wpf.Ui.Controls"> + + + - + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml b/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml index 0580b1e89..05a20f08c 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewTop.xaml @@ -1,368 +1,368 @@ - - - - - + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="clr-namespace:Wpf.Ui.Controls" + xmlns:converters="clr-namespace:Wpf.Ui.Converters"> + + + + + - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - + + - - - - - - - + + + + + + + - - + + - - + + - - - - - - - - + + + + + + + + + + + + + + - - - - - + + - - + + - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + From 33dd1d1758a9c7d1e8b9c1705cc1375bc7893fcb Mon Sep 17 00:00:00 2001 From: koal Date: Wed, 13 Sep 2023 13:31:33 -0700 Subject: [PATCH 5/5] Use HasMenuItems property for clearer intent and null-safety --- src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs | 2 ++ src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs | 4 ++-- src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs index ead955c7c..8ea810b6a 100644 --- a/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/INavigationViewItem.cs @@ -80,6 +80,8 @@ public interface INavigationViewItem internal bool IsMenuElement { get; set; } + bool HasMenuItems { get; } + /// /// Correctly activates /// diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs index 731aa73df..5522f5c3a 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationView.Base.cs @@ -287,7 +287,7 @@ singleNavigationViewItem.TargetPageType is not null singleNavigationViewItem.IsMenuElement = true; - if (singleNavigationViewItem.MenuItems.Count <= 0) + if (!singleNavigationViewItem.HasMenuItems) continue; AddItemsToDictionaries(singleNavigationViewItem.MenuItems); @@ -315,7 +315,7 @@ protected virtual void AddItemsToAutoSuggestBoxItems(IList list) ) _autoSuggestBoxItems.Add(content); - if (singleNavigationViewItem.MenuItems.Count <= 0) + if (!singleNavigationViewItem.HasMenuItems) continue; AddItemsToAutoSuggestBoxItems(singleNavigationViewItem.MenuItems); diff --git a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs index 43c7947ca..f2b6b6cdc 100644 --- a/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs +++ b/src/Wpf.Ui/Controls/NavigationView/NavigationViewItem.cs @@ -367,6 +367,11 @@ private static void OnMenuItemsChanged(DependencyObject d, DependencyPropertyCha if (d is not NavigationViewItem navigationViewItem) return; + if (e.NewValue == null) + { + navigationViewItem.HasMenuItems = false; + } + if (e.OldValue is ObservableCollection oldCollection) { oldCollection.CollectionChanged -= navigationViewItem.OnMenuItemsCollectionChanged;