Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a BreadcrumbBar Navigation integration #156

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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