WPFで ItemsControl を使ったパンくずリスト(Breadcrumb)

コンピュータ

WPFのItemsControlでパンくずリストをつくって見ます。

ソースコード

ファイル名:BreadcrumbDemo2.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

ファイル名:App.xaml.cs

using System.Configuration;
using System.Data;
using System.Windows;

namespace BreadcrumbDemo2;

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}


ファイル名:BreadcrumbItem.cs

// パンくずリストの各項目を表すクラス
public sealed class BreadcrumbItem
{
    public string Name { get; set; } = "";
    public string FullPath { get; set; } = "";
}

ファイル名:Helpers\RoutedCommandHelper.cs

using System.Windows;
using System.Windows.Input;

public static class RoutedCommandHelper
{
    public static RoutedUICommand Create(
        Window window,
        string? name,
        Action<ExecutedRoutedEventArgs> execute,
        Func<bool>? canExecute = null,
        Key? key = null,
        ModifierKeys modifiers = ModifierKeys.None)
    {
        var cmd = name == null
            ? new RoutedUICommand()
            : new RoutedUICommand(name, name, window.GetType());

        ExecutedRoutedEventHandler exec = (_, e) =>
            execute(e);

        CanExecuteRoutedEventHandler can = (_, e) =>
            e.CanExecute = canExecute?.Invoke() ?? true;

        window.CommandBindings.Add(
            new CommandBinding(cmd, exec, can));

        if (key != null)
        {
            window.InputBindings.Add(
                new KeyBinding(cmd, key.Value, modifiers));
        }

        return cmd;
    }
}

ファイル名:Helpers\ViewModelBase.cs

// 汎用的な ViewModel 基底クラス実装。
using System.ComponentModel;
using System.Runtime.CompilerServices;

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

ファイル名:MainViewState.cs


using System.Collections.ObjectModel;

namespace BreadcrumbDemo2;
public class MainViewState : ViewModelBase
{
    public ObservableCollection<BreadcrumbItem> Breadcrumbs { get; } = [];
}

ファイル名:MainWindow.xaml.cs

// コードビハインド
using System.Windows;
using System.Windows.Input;

namespace BreadcrumbDemo2;

public partial class MainWindow : Window
{
    public MainViewState State { get; } = new();
    public RoutedUICommand NavigateCommand { get; private set; }
    public MainWindow()
    {
        InitializeComponent();
        
        NavigateCommand = RoutedCommandHelper.Create(
            window: this,
            name: "Navigate",
            execute: Navigate);
        
        DataContext = this;

        this.Loaded += (sender, e) =>
        {
            this.SetPath("C:\\Users\\karet\\Documets");
        };
    }
    void Navigate(ExecutedRoutedEventArgs e)
    {
        var x = e.Parameter as BreadcrumbItem;
        if (x is null) return;
        
        this.SetPath(x.FullPath);
    }
    void SetPath(string path)
    {
        State.Breadcrumbs.Clear();

        // Windowsパス想定の簡易分解(最小サンプル)
        // 例: "C:\Users\karet" → ["C:", "Users", "karet"]
        var parts = path.Split('\\', StringSplitOptions.RemoveEmptyEntries);

        // 先頭が "C:" のようなドライブの場合を想定
        string current = "";
        for (int i = 0; i < parts.Length; i++)
        {
            var part = parts[i];

            if (i == 0 && part.EndsWith(":"))
            {
                current = part + "\\";
            }
            else
            {
                current = current.EndsWith("\\") ? current + part : current + "\\" + part;
            }

            State.Breadcrumbs.Add(new BreadcrumbItem
            {
                Name = part,
                FullPath = current
            });
        }        

    }
    
}

ファイル名:App.xaml

<Application x:Class="BreadcrumbDemo2.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:BreadcrumbDemo2"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>

ファイル名:MainWindow.xaml

<Window x:Class="BreadcrumbDemo2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BreadcrumbDemo2"
        mc:Ignorable="d"
        Title="BreadcrumbDemo" Height="450" Width="800">

    <Grid Margin="8">
        <ItemsControl
            ItemsSource="{Binding State.Breadcrumbs}"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Height="24">

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                    <!-- ItemsControlの項目を水平方向に配置 -->
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <!-- ItemsControlのItemを水平方向に配置 -->

                        <!-- 区切り -->
                        <TextBlock Text="  "
                            Margin="3,0"
                            VerticalAlignment="Center" />

                        <!-- クリック可能なパンくず要素 -->
                        <Button Content="{Binding Name}"
                                Padding="2,0"
                                Margin="0"
                                Background="Transparent"
                                BorderThickness="0"
                                Cursor="Hand"
                                VerticalAlignment="Center"
                                Command="{Binding DataContext.NavigateCommand,
                                                    RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                CommandParameter="{Binding}" />
                                <!-- Command ItemsControlをソースに指定 -->
                                <!-- CommandParameter BreadcrumbItemとバインド -->
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>                
        </ItemsControl>
    </Grid>
</Window>

実行例

スクリーンショット

ディレクトリ名がボタンになっているので、クリックすると変化します。

コメント