【WPF】KeyBinding with TriggerAction

追記。これ、このままじゃData Binding動かへん。書き直します。。


KeyBindingで実行対象がCommandなら別になんてことないんですけどね。
TriggerAction使う場合ね。


いつも通りおもむろに以下を作りましょう。

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

namespace Hoge.Action
{
    public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
    {
        public static readonly DependencyProperty InputBindingProperty =
            DependencyProperty.Register("InputBinding", typeof(InputBinding) , typeof(InputBindingTrigger) , new UIPropertyMetadata(null));

        public InputBinding InputBinding
        {
            get => (InputBinding)GetValue(InputBindingProperty);
            set => SetValue(InputBindingProperty, value);
        }

        public event EventHandler CanExecuteChanged = delegate { };

        protected override void OnAttached()
        {
            if (InputBinding != null)
            {
                InputBinding.Command = this;
                AssociatedObject.InputBindings.Add(InputBinding);
            }
            base.OnAttached();
        }

        public bool CanExecute(object parameter) => true;

        public void Execute(object parameter) => InvokeActions(parameter);
    }
}



使い方

こんなTriggerActionが用意してあります。フォルダ選択ダイアログですね。

namespace Hoge.Action
{
    /// <summary>
    /// FolderBrowserDialog Confirmation
    /// </summary>
    public sealed class FolderBrowserDialogConfirmation : Confirmation
    {
        public Boolean? ShowNewFolderButton { get; set; }
        public String SelectedPath { get; set; }
        public Environment.SpecialFolder? RootFolder { get; set; }
        public String Description { get; set; }
        public Object Tag { get; set; }
    }

    /// <summary>
    /// FolderBrowserDialog表示アクション
    /// </summary>
    public sealed partial class ShowFolderBrowserDialogAction
    {
        public Boolean? ShowNewFolderButton
        {
            get => (Boolean?)GetValue(ShowNewFolderButtonProperty);
            set => SetValue(ShowNewFolderButtonProperty, value);
        }
        private static readonly DependencyProperty ShowNewFolderButtonProperty = DependencyProperty.Register("ShowNewFolderButton", typeof(Boolean?), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public String SelectedPath
        {
            get => (String)GetValue(SelectedPathProperty);
            set => SetValue(SelectedPathProperty, value);
        }
        private static readonly DependencyProperty SelectedPathProperty = DependencyProperty.Register("SelectedPath", typeof(String), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public Environment.SpecialFolder? RootFolder
        {
            get => (Environment.SpecialFolder)GetValue(RootFolderProperty);
            set => SetValue(RootFolderProperty, value);
        }
        private static readonly DependencyProperty RootFolderProperty = DependencyProperty.Register("RootFolder", typeof(Environment.SpecialFolder), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public String Description
        {
            get => (String)GetValue(DescriptionProperty);
            set => SetValue(DescriptionProperty, value);
        }
        private static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));

        public Object Tag
        {
            get => GetValue(TagProperty);
            set => SetValue(TagProperty, value);
        }
        private static readonly DependencyProperty TagProperty = DependencyProperty.Register("Tag", typeof(Object), typeof(ShowFolderBrowserDialogAction), new PropertyMetadata(null));
    }

    public sealed partial class ShowFolderBrowserDialogAction : TriggerAction<DependencyObject>
    {
        private static readonly FolderBrowserDialogConfirmation NullObject = new FolderBrowserDialogConfirmation();

        protected override void Invoke(object parameter)
        {
            var param = parameter as InteractionRequestedEventArgs;
            var confirmation = param == null ? NullObject : param.Context as FolderBrowserDialogConfirmation;

            var dialog = GenerateDialog(confirmation);
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                confirmation.Confirmed = true;
                ApplyDialogPropertyValues(confirmation, dialog);
            }
            else
            {
                confirmation.Confirmed = false;
            }

            param?.Callback();
        }

        private FolderBrowserDialog GenerateDialog(FolderBrowserDialogConfirmation confirmation)
        {
            var dlg = new FolderBrowserDialog();
            dlg.ShowNewFolderButton = confirmation.ShowNewFolderButton ?? ShowNewFolderButton ?? dlg.ShowNewFolderButton;
            dlg.RootFolder = confirmation.RootFolder ?? RootFolder ?? dlg.RootFolder;
            dlg.Description = confirmation.Description ?? Description ?? dlg.Description;
            dlg.Tag = confirmation.Tag ?? Tag ?? dlg.Tag;
            SelectedPath = string.Empty;
            return dlg;
        }

        private void ApplyDialogPropertyValues(FolderBrowserDialogConfirmation n, FolderBrowserDialog dlg)
        {
            if (n != NullObject)
            {
                n.ShowNewFolderButton = dlg.ShowNewFolderButton;
                n.SelectedPath = dlg.SelectedPath;
                n.RootFolder = dlg.RootFolder;
                n.Description = dlg.Description;
                n.Tag = dlg.Tag;
            }

            ShowNewFolderButton = dlg.ShowNewFolderButton;
            SelectedPath = dlg.SelectedPath;
            RootFolder = dlg.RootFolder;
            Description = dlg.Description;
            Tag = dlg.Tag;
        }
    }
}


んで、xaml。コードビハインドとViewModelは省略。ReactiveProperty使ってます。

<Window x:Class="Hoge.MainWindow"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:action="clr-namespace:HogeAction"
                 mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <i:Interaction.Triggers>
            <action:InputBindingTrigger>
                <action:InputBindingTrigger.InputBinding>
                    <KeyBinding Modifiers="Ctrl" Key="N"/>
                </action:InputBindingTrigger.InputBinding>
                <action:ShowFolderBrowserDialogAction Description="フォルダを選択してください"
                                                                                   SelectedPath="{Binding SelectedPath.Value, Mode=OneWayToSource}" />
            </action:InputBindingTrigger>
        </i:Interaction.Triggers>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Menu Grid.Row="0" >
            <MenuItem Header="ファイル(_F)">
                <MenuItem Header="新規(_N)" InputGestureText="Ctrl+N">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <action:ShowFolderBrowserDialogAction Description="フォルダを選択してください" 
                                                                                               SelectedPath="{Binding SelectedPath.Value, Mode=OneWayToSource}" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </MenuItem>
            </MenuItem>
        </Menu>
    </Grid>
</Window>