XAMLで始めるWPF入門:EventToCommandでイベントをICommandに変換する方法

コンピュータ

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();
    }
}

実行イメージ

  1. 実行すると、ボタンとリストビューが表示されます。
    image
  2. アイテムを選択しボタンを押すと削除されます
    image
  3. 解説

    ボタンを押した際、発生するClickイベントをMainViewModelDeleteCommandへ変換することで削除処理を実装しています。

    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}" />
    • EventName … 監視するイベント名(例:Click)
    • Command … 実行する ICommand(VMのプロパティ)
    • CommandParameter … コマンドへ渡す値(任意)。書いた時だけ渡り、未設定なら null

コメント