From 41c9bb96ca4f16af1d782e9d12ab4d846b2ac926 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Sat, 14 Jan 2023 14:55:19 +0600 Subject: [PATCH 1/6] implemented BreadcrumbBar --- src/Wpf.Ui/Controls/BreadcrumbBar.cs | 65 +++++++++++ src/Wpf.Ui/Controls/BreadcrumbBarItem.cs | 46 ++++++++ .../BreadcrumbBarItemClickedEventArgs.cs | 13 +++ .../IsLastItemInContainerConverter.cs | 23 ++++ src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml | 109 ++++++++++++++++++ src/Wpf.Ui/Styles/Wpf.Ui.xaml | 1 + 6 files changed, 257 insertions(+) create mode 100644 src/Wpf.Ui/Controls/BreadcrumbBar.cs create mode 100644 src/Wpf.Ui/Controls/BreadcrumbBarItem.cs create mode 100644 src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs create mode 100644 src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs create mode 100644 src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml diff --git a/src/Wpf.Ui/Controls/BreadcrumbBar.cs b/src/Wpf.Ui/Controls/BreadcrumbBar.cs new file mode 100644 index 000000000..9751eac72 --- /dev/null +++ b/src/Wpf.Ui/Controls/BreadcrumbBar.cs @@ -0,0 +1,65 @@ +using System; +using System.Windows; +using Wpf.Ui.Common; + +namespace Wpf.Ui.Controls; + +[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(BreadcrumbBarItem))] +public class BreadcrumbBar : System.Windows.Controls.ItemsControl +{ + /// + /// Property for . + /// + public static readonly DependencyProperty TemplateButtonCommandProperty = + DependencyProperty.Register(nameof(TemplateButtonCommand), typeof(IRelayCommand), typeof(InfoBar), + new PropertyMetadata(null)); + + /// + /// Gets the triggered after clicking + /// + public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); + + /// + /// Property for . + /// + public static readonly RoutedEvent ItemClickedRoutedEvent = EventManager.RegisterRoutedEvent(nameof(ItemClicked), + RoutingStrategy.Bubble, typeof(EventHandler), typeof(BreadcrumbBar)); + + /// + /// Occurs when an item is clicked in the . + /// + public event RoutedEventHandler ItemClicked + { + add => AddHandler(ItemClickedRoutedEvent, value); + remove => RemoveHandler(ItemClickedRoutedEvent, value); + } + + public BreadcrumbBar() + { + SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + return item is BreadcrumbBarItem; + } + + protected override DependencyObject GetContainerForItemOverride() + { + return new BreadcrumbBarItem(); + } + + private void OnItemClicked(object item) + { + var args = new BreadcrumbBarItemClickedEventArgs(ItemClickedRoutedEvent, this, item); + RaiseEvent(args); + } + + private void OnTemplateButtonClick(object? obj) + { + if (obj is null) + throw new ArgumentNullException("BreadcrumbBarItem's content is null"); + + OnItemClicked(obj); + } +} diff --git a/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs b/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs new file mode 100644 index 000000000..376d31cf9 --- /dev/null +++ b/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs @@ -0,0 +1,46 @@ +using System.Windows; + +namespace Wpf.Ui.Controls; + +public class BreadcrumbBarItem : System.Windows.Controls.ContentControl +{ + public static readonly DependencyProperty SymbolIconFontSizeProperty = + DependencyProperty.Register(nameof(SymbolIconFontSize), typeof(double), typeof(BreadcrumbBarItem), + new PropertyMetadata(18.0)); + + public static readonly DependencyProperty SymbolIconFontWeightProperty = + DependencyProperty.Register(nameof(SymbolIconFontWeight), typeof(FontWeight), typeof(BreadcrumbBarItem), + new PropertyMetadata(FontWeights.DemiBold)); + + public static readonly DependencyProperty SymbolIconSymbolProperty = + DependencyProperty.Register(nameof(SymbolIconSymbol), typeof(Common.SymbolRegular), typeof(BreadcrumbBarItem), + new PropertyMetadata(Common.SymbolRegular.ChevronRight24)); + + public static readonly DependencyProperty SymbolIconMarginProperty = + DependencyProperty.Register(nameof(SymbolIconMargin), typeof(Thickness), typeof(BreadcrumbBarItem), + new PropertyMetadata(new Thickness(10, 0, 10, 0))); + + public double SymbolIconFontSize + { + get => (double)GetValue(SymbolIconFontSizeProperty); + set => SetValue(SymbolIconFontSizeProperty, value); + } + + public FontWeight SymbolIconFontWeight + { + get => (FontWeight)GetValue(SymbolIconFontWeightProperty); + set => SetValue(SymbolIconFontWeightProperty, value); + } + + public Common.SymbolRegular SymbolIconSymbol + { + get => (Common.SymbolRegular)GetValue(SymbolIconSymbolProperty); + set => SetValue(SymbolIconSymbolProperty, value); + } + + public Thickness SymbolIconMargin + { + get => (Thickness)GetValue(SymbolIconMarginProperty); + set => SetValue(SymbolIconMarginProperty, value); + } +} diff --git a/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs b/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs new file mode 100644 index 000000000..294721a3d --- /dev/null +++ b/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs @@ -0,0 +1,13 @@ +using System.Windows; + +namespace Wpf.Ui.Controls; + +public sealed class BreadcrumbBarItemClickedEventArgs : RoutedEventArgs +{ + public BreadcrumbBarItemClickedEventArgs(RoutedEvent routedEvent, object source, object item) : base(routedEvent, source) + { + Item = item; + } + + public object Item { get; } +} diff --git a/src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs b/src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs new file mode 100644 index 000000000..03f9ba6da --- /dev/null +++ b/src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows; + +namespace Wpf.Ui.Converters; + +public class IsLastItemInContainerConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + DependencyObject item = (DependencyObject)value; + ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item); + + return ic != null && ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml b/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml new file mode 100644 index 000000000..f278a006d --- /dev/null +++ b/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Wpf.Ui/Styles/Wpf.Ui.xaml b/src/Wpf.Ui/Styles/Wpf.Ui.xaml index 0cef9bf8b..86d1b702c 100644 --- a/src/Wpf.Ui/Styles/Wpf.Ui.xaml +++ b/src/Wpf.Ui/Styles/Wpf.Ui.xaml @@ -54,6 +54,7 @@ + From f5f694ed6888486c04215ad2f52cfc7096eceb91 Mon Sep 17 00:00:00 2001 From: Ivan Dmitriev <42055372+IvanDmitriev1@users.noreply.github.com> Date: Sun, 15 Jan 2023 18:28:14 +0600 Subject: [PATCH 2/6] properly implemented hiding of the icon --- src/Wpf.Ui/Controls/BreadcrumbBar.cs | 71 +++++++++++++++++-- src/Wpf.Ui/Controls/BreadcrumbBarItem.cs | 10 +++ .../BreadcrumbBarItemClickedEventArgs.cs | 11 ++- .../IsLastItemInContainerConverter.cs | 23 ------ src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml | 19 ++--- 5 files changed, 96 insertions(+), 38 deletions(-) delete mode 100644 src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs diff --git a/src/Wpf.Ui/Controls/BreadcrumbBar.cs b/src/Wpf.Ui/Controls/BreadcrumbBar.cs index 9751eac72..1f97c049a 100644 --- a/src/Wpf.Ui/Controls/BreadcrumbBar.cs +++ b/src/Wpf.Ui/Controls/BreadcrumbBar.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Specialized; using System.Windows; +using System.Windows.Controls.Primitives; using Wpf.Ui.Common; namespace Wpf.Ui.Controls; -[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(BreadcrumbBarItem))] +[StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(BreadcrumbBarItem))] public class BreadcrumbBar : System.Windows.Controls.ItemsControl { /// @@ -37,6 +39,9 @@ public event RoutedEventHandler ItemClicked public BreadcrumbBar() { SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); + + Loaded += OnLoaded; + Unloaded += OnUnloaded; } protected override bool IsItemItsOwnContainerOverride(object item) @@ -49,17 +54,73 @@ protected override DependencyObject GetContainerForItemOverride() return new BreadcrumbBarItem(); } - private void OnItemClicked(object item) + private void OnLoaded(object sender, RoutedEventArgs e) { - var args = new BreadcrumbBarItemClickedEventArgs(ItemClickedRoutedEvent, this, item); + ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorOnItemsChanged; + ItemContainerGenerator.StatusChanged += ItemContainerGeneratorOnStatusChanged; + + UpdateLastContainer(); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + Loaded -= OnLoaded; + Unloaded -= OnUnloaded; + + ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorOnItemsChanged; + } + + private void ItemContainerGeneratorOnStatusChanged(object? sender, EventArgs e) + { + if (ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) + return; + + if (ItemContainerGenerator.Items.Count <= 1) + { + UpdateLastContainer(); + return; + } + + var beforeLastItem = ItemContainerGenerator.Items[ItemContainerGenerator.Items.Count - 2]; + var beforeLastItemContainer = (BreadcrumbBarItem) ItemContainerGenerator.ContainerFromItem(beforeLastItem); + beforeLastItemContainer.IsLast = false; + + UpdateLastContainer(); + } + + private void ItemContainerGeneratorOnItemsChanged(object sender, ItemsChangedEventArgs e) + { + if (e.Action != NotifyCollectionChangedAction.Remove) + return; + + UpdateLastContainer(); + } + + private void OnItemClicked(object item, int index) + { + var args = new BreadcrumbBarItemClickedEventArgs(ItemClickedRoutedEvent, this, item, index); RaiseEvent(args); } private void OnTemplateButtonClick(object? obj) { if (obj is null) - throw new ArgumentNullException("BreadcrumbBarItem's content is null"); + throw new ArgumentNullException("Item content is null"); + + var container = ItemContainerGenerator.ContainerFromItem(obj); + var index = ItemContainerGenerator.IndexFromContainer(container); + + OnItemClicked(obj, index); + } + + private void UpdateLastContainer() + { + if (ItemContainerGenerator.Items.Count <= 0) + return; + + var lastItem = ItemContainerGenerator.Items[ItemContainerGenerator.Items.Count - 1]; + var lastContainer = (BreadcrumbBarItem) ItemContainerGenerator.ContainerFromItem(lastItem); - OnItemClicked(obj); + lastContainer.IsLast = true; } } diff --git a/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs b/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs index 376d31cf9..7bd9fb646 100644 --- a/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs +++ b/src/Wpf.Ui/Controls/BreadcrumbBarItem.cs @@ -20,6 +20,10 @@ public class BreadcrumbBarItem : System.Windows.Controls.ContentControl DependencyProperty.Register(nameof(SymbolIconMargin), typeof(Thickness), typeof(BreadcrumbBarItem), new PropertyMetadata(new Thickness(10, 0, 10, 0))); + public static readonly DependencyProperty IsLastProperty = + DependencyProperty.Register(nameof(IsLast), typeof(bool), typeof(BreadcrumbBarItem), + new PropertyMetadata(false)); + public double SymbolIconFontSize { get => (double)GetValue(SymbolIconFontSizeProperty); @@ -43,4 +47,10 @@ public Thickness SymbolIconMargin get => (Thickness)GetValue(SymbolIconMarginProperty); set => SetValue(SymbolIconMarginProperty, value); } + + public bool IsLast + { + get => (bool)GetValue(IsLastProperty); + set => SetValue(IsLastProperty, value); + } } diff --git a/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs b/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs index 294721a3d..48882e83c 100644 --- a/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs +++ b/src/Wpf.Ui/Controls/BreadcrumbBarItemClickedEventArgs.cs @@ -4,10 +4,19 @@ namespace Wpf.Ui.Controls; public sealed class BreadcrumbBarItemClickedEventArgs : RoutedEventArgs { - public BreadcrumbBarItemClickedEventArgs(RoutedEvent routedEvent, object source, object item) : base(routedEvent, source) + public BreadcrumbBarItemClickedEventArgs(RoutedEvent routedEvent, object source, object item, int index) : base(routedEvent, source) { Item = item; + Index = index; } + /// + /// Gets the Content property value of the BreadcrumbBarItem that is clicked. + /// public object Item { get; } + + /// + /// Gets the index of the item that was clicked. + /// + public int Index { get; } } diff --git a/src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs b/src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs deleted file mode 100644 index 03f9ba6da..000000000 --- a/src/Wpf.Ui/Converters/IsLastItemInContainerConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Globalization; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows; - -namespace Wpf.Ui.Converters; - -public class IsLastItemInContainerConverter : IValueConverter -{ - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - DependencyObject item = (DependencyObject)value; - ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item); - - return ic != null && ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} diff --git a/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml b/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml index f278a006d..0ebd96007 100644 --- a/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml +++ b/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml @@ -1,12 +1,9 @@  + xmlns:controls="clr-namespace:Wpf.Ui.Controls"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +