Skip to content
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
1 change: 1 addition & 0 deletions src/Wpf.Ui.Gallery/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public partial class App : Application
services.AddTransient<WebBrowserViewModel>();

// Navigation
services.AddTransient<BreadcrumbBarPage>();
services.AddTransient<NavigationPage>();
services.AddTransient<NavigationViewModel>();
services.AddTransient<NavigationViewPage>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using Wpf.Ui.Common;
using Wpf.Ui.Gallery.Models;

namespace Wpf.Ui.Gallery.ViewModels.Pages.Navigation;
Expand All @@ -16,6 +13,13 @@ public partial class NavigationViewModel : ObservableObject
[ObservableProperty]
private ICollection<NavigationCard> _navigationCards = new ObservableCollection<NavigationCard>
{
new()
{
Name = "BreadcrumbBar",
Icon = SymbolRegular.Navigation24,
Description = "Shows the trail of navigation taken to the current location.",
Link = "BreadcrumbBar"
},
new()
{
Name = "NavigationView",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public MainWindowViewModel(IServiceProvider serviceProvider)
}},
new NavigationViewItem {Content = "Navigation", Icon = new SymbolIcon { Symbol = SymbolRegular.Navigation24 }, TargetPageType = typeof(NavigationPage), MenuItems = new ObservableCollection<object>
{
new NavigationViewItem { Content = "BreadcrumbBar", TargetPageType = typeof(BreadcrumbBarPage) },
new NavigationViewItem { Content = "NavigationView", TargetPageType = typeof(NavigationViewPage) },
new NavigationViewItem { Content = "TabControl", TargetPageType = typeof(TabControlPage) },
}},
Expand Down
89 changes: 89 additions & 0 deletions src/Wpf.Ui.Gallery/Views/Pages/Navigation/BreadcrumbBarPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<Page
x:Class="Wpf.Ui.Gallery.Views.Pages.Navigation.BreadcrumbBarPage"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:Wpf.Ui.Gallery.Views.Pages.Navigation"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
Title="BreadcrumbBar"
d:DataContext="{d:DesignInstance navigation:BreadcrumbBarPage,
IsDesignTimeCreatable=True}"
d:DesignHeight="450"
d:DesignWidth="850"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
WindowWidth="850"
mc:Ignorable="d">

<Page.Resources>
<ResourceDictionary>
<system:String x:Key="PageXamlUrl">https://github.com/lepoco/wpfui/blob/development/src/Wpf.Ui/Styles/Controls/BreadcrumbBar.xaml</system:String>
<system:String x:Key="PageCsharpUrl">https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/BreadcrumbBar.cs</system:String>

<Style BasedOn="{StaticResource {x:Type ui:BreadcrumbBarItem}}" TargetType="{x:Type ui:BreadcrumbBarItem}">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="SymbolIconFontSize" Value="10" />
<Setter Property="SymbolIconFontWeight" Value="Regular" />
</Style>
</ResourceDictionary>
</Page.Resources>


<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:DynamicScrollViewer
x:Name="PageScrollViewer"
Grid.RowSpan="2"
Padding="42">

<StackPanel>
<controls:GalleryControlPresenter CodeText="&lt;ui:BreadcrumbBar /&gt;" HeaderText="A BreadcrumbBar control">
<controls:GalleryControlPresenter.Content>
<ui:BreadcrumbBar ItemsSource="{Binding Strings, Mode=OneTime}" />
</controls:GalleryControlPresenter.Content>
</controls:GalleryControlPresenter>

<controls:GalleryControlPresenter Margin="0,42,0,0" HeaderText="BreadcrumbBar Control with Custom DataTemplate">
<controls:GalleryControlPresenter.Content>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<ui:BreadcrumbBar
x:Name="BreadcrumbBar2"
Grid.Column="0"
ItemsSource="{Binding Folders, Mode=OneTime}">
<ui:BreadcrumbBar.ItemTemplate>
<DataTemplate DataType="{x:Type navigation:Folder}">
<TextBlock Text="{Binding Name, Mode=OneTime}" />
</DataTemplate>
</ui:BreadcrumbBar.ItemTemplate>
</ui:BreadcrumbBar>


<Button
Grid.Column="1"
Click="ResetButton_OnClick"
Content="Reset" />
</Grid>
</controls:GalleryControlPresenter.Content>
</controls:GalleryControlPresenter>
</StackPanel>
</ui:DynamicScrollViewer>
<controls:ControlDocumentationSummary
Grid.RowSpan="2"
Grid.Column="1"
CsharpUrl="{StaticResource PageCsharpUrl}"
XamlUrl="{StaticResource PageXamlUrl}" />
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections.ObjectModel;
using System.Windows;
using Wpf.Ui.Controls;

namespace Wpf.Ui.Gallery.Views.Pages.Navigation;

public partial class BreadcrumbBarPage
{
public BreadcrumbBarPage()
{
InitializeComponent();
DataContext = this;

_folders = new Folder[]
{
new("Home"),
new("Folder1"),
new("Folder2"),
new("Folder3"),
};

Strings = new[]
{
"Home",
"Document",
"Design",
"Northwind",
"Images",
"Folder1",
"Folder2",
"Folder3"
};

Folders = new ObservableCollection<Folder>(_folders);

Loaded += OnLoaded;
Unloaded += OnUnloaded;
}

private readonly Folder[] _folders;

public string[] Strings { get; }
public ObservableCollection<Folder> Folders { get; }

private void OnLoaded(object sender, RoutedEventArgs e)
{
BreadcrumbBar2.ItemClicked += BreadcrumbBar2OnItemClicked;
}

private void OnUnloaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded;
Unloaded -= OnUnloaded;

BreadcrumbBar2.ItemClicked -= BreadcrumbBar2OnItemClicked;
}

private void BreadcrumbBar2OnItemClicked(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs e)
{
Folders.RemoveAt(e.Index + 1);
}

private void ResetButton_OnClick(object sender, RoutedEventArgs e)
{
Folders.Clear();

foreach (var folder in _folders)
{
Folders.Add(folder);
}
}
}

public record Folder(string Name);
30 changes: 30 additions & 0 deletions src/Wpf.Ui/Common/BreadcrumbBarEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Windows;
using Wpf.Ui.Controls;
using System.Diagnostics.CodeAnalysis;


namespace Wpf.Ui.Common;

/// <summary>
/// Event triggered on via <see cref="BreadcrumbBar"/>.
/// </summary>
/// <param name="sender">Current <see cref="BreadcrumbBar"/> instance.</param>
public delegate void BreadcrumbBarItemClickedEvent(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs e);

public sealed class BreadcrumbBarItemClickedEventArgs : RoutedEventArgs
{
public BreadcrumbBarItemClickedEventArgs(RoutedEvent routedEvent, object source, object item, int index) : base(routedEvent, source)
{
Item = item;
Index = index;
}

/// <summary>
/// Gets the Content property value of the BreadcrumbBarItem that is clicked.
/// </summary>
public object Item { get; }
/// <summary>
/// Gets the index of the item that was clicked.
/// </summary>
public int Index { get; }
}
126 changes: 126 additions & 0 deletions src/Wpf.Ui/Controls/BreadcrumbBar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls.Primitives;
using Wpf.Ui.Common;

namespace Wpf.Ui.Controls;

[StyleTypedProperty(Property = nameof(ItemContainerStyle), StyleTargetType = typeof(BreadcrumbBarItem))]
public class BreadcrumbBar : System.Windows.Controls.ItemsControl
{
/// <summary>
/// Property for <see cref="TemplateButtonCommand"/>.
/// </summary>
public static readonly DependencyProperty TemplateButtonCommandProperty =
DependencyProperty.Register(nameof(TemplateButtonCommand), typeof(IRelayCommand), typeof(InfoBar),
new PropertyMetadata(null));

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

/// <summary>
/// Property for <see cref="ItemClicked"/>.
/// </summary>
public static readonly RoutedEvent ItemClickedRoutedEvent = EventManager.RegisterRoutedEvent(nameof(ItemClicked),
RoutingStrategy.Bubble, typeof(BreadcrumbBarItemClickedEvent), typeof(BreadcrumbBar));

/// <summary>
/// Occurs when an item is clicked in the <see cref="BreadcrumbBar"/>.
/// </summary>
public event BreadcrumbBarItemClickedEvent ItemClicked
{
add => AddHandler(ItemClickedRoutedEvent, value);
remove => RemoveHandler(ItemClickedRoutedEvent, value);
}

public BreadcrumbBar()
{
SetValue(TemplateButtonCommandProperty, new RelayCommand<object>(OnTemplateButtonClick));

Loaded += OnLoaded;
Unloaded += OnUnloaded;
}

protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is BreadcrumbBarItem;
}

protected override DependencyObject GetContainerForItemOverride()
{
return new BreadcrumbBarItem();
}

private void OnLoaded(object sender, RoutedEventArgs e)
{
ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorOnItemsChanged;
ItemContainerGenerator.StatusChanged += ItemContainerGeneratorOnStatusChanged;

UpdateLastContainer();
}

private void OnUnloaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded;
Unloaded -= OnUnloaded;

ItemContainerGenerator.ItemsChanged -= ItemContainerGeneratorOnItemsChanged;
ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorOnStatusChanged;
}

private void ItemContainerGeneratorOnStatusChanged(object? sender, EventArgs e)
{
if (ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;

if (ItemContainerGenerator.Items.Count <= 1)
{
UpdateLastContainer();
return;
}

InteractWithItemContainer(2, static item => item.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("Item content is null");

var container = ItemContainerGenerator.ContainerFromItem(obj);
var index = ItemContainerGenerator.IndexFromContainer(container);

OnItemClicked(obj, index);
}

private void InteractWithItemContainer(int offsetFromEnd, Action<BreadcrumbBarItem> action)
{
if (ItemContainerGenerator.Items.Count <= 0)
return;

var item = ItemContainerGenerator.Items[ItemContainerGenerator.Items.Count - offsetFromEnd];
var container = (BreadcrumbBarItem) ItemContainerGenerator.ContainerFromItem(item);

action.Invoke(container);
}

private void UpdateLastContainer() => InteractWithItemContainer(1, static item => item.IsLast = true);
}
Loading