ファイルマネージャの作成のため、Explorerを真似たUIの操作をWPFで再現してみたいと思います。
F2で名前の変更
ファイル名:Converter\BoolToVisibilityConverter.cs
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ExplorerModoki;
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool b && b ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value is Visibility v && v == Visibility.Visible;
}
ファイル名:Converter\IInverseBoolToVisibilityConverter.cs
using System.Globalization;
namespace ExplorerModoki;
public interface IInverseBoolToVisibilityConverter
{
object Convert(object value, Type targetType, object parameter, CultureInfo culture);
object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
}
ファイル名:Converter\InverseBoolToVisibilityConverter.cs
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ExplorerModoki;
public class InverseBoolToVisibilityConverter : IValueConverter, IInverseBoolToVisibilityConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool b && b ? Visibility.Collapsed : Visibility.Visible;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value is Visibility v && v != Visibility.Visible;
}
ファイル名:ExplorerModoki.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>
ファイル名:FileItem.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExplorerModoki;
public class FileItem : INotifyPropertyChanged
{
string _name = "";
string _originalName = "";
bool _isEditing;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing == value) return;
_isEditing = value;
// 編集開始時に元の名前を保存
if (_isEditing)
_originalName = _name;
OnPropertyChanged();
}
}
public string OriginalName => _originalName;
public bool IsFolder { get; set; }
// 並び替え用
public long Size { get; set; }
public DateTime Modified { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ファイル名:MainWindow.xaml
<Window x:Class="ExplorerModoki.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:ExplorerModoki"
mc:Ignorable="d"
Title="ExplorerModoki" Height="450" Width="800">
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVis"/>
<local:InverseBoolToVisibilityConverter x:Key="InvBoolToVis"/>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
KeyDown="ListView_KeyDown">
<ListView.View>
<GridView>
<!-- 名前列(編集対応) -->
<GridViewColumn Header="Name" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<!-- 表示 -->
<TextBlock
Text="{Binding Name}"
Visibility="{Binding IsEditing,
Converter={StaticResource InvBoolToVis}}"/>
<!-- 編集 -->
<TextBox
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding IsEditing,
Converter={StaticResource BoolToVis}}"
IsVisibleChanged="RenameTextBox_IsVisibleChanged"
LostFocus="RenameTextBox_LostFocus"
KeyDown="RenameTextBox_KeyDown"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- サイズ列(表示のみ) -->
<GridViewColumn Header="Size" Width="80"
DisplayMemberBinding="{Binding Size}" />
<!-- 更新日時列 -->
<GridViewColumn Header="Modified" Width="140"
DisplayMemberBinding="{Binding Modified}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ファイル名:MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
namespace ExplorerModoki;
public partial class MainWindow : Window, INotifyPropertyChanged
{
// ファイルの一覧、ListViewのItemSource
public ObservableCollection<FileItem> Items {get; set;} = [];
// 選択中のItem、バインド元
FileItem? _selectedItem;
public FileItem? SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
// コンストラクタ
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
// ファイルの一覧取得(検証用)
void MainWindow_Loaded(object? sender, RoutedEventArgs e)
{
Items.Clear();
string path = @"C:\Users\karet\Pictures";
var dir = new DirectoryInfo(path);
// フォルダ
foreach (var d in dir.GetDirectories())
{
Items.Add(new FileItem
{
Name = d.Name,
IsFolder = true,
Size = 0,
Modified = d.LastWriteTime
});
}
// ファイル
foreach (var f in dir.GetFiles())
{
Items.Add(new FileItem
{
Name = f.Name,
IsFolder = false,
Size = f.Length,
Modified = f.LastWriteTime
});
}
}
// F2キーで編集モード
void ListView_KeyDown(object?senndr, KeyEventArgs e)
{
if (e.Key == Key.F2 && SelectedItem != null)
{
foreach (var item in Items)
item.IsEditing = false;
SelectedItem.IsEditing = true;
e.Handled = true;
}
}
// TextBox フォーカス
void RenameTextBox_IsVisibleChanged(object? sender, DependencyPropertyChangedEventArgs e)
{
if (sender is not TextBox tb) return;
if (!tb.IsVisible) return;
// フォーカス競合に勝つため Input 優先で遅延
tb.Dispatcher.BeginInvoke(new Action(() =>
{
tb.Focus();
Keyboard.Focus(tb);
tb.SelectAll();
}), DispatcherPriority.Input);
}
// Enter / Esc で編集終了
void RenameTextBox_KeyDown(object? sender, KeyEventArgs e)
{
if (sender is not TextBox tb) return;
if (tb.DataContext is not FileItem item) return;
if (e.Key == Key.Enter)
{
// 編集確定
CommitRename(item);
e.Handled = true;
}
else if (e.Key == Key.Escape)
{
// キャンセル
item.Name = item.OriginalName;
item.IsEditing = false;
e.Handled = true;
}
}
// フォーカスアウト
void RenameTextBox_LostFocus(object sender, RoutedEventArgs e)
{
if (sender is TextBox tb &&
tb.DataContext is FileItem item &&
item.IsEditing)
{
CommitRename(item);
}
}
// 名前変更コミット
void CommitRename(FileItem item)
{
item.IsEditing = false;
if (item.Name != item.OriginalName)
{
Debug.WriteLine($"Rename: {item.OriginalName} → {item.Name}");
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
キーボードの「F2」を押すと編集モードになり、「エンターキー」で確定

コンテキストメニュー
ファイル名:ExplorerModoki2.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>
ファイル名:FileItem.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExplorerModoki2;
public class FileItem : INotifyPropertyChanged
{
string _name = "";
string _originalName = "";
bool _isEditing;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing == value) return;
_isEditing = value;
// 編集開始時に元の名前を保存
if (_isEditing)
_originalName = _name;
OnPropertyChanged();
}
}
public string OriginalName => _originalName;
public bool IsFolder { get; set; }
// 並び替え用
public long Size { get; set; }
public DateTime Modified { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ファイル名:MainWindow.xaml
<Window x:Class="ExplorerModoki2.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:ExplorerModoki2"
mc:Ignorable="d"
Title="コンテキストメニュー" Height="450" Width="800">
<Window.Resources>
<!-- FileItem 用メニュー -->
<ContextMenu x:Key="FileItemMenu">
<MenuItem Header="名前の変更" Click="RenameMenu_Click"/>
<Separator/>
<MenuItem Header="削除" Click="DeleteMenu_Click"/>
</ContextMenu>
<!-- 空白用メニュー -->
<ContextMenu x:Key="BackgroundMenu">
<MenuItem Header="更新" Click="RefreshMenu_Click"/>
<MenuItem Header="並び替え">
<MenuItem Header="名前" Click="SortByName_Click"/>
<MenuItem Header="更新日時" Click="SortByModified_Click"/>
</MenuItem>
</ContextMenu>
</Window.Resources>
<Grid>
<ListView x:Name="ListView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ContextMenuOpening="ListView_ContextMenuOpening">
<ListView.View>
<GridView>
<!-- 名前列(編集対応) -->
<GridViewColumn Header="Name" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- サイズ列(表示のみ) -->
<GridViewColumn Header="Size" Width="80"
DisplayMemberBinding="{Binding Size}" />
<!-- 更新日時列 -->
<GridViewColumn Header="Modified" Width="140"
DisplayMemberBinding="{Binding Modified}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ファイル名:MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
namespace ExplorerModoki2;
public partial class MainWindow : Window, INotifyPropertyChanged
{
// ファイルの一覧、ListViewのItemSource
public ObservableCollection<FileItem> Items {get; set;} = [];
// 選択中のItem、バインド元
FileItem? _selectedItem;
public FileItem? SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
// コンストラクタ
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
// ファイルの一覧取得(検証用)
void MainWindow_Loaded(object? sender, RoutedEventArgs e)
{
Items.Clear();
string path = @"C:\Users\karet\Pictures";
var dir = new DirectoryInfo(path);
// フォルダ
foreach (var d in dir.GetDirectories())
{
Items.Add(new FileItem
{
Name = d.Name,
IsFolder = true,
Size = 0,
Modified = d.LastWriteTime
});
}
// ファイル
foreach (var f in dir.GetFiles())
{
Items.Add(new FileItem
{
Name = f.Name,
IsFolder = false,
Size = f.Length,
Modified = f.LastWriteTime
});
}
}
void ListView_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
if (sender is not ListView lv) return;
// 右クリック位置から ListViewItem を探す
var element = e.OriginalSource as DependencyObject;
var lvi = ItemsControl.ContainerFromElement(lv, element!) as ListViewItem;
if (lvi != null)
{
// FileItem 上
lv.SelectedItem = lvi.DataContext; // Explorer挙動:右クリックで選択移動
lv.ContextMenu = (ContextMenu)FindResource("FileItemMenu");
}
else
{
// 空白部分
lv.ContextMenu = (ContextMenu)FindResource("BackgroundMenu");
}
}
// 名前の変更
void RenameMenu_Click(object sender, RoutedEventArgs e)
{
Debug.Print($"変更:");
}
// 削除
void DeleteMenu_Click(object sender, RoutedEventArgs e)
{
Debug.Print($"削除:");
}
// 更新
void RefreshMenu_Click(object sender, RoutedEventArgs e)
{
Debug.Print($"更新(ダミー)");
}
// 名前でソート
void SortByName_Click(object sender, RoutedEventArgs e)
{
Debug.Print($"名前でソート");
}
// 更新日でソート
void SortByModified_Click(object sender, RoutedEventArgs e)
{
Debug.Print($"更新日でソート");
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ファイルItem上で右クリック→コンテキストメニュー
ファイルItem外で右クリック→コンテキストメニュー

領域によって異なるコンテキストメニューが表示される。
ヘッダークリックでソート
ファイル名:ExplorerModoki3.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>
ファイル名:FileItem.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExplorerModoki3;
public class FileItem : INotifyPropertyChanged
{
string _name = "";
string _originalName = "";
bool _isEditing;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing == value) return;
_isEditing = value;
// 編集開始時に元の名前を保存
if (_isEditing)
_originalName = _name;
OnPropertyChanged();
}
}
public string OriginalName => _originalName;
public bool IsFolder { get; set; }
// 並び替え用
public long Size { get; set; }
public DateTime Modified { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ファイル名:MainWindow.xaml
<Window x:Class="ExplorerModoki3.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:ExplorerModoki3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView x:Name="ListView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
GridViewColumnHeader.Click="GridViewColumnHeader_Click">
<ListView.View>
<GridView>
<GridViewColumn Width="200">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Name" Tag="Name"/>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding="{Binding Size}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Size" Tag="Size"/>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="140" DisplayMemberBinding="{Binding Modified}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Modified" Tag="Modified"/>
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ファイル名:MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace ExplorerModoki3;
public partial class MainWindow : Window, INotifyPropertyChanged
{
// ソートの状態
string? _currentSortProperty;
ListSortDirection _currentSortDirection = ListSortDirection.Ascending;
// ファイルの一覧、ListViewのItemSource
public ObservableCollection<FileItem> Items {get; set;} = [];
// 選択中のItem、バインド元
FileItem? _selectedItem;
public FileItem? SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
// コンストラクタ
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
// ファイルの一覧取得(検証用)
void MainWindow_Loaded(object? sender, RoutedEventArgs e)
{
Items.Clear();
string path = @"C:\Users\karet\Pictures";
var dir = new DirectoryInfo(path);
// フォルダ
foreach (var d in dir.GetDirectories())
{
Items.Add(new FileItem
{
Name = d.Name,
IsFolder = true,
Size = 0,
Modified = d.LastWriteTime
});
}
// ファイル
foreach (var f in dir.GetFiles())
{
Items.Add(new FileItem
{
Name = f.Name,
IsFolder = false,
Size = f.Length,
Modified = f.LastWriteTime
});
}
}
// ヘッダークリック
void GridViewColumnHeader_Click(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is not GridViewColumnHeader header)
return;
if (header.Tag is not string propertyName)
return;
// 同じ列 → 昇降順トグル
if (_currentSortProperty == propertyName)
{
_currentSortDirection =
_currentSortDirection == ListSortDirection.Ascending
? ListSortDirection.Descending
: ListSortDirection.Ascending;
}
else
{
_currentSortProperty = propertyName;
_currentSortDirection = ListSortDirection.Ascending;
}
ApplySort();
}
// ソート本体
void ApplySort()
{
var view = CollectionViewSource.GetDefaultView(Items);
view.SortDescriptions.Clear();
// ★ Explorer 風:フォルダ優先
view.SortDescriptions.Add(
new SortDescription(nameof(FileItem.IsFolder),
ListSortDirection.Descending));
// ★ 選択列でソート
view.SortDescriptions.Add(
new SortDescription(_currentSortProperty!,
_currentSortDirection));
view.Refresh();
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ヘッダーの「Size」をクリック
「Size」でソートされる。

Deleteキーで削除
ファイル名:ExplorerModoki4.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>
ファイル名:FileItem.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExplorerModoki4;
public class FileItem : INotifyPropertyChanged
{
string _name = "";
string _originalName = "";
bool _isEditing;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing == value) return;
_isEditing = value;
// 編集開始時に元の名前を保存
if (_isEditing)
_originalName = _name;
OnPropertyChanged();
}
}
public string OriginalName => _originalName;
public bool IsFolder { get; set; }
// 並び替え用
public long Size { get; set; }
public DateTime Modified { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ファイル名:MainWindow.xaml
<Window x:Class="ExplorerModoki4.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:ExplorerModoki4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView x:Name="ListView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
KeyDown="ListView_KeyDown">
<ListView.View>
<GridView>
<GridViewColumn Width="200">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Name" Tag="Name"/>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding="{Binding Size}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Size" Tag="Size"/>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="140" DisplayMemberBinding="{Binding Modified}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Modified" Tag="Modified"/>
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ファイル名:MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace ExplorerModoki4;
public partial class MainWindow : Window, INotifyPropertyChanged
{
// ファイルの一覧、ListViewのItemSource
public ObservableCollection<FileItem> Items {get; set;} = [];
// 選択中のItem、バインド元
FileItem? _selectedItem;
public FileItem? SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
// コンストラクタ
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
// ファイルの一覧取得(検証用)
void MainWindow_Loaded(object? sender, RoutedEventArgs e)
{
Items.Clear();
string path = @"C:\Users\karet\Pictures";
var dir = new DirectoryInfo(path);
// フォルダ
foreach (var d in dir.GetDirectories())
{
Items.Add(new FileItem
{
Name = d.Name,
IsFolder = true,
Size = 0,
Modified = d.LastWriteTime
});
}
// ファイル
foreach (var f in dir.GetFiles())
{
Items.Add(new FileItem
{
Name = f.Name,
IsFolder = false,
Size = f.Length,
Modified = f.LastWriteTime
});
}
}
// Deleteキーで削除
void ListView_KeyDown(object? sender, KeyEventArgs e)
{
if (e.Key != Key.Delete)
return;
// Rename 中は削除しない(Explorer挙動)
if (Items.Any(x => x.IsEditing))
return;
if (SelectedItem == null)
return;
// ダミー削除(UIのみ)
Items.Remove(SelectedItem);
e.Handled = true;
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
「desktop.ini」を選択した状態で、キーボードの「Delete」キーを押す。

「deskitop.ini」が削除される。

キーボード入力ジャンプ
ファイル名:ExplorerModoki4.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>
ファイル名:FileItem.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ExplorerModoki4;
public class FileItem : INotifyPropertyChanged
{
string _name = "";
string _originalName = "";
bool _isEditing;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public bool IsEditing
{
get => _isEditing;
set
{
if (_isEditing == value) return;
_isEditing = value;
// 編集開始時に元の名前を保存
if (_isEditing)
_originalName = _name;
OnPropertyChanged();
}
}
public string OriginalName => _originalName;
public bool IsFolder { get; set; }
// 並び替え用
public long Size { get; set; }
public DateTime Modified { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
ファイル名:MainWindow.xaml
<Window x:Class="ExplorerModoki4.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:ExplorerModoki4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView x:Name="ListView"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
PreviewTextInput="ListView_PreviewTextInput">
<ListView.View>
<GridView>
<GridViewColumn Width="200">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Name" Tag="Name"/>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding="{Binding Size}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Size" Tag="Size"/>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="140" DisplayMemberBinding="{Binding Modified}">
<GridViewColumn.Header>
<GridViewColumnHeader Content="Modified" Tag="Modified"/>
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ファイル名:MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace ExplorerModoki4;
public partial class MainWindow : Window, INotifyPropertyChanged
{
// キー入力のディレイ
string _typeBuffer = "";
DateTime _lastTypeTime = DateTime.MinValue;
const int TypeTimeoutMs = 700;
// ファイルの一覧、ListViewのItemSource
public ObservableCollection<FileItem> Items {get; set;} = [];
// 選択中のItem、バインド元
FileItem? _selectedItem;
public FileItem? SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged();
}
}
}
// コンストラクタ
public MainWindow()
{
InitializeComponent();
DataContext = this;
this.Loaded += MainWindow_Loaded;
}
// ファイルの一覧取得(検証用)
void MainWindow_Loaded(object? sender, RoutedEventArgs e)
{
Items.Clear();
string path = @"C:\Users\karet\Pictures";
var dir = new DirectoryInfo(path);
// フォルダ
foreach (var d in dir.GetDirectories())
{
Items.Add(new FileItem
{
Name = d.Name,
IsFolder = true,
Size = 0,
Modified = d.LastWriteTime
});
}
// ファイル
foreach (var f in dir.GetFiles())
{
Items.Add(new FileItem
{
Name = f.Name,
IsFolder = false,
Size = f.Length,
Modified = f.LastWriteTime
});
}
}
void ListView_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
// Rename 中は TextBox に任せる
if (Items.Any(x => x.IsEditing))
return;
var now = DateTime.Now;
// 一定時間空いたらリセット
if ((now - _lastTypeTime).TotalMilliseconds > TypeTimeoutMs)
_typeBuffer = "";
_lastTypeTime = now;
_typeBuffer += e.Text;
// Explorer 風:フォルダ優先 + 名前順で検索
var view = CollectionViewSource.GetDefaultView(Items);
var match = view.Cast<FileItem>()
.FirstOrDefault(x =>
x.Name.StartsWith(
_typeBuffer,
StringComparison.OrdinalIgnoreCase));
if (match != null)
{
SelectedItem = match;
ListView.ScrollIntoView(match);
}
e.Handled = true;
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
1行目選択→キーボードの「D」を押す

「desktop.ini」へジャンプする。

さいごに
思いつく機能を施策してみましたが、
これらを一つのプロジェクトにまとめて、
きちんと機能するかは後日挑戦したいと思います。

コメント