Skip to content

Commit

Permalink
Create a breadcrumb Navigation integration
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisPulman committed Nov 25, 2024
1 parent e94f7ec commit e2f7d68
Show file tree
Hide file tree
Showing 19 changed files with 222 additions and 291 deletions.
2 changes: 1 addition & 1 deletion Version.json
Original file line number Diff line number Diff line change
@@ -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$"
Expand Down
3 changes: 3 additions & 0 deletions src/CrissCross.WPF.UI.Gallery/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
d:DataContext="{d:DesignInstance Type=local:MainWindowViewModel}"
x:TypeArguments="local:MainWindowViewModel"
mc:Ignorable="d">
<ui:FluentNavigationWindow.TopContent>
<ui:BreadcrumbBar x:Name="NavBreadcrumb" />
</ui:FluentNavigationWindow.TopContent>
<ui:FluentNavigationWindow.LeftContent>
<ui:StackPanel>
<ui:TextBox Margin="3,0,0,0" Text="{Binding Filter, ElementName=NavigationLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
Expand Down
16 changes: 15 additions & 1 deletion src/CrissCross.WPF.UI.Gallery/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -33,6 +34,7 @@ public MainWindow()
// Watch for system theme changes
SystemThemeWatcher.Watch(this);
InitializeComponent();
Navigation = NavBreadcrumb;

// Set the data context
DataContext = ViewModel = new();
Expand All @@ -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<MainViewModel>();
NavBreadcrumb.NavigateTo<MainViewModel>(breadcrumbItemContent: "Main");
////this.NavigateToView<MainViewModel>();
});

// Dispose the view model on close
Closing += (s, e) => ViewModel.Dispose();
}

/// <summary>
/// Gets the nav breadcrumb.
/// </summary>
/// <value>
/// The nav breadcrumb.
/// </value>
public static BreadcrumbBar? Navigation { get; private set; }
}
24 changes: 12 additions & 12 deletions src/CrissCross.WPF.UI.Gallery/ViewModels/AllControlsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,72 +15,72 @@ public partial class AllControlsViewModel : RxObject
[ReactiveCommand]
private void Buttons()
{
this.NavigateToView<ButtonsViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<ButtonsViewModel>(breadcrumbItemContent: "Buttons");
}

[ReactiveCommand]
private void CheckBox()
{
this.NavigateToView<CheckBoxViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<CheckBoxViewModel>(breadcrumbItemContent: "CheckBox");
}

[ReactiveCommand]
private void ComboBox()
{
this.NavigateToView<ComboBoxViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<ComboBoxViewModel>(breadcrumbItemContent: "ComboBox");
}

[ReactiveCommand]
private void DatePicker()
{
this.NavigateToView<DatePickerViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<DatePickerViewModel>(breadcrumbItemContent: "DatePicker");
}

[ReactiveCommand]
private void Image()
{
this.NavigateToView<ImageViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<ImageViewModel>(breadcrumbItemContent: "Image");
}

[ReactiveCommand]
private void NumericPushButton()
{
this.NavigateToView<NumericPushButtonViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<NumericPushButtonViewModel>(breadcrumbItemContent: "NumericPushButton");
}

[ReactiveCommand]
private void PasswordBox()
{
this.NavigateToView<PasswordBoxViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<PasswordBoxViewModel>(breadcrumbItemContent: "PasswordBox");
}

[ReactiveCommand]
private void RadioButton()
{
this.NavigateToView<RadioButtonViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<RadioButtonViewModel>(breadcrumbItemContent: "RadioButton");
}

[ReactiveCommand]
private void Slider()
{
this.NavigateToView<SliderViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<SliderViewModel>(breadcrumbItemContent: "Slider");
}

[ReactiveCommand]
private void TextBlock()
{
this.NavigateToView<TextBlockViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<TextBlockViewModel>(breadcrumbItemContent: "TextBlock");
}

[ReactiveCommand]
private void TextBox()
{
this.NavigateToView<TextBoxViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<TextBoxViewModel>(breadcrumbItemContent: "TextBox");
}

[ReactiveCommand]
private void ToggleButton()
{
this.NavigateToView<ToggleButtonViewModel>("mainWindow");
MainWindow.Navigation?.NavigateTo<ToggleButtonViewModel>(breadcrumbItemContent: "ToggleButton");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
{
/// <summary>
/// Property for <see cref="OriginalItemsSource"/>.
Expand Down Expand Up @@ -190,7 +190,7 @@ public AutoSuggestBox()
self.ReleaseTemplateResources();
};

SetValue(FocusCommandProperty, new RelayCommand<object>(_ => Focus()));
SetValue(FocusCommandProperty, ReactiveCommand.Create(Focus));
}

/// <summary>
Expand Down
95 changes: 88 additions & 7 deletions src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -18,7 +19,7 @@ namespace CrissCross.WPF.UI.Controls;
/// </code>
/// </example>
[StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(BreadcrumbBarItem))]
public class BreadcrumbBar : System.Windows.Controls.ItemsControl
public partial class BreadcrumbBar : System.Windows.Controls.ItemsControl, IUseHostedNavigation
{
/// <summary>
/// Property for <see cref="Command"/>.
Expand All @@ -34,7 +35,7 @@ public class BreadcrumbBar : System.Windows.Controls.ItemsControl
/// </summary>
public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register(
nameof(TemplateButtonCommand),
typeof(IRelayCommand),
typeof(IReactiveCommand),
typeof(BreadcrumbBar),
new PropertyMetadata(null));

Expand All @@ -47,13 +48,14 @@ public class BreadcrumbBar : System.Windows.Controls.ItemsControl
typeof(TypedEventHandler<BreadcrumbBar, BreadcrumbBarItemClickedEventArgs>),
typeof(BreadcrumbBar));

private string? _hostName;

/// <summary>
/// Initializes a new instance of the <see cref="BreadcrumbBar"/> class.
/// </summary>
public BreadcrumbBar()
{
SetValue(TemplateButtonCommandProperty, new RelayCommand<object>(OnTemplateButtonClick));

SetValue(TemplateButtonCommandProperty, OnTemplateButtonClickCommand);
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
Expand All @@ -68,9 +70,9 @@ public event TypedEventHandler<BreadcrumbBar, BreadcrumbBarItemClickedEventArgs>
}

/// <summary>
/// Gets the <see cref="RelayCommand{T}"/> triggered after clicking.
/// Gets the <see cref="ReactiveCommand{Tin, Tout}"/> triggered after clicking.
/// </summary>
public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
public IReactiveCommand TemplateButtonCommand => (IReactiveCommand)GetValue(TemplateButtonCommandProperty);

/// <summary>
/// Gets or sets custom command executed after selecting the item.
Expand All @@ -84,6 +86,67 @@ public ICommand Command
set => SetValue(CommandProperty, value);
}

/// <summary>
/// Setups the navigation.
/// </summary>
/// <param name="hostName">Name of the host.</param>
public void SetupNavigation(string hostName)
{
_hostName = hostName;
ItemClicked += (s, e) =>
{
if (e.Item is BreadcrumbBarItem item)
{
NavigateTo(item.NavigationType);
}
};
}

/// <summary>
/// Navigates to the specified view as registered to the viewmodel and updates the Breadcrumb.
/// </summary>
/// <typeparam name="T">Type of ViewModel.</typeparam>
/// <param name="contract">The viewmodel contract.</param>
/// <param name="parameter">The navigation parameter.</param>
/// <param name="breadcrumbItemContent">Content of the breadcrumb item.</param>
public void NavigateTo<T>(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<T>(_hostName, contract, parameter);
}

/// <summary>
/// Navigates to the specified view as registered to the viewmodel and updates the Breadcrumb.
/// </summary>
/// <param name="type">Type of ViewModel.</param>
/// <param name="contract">The viewmodel contract.</param>
/// <param name="parameter">The navigation parameter.</param>
/// <param name="breadcrumbItemContent">Content of the breadcrumb item.</param>
/// <exception cref="System.ArgumentNullException">type.</exception>
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);
}

/// <summary>
/// Called when [item clicked].
/// </summary>
Expand Down Expand Up @@ -122,6 +185,23 @@ protected virtual void OnItemClicked(object item, int index)
/// </returns>
protected override DependencyObject GetContainerForItemOverride() => new BreadcrumbBarItem();

private void UpdateItems(Type typeName, string? content = null)
{
var list = Items.OfType<BreadcrumbBarItem>().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;
Expand Down Expand Up @@ -166,6 +246,7 @@ private void ItemContainerGeneratorOnItemsChanged(object sender, ItemsChangedEve
UpdateLastContainer();
}

[ReactiveCommand]
private void OnTemplateButtonClick(object? obj)
{
if (obj is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}}"
Expand Down
49 changes: 49 additions & 0 deletions src/CrissCross.WPF.UI/Controls/BreadcrumbBar/BreadcrumbBarItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,55 @@ public class BreadcrumbBarItem : System.Windows.Controls.ContentControl
typeof(BreadcrumbBarItem),
new PropertyMetadata(false));

/// <summary>
/// The self property.
/// </summary>
public static readonly DependencyProperty SelfProperty =
DependencyProperty.Register(
nameof(Self),
typeof(BreadcrumbBarItem),
typeof(BreadcrumbBarItem),
new PropertyMetadata(null));

/// <summary>
/// The navigation type property.
/// </summary>
public static readonly DependencyProperty NavigationTypeProperty =
DependencyProperty.Register(
nameof(NavigationType),
typeof(Type),
typeof(BreadcrumbBarItem),
new PropertyMetadata(null));

/// <summary>
/// Initializes a new instance of the <see cref="BreadcrumbBarItem"/> class.
/// </summary>
public BreadcrumbBarItem() => Self = this;

/// <summary>
/// Gets or sets the type of the navigation.
/// </summary>
/// <value>
/// The type of the navigation.
/// </value>
public Type NavigationType
{
get => (Type)GetValue(NavigationTypeProperty);
set => SetValue(NavigationTypeProperty, value);
}

/// <summary>
/// Gets the self.
/// </summary>
/// <value>
/// The self.
/// </value>
public BreadcrumbBarItem Self
{
get => (BreadcrumbBarItem)GetValue(SelfProperty);
private set => SetValue(SelfProperty, value);
}

/// <summary>
/// Gets or sets displayed <see cref="IconElement"/>.
/// </summary>
Expand Down
Loading

0 comments on commit e2f7d68

Please sign in to comment.