EventToCommandを使い、特定のコントロールで発生するイベントをViewModelのICommandプロパティを呼び出します。
サンプルコード
ファイル名:Helpers\EventToCommand.cs
// EventToCommand.cs
using System.Reflection;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
namespace eventToCommandSample;
public static class EventToCommand
{
public static readonly DependencyProperty EventNameProperty =
DependencyProperty.RegisterAttached("EventName", typeof(string), typeof(EventToCommand),
new PropertyMetadata(null, OnEventNameChanged));
public static void SetEventName(DependencyObject o, string v) => o.SetValue(EventNameProperty, v);
public static string GetEventName(DependencyObject o) => (string)o.GetValue(EventNameProperty);
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EventToCommand));
public static void SetCommand(DependencyObject o, ICommand v) => o.SetValue(CommandProperty, v);
public static ICommand GetCommand(DependencyObject o) => (ICommand)o.GetValue(CommandProperty);
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(EventToCommand));
public static void SetCommandParameter(DependencyObject o, object v) => o.SetValue(CommandParameterProperty, v);
public static object GetCommandParameter(DependencyObject o) => o.GetValue(CommandParameterProperty);
private static void OnEventNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not FrameworkElement fe) return;
var name = e.NewValue as string; if (string.IsNullOrWhiteSpace(name)) return;
var routed = typeof(UIElement).GetField(name + "Event", BindingFlags.Static | BindingFlags.Public)
?? fe.GetType().GetField(name + "Event", BindingFlags.Static | BindingFlags.Public);
if (routed?.GetValue(null) is RoutedEvent re)
{
fe.AddHandler(re, new RoutedEventHandler((s, ev) => Execute(fe)));
return;
}
var ev = fe.GetType().GetEvent(name, BindingFlags.Instance | BindingFlags.Public)
?? throw new ArgumentException($"Event '{name}' not found on {fe.GetType().Name}");
EventHandler handler = (s, e) => Execute(fe);
var del = Delegate.CreateDelegate(ev.EventHandlerType!, handler.Target!, handler.Method);
ev.AddEventHandler(fe, del);
fe.Unloaded += (_, __) => ev.RemoveEventHandler(fe, del);
}
private static void Execute(FrameworkElement fe)
{
var cmd = GetCommand(fe); if (cmd is null) return;
// CommandParameter がバインド/設定されていればそれを使う。UnsetValue は null 扱い。
var hasLocal = fe.ReadLocalValue(CommandParameterProperty) != DependencyProperty.UnsetValue;
var hasBind = BindingOperations.IsDataBound(fe, CommandParameterProperty);
var param = (hasLocal || hasBind) ? GetCommandParameter(fe) : null;
if (ReferenceEquals(param, DependencyProperty.UnsetValue)) param = null;
if (cmd.CanExecute(param)) cmd.Execute(param);
}
}
ファイル名:Helpers\RelayCommand.cs
// RelayCommand.cs
using System.Windows.Input;
namespace eventToCommandSample;
public sealed class RelayCommand : ICommand
{
private readonly Action<object?> _run; private readonly Predicate<object?>? _can;
public RelayCommand(Action<object?> run, Predicate<object?>? can = null){ _run = run; _can = can; }
public bool CanExecute(object? p) => _can?.Invoke(p) ?? true;
public void Execute(object? p) => _run(p);
public event EventHandler? CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }
}
ファイル名:ViewModels\MainViewModel.cs
// ViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace eventToCommandSample;
public class MainViewModel
{
// リストビューの内容
public ObservableCollection<string> Items { get; } =
new() { "Apple", "Banana", "Cherry" };
// リストビューの選択中の要素の内容
public string? Selected { get; set; }
// リストビューの要素の削除コマンド
public ICommand DeleteCommand => new RelayCommand(_ =>
{
if (Selected is not null) Items.Remove(Selected);
}, _ => Selected is not null);
}
ファイル名:MainWindow.xaml
<Window x:Class="eventToCommandSample.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:eventToCommandSample"
mc:Ignorable="d"
Title="EventToCommand Sample" Width="360" Height="260">
<DockPanel Margin="12">
<Button DockPanel.Dock="Top" Content="Delete selected"
local:EventToCommand.EventName="Click"
local:EventToCommand.Command="{Binding DeleteCommand}"
local:EventToCommand.CommandParameter="{Binding Selected}" />
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding Selected, Mode=TwoWay}" />
</DockPanel>
</Window>
ファイル名:MainWindow.xaml.cs
using System.Windows;
namespace eventToCommandSample;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// DataContenxtにViewModelを設定
DataContext = new MainViewModel();
}
}
実行イメージ
- 実行すると、ボタンとリストビューが表示されます。
- アイテムを選択しボタンを押すと削除されます
- EventName … 監視するイベント名(例:Click)
- Command … 実行する ICommand(VMのプロパティ)
- CommandParameter … コマンドへ渡す値(任意)。書いた時だけ渡り、未設定なら null
解説
ボタンを押した際、発生するClick
イベントをMainViewModel
のDeleteCommand
へ変換することで削除処理を実装しています。
XAML抜粋
<Window x:Class="eventToCommandSample.MainWindow"<Button DockPanel.Dock="Top" Content="Delete selected"
local:EventToCommand.EventName="Click"
local:EventToCommand.Command="{Binding DeleteCommand}"
local:EventToCommand.CommandParameter="{Binding Selected}" />
コメント