エクスプローラーF2キーでのファイル名が編集が出来るますが、ListViewで同じことができないか試行錯誤しています。
プロジェクトの作成
ソースコード
ファイル名:BooleanToVisibilityConverter.cs
using System.Windows;
using System.Windows.Data;
namespace ListItemEdit01;
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visibility = (bool)value;
string? parameterString = parameter as string;
bool reverse = (parameterString != null && parameterString.ToLower() == "collapsed");
return (visibility ^ reverse) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value is Visibility.Visible;
}
}
boolをVisibilityへ変換するコンバータ。
ファイル名:FocusBehavior.cs
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
namespace ListItemEdit01;
public static class FocusBehavior
{
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached(
"IsFocused",
typeof(bool),
typeof(FocusBehavior),
new PropertyMetadata(false, OnIsFocusedChanged));
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
private static void OnIsFocusedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox textBox && (bool)e.NewValue)
{
// Dispatcher.BeginInvoke を使用して、UI スレッドで非同期的にフォーカスを設定
textBox.Dispatcher.BeginInvoke((Action)(() =>
{
textBox.Focus();
//Keyboard.Focus(textBox); // 必要に応じてキーボードフォーカスも設定
//SetIsFocused(textBox, false); // フォーカス移動後にリセット
}), DispatcherPriority.Loaded);
}
}
}
編集用のテキストボックスにフォーカスを移動するためのビヘイビア。
ファイル名:ItemViewModel.cs
using System.ComponentModel;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Disposables;
using System.Diagnostics;
namespace ListItemEdit01;
public class ItemViewModel : INotifyPropertyChanged, IDisposable
{
#region
// INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
// IDisposable
private CompositeDisposable Disposable { get; } = [];
public void Dispose() => Disposable.Dispose();
#endregion
public ReactiveProperty<string> Name {get; set;} = new("");
public ReactiveProperty<bool> IsEditing {get; set;} = new(false);
public ItemViewModel()
{
Name.AddTo(Disposable);
IsEditing.AddTo(Disposable);
}
}
こちらがListViewItemにバインドされます。
ファイル名:MainWindow.xaml
<Window x:Class="ListItemEdit01.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:ListItemEdit01"
mc:Ignorable="d"
Title="Title"
Height="450"
Width="800"
xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors">
<Window.DataContext>
<local:MainWindowViewModel x:Name="Root"/>
</Window.DataContext>
<Window.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<i:Interaction.Behaviors>
<local:ViewModelCleanupBehavior />
</i:Interaction.Behaviors>
<Grid>
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem.Value}">
<ListView.InputBindings>
<KeyBinding Key="F2" Command="{Binding ElementName=Root, Path=EditItemCommand}" />
<KeyBinding Key="Esc" Command="{Binding ElementName=Root, Path=LostFocusCommand}" />
</ListView.InputBindings>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<interactivity:EventToReactiveCommand Command="{Binding EditItemCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name.Value}" Visibility="{Binding IsEditing.Value, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}">
</TextBlock>
<TextBox Text="{Binding Name.Value, UpdateSourceTrigger=LostFocus}" local:FocusBehavior.IsFocused="{Binding IsEditing.Value}" Visibility="{Binding IsEditing.Value, Converter={StaticResource BooleanToVisibilityConverter}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<interactivity:EventToReactiveCommand Command="{Binding ElementName=Root, Path=LostFocusCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ElementName=Root, Path=LostFocusCommand}" />
</TextBox.InputBindings>
</TextBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
MainWindowのViewになります。
ファイル名:MainWindowViewModel.cs
using System.Diagnostics;
using System.ComponentModel;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.Reactive.Disposables;
using System.Windows;
namespace ListItemEdit01;
public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
{
#region
// INotifyPropertyChanged
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
// IDisposable
private CompositeDisposable Disposable { get; } = [];
public void Dispose() => Disposable.Dispose();
#endregion
/**************************************************************************
* プロパティ
**************************************************************************/
public ReactiveCollection<ItemViewModel> Items { get; set; } = [];
public ReactiveProperty<ItemViewModel> SelectedItem { get; set; } = new();
ItemViewModel? PreviousSelectItem;
public ReactiveCommand<EventArgs> EditItemCommand { get; } = new();
public ReactiveCommand<EventArgs> LostFocusCommand { get; } = new();
public MainWindowViewModel()
{
Items.AddTo(this.Disposable);
Items.AddOnScheduler(new() { Name = new("1アイテム"), });
Items.AddOnScheduler(new() { Name = new("Bアイテム"), });
Items.AddOnScheduler(new() { Name = new("Eアイテム"), });
Items.AddOnScheduler(new() { Name = new("Fアイテム"), });
Items.AddOnScheduler(new() { Name = new("Sアイテム"), });
Items.AddOnScheduler(new() { Name = new("Xアイテム"), });
EditItemCommand.Subscribe(e=>
{
if (SelectedItem is null) return;
SelectedItem.Value.IsEditing.Value = true;
}).AddTo(Disposable);
LostFocusCommand.Subscribe(e=>
{
if (SelectedItem is null) return;
SelectedItem.Value.IsEditing.Value = false;
}).AddTo(Disposable);
SelectedItem.Subscribe(e=>
{
if (PreviousSelectItem is not null && PreviousSelectItem.IsEditing.Value == true)
{
PreviousSelectItem.IsEditing.Value = false;
}
PreviousSelectItem = e;
}).AddTo(Disposable);
}
}
MainWindowのデータコンテキスト
ファイル名:ViewModelCleanupBehavior.cs
using Microsoft.Xaml.Behaviors;
using System.Windows;
namespace ListItemEdit01;
public class ViewModelCleanupBehavior : Behavior<Window>
{
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Closed += this.WindowClosed;
}
private void WindowClosed(object? sender, EventArgs e)
{
(this.AssociatedObject.DataContext as IDisposable)?.Dispose();
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.Closed -= this.WindowClosed;
}
}
ウィンドウを閉じる(アプリの終了)際、オブジェクトを一括Dispose()呼び出し用
実行
dotnet run
起動するとリストビューが表示される。
F2キーを押すと編集モードになります。
編集変更後エンターキーを押す。
キー割当
- F2
- 編集モード
- Esc
- 編集モード解除
- ダブルクリック
- 編集モード
- Enter
- 編集モード解除
Escはキャンセルではなく単純に編集モードの終了になっています。
コメント