15

で ENTER を押したときにコマンドを呼び出したいTextBox。次の XAML を検討してください。

<UserControl
     ...
     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
     ...>    
     ...    
     <TextBox>
          <i:Interaction.Triggers>
               <i:EventTrigger EventName="KeyUp">
                    <i:InvokeCommandAction Command="{Binding MyCommand}"
                                           CommandParameter="{Binding Text}" />
               </i:EventTrigger>
          </i:Interaction.Triggers>
     </TextBox>    
     ...    
</UserControl>

その MyCommand は次のとおりです。

public ICommand MyCommand {
     get { return new DelegateCommand<string>(MyCommandExecute); }
}

private void MyCommandExecute(string s) { ... }

上記により、キーを押すたびにコマンドが呼び出されます。ENTER キーが押されたときにのみコマンドを呼び出すように制限するにはどうすればよいですか?

Expression Blend では条件を使用できることは理解していますが、それらは要素に制限されているようで、イベント引数を考慮することができません。

また、実装の上に構築され、必要なことを実行できる独自の実装を提供するSLEXにも出会いました。別の考慮事項は、独自のトリガーを作成することですが、外部ツールキットを使用せずにそれを行う方法があることを願っています。InvokeCommandActionSystems.Windows.Interactivity

4

5 に答える 5

16

I like scottrudy's approach (to which I've given a +1) with the custom triggers approach as it stays true to my initial approach. I'm including a modified version of it below to use dependency properties instead of reflection info so that it's possible to bind directly to the ICommand. I'm also including an approach using attached properties to avoid using System.Windows.Interactivity if desired. The caveat to the latter approach is that you lose the feature of multiple invokations from an event, but you can apply it more generally.


Custom Triggers Approach

ExecuteCommandAction.cs

public class ExecuteCommandAction : TriggerAction<DependencyObject> {
    #region Properties
    public ICommand Command {
        get { return (ICommand)base.GetValue(CommandProperty); }
        set { base.SetValue(CommandProperty, value); }
    }

    public static ICommand GetCommand(DependencyObject obj) {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, ICommand value) {
        obj.SetValue(CommandProperty, value);
    }

    // We use a DependencyProperty so we can bind commands directly rather
    // than have to use reflection info to find them
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommandAction), null);
    #endregion Properties

    protected override void Invoke(object parameter) {
        ICommand command = Command ?? GetCommand(AssociatedObject);
        if (command != null && command.CanExecute(parameter)) {
            command.Execute(parameter);
        }
    }
}

TextBoxEnterKeyTrigger.cs

public class TextBoxEnterKeyTrigger : TriggerBase<UIElement> {
    protected override void OnAttached() {
        base.OnAttached();
        TextBox textBox = this.AssociatedObject as TextBox;

        if (textBox != null) {
            this.AssociatedObject.KeyUp += new System.Windows.Input.KeyEventHandler(AssociatedObject_KeyUp);
        }
        else {
            throw new InvalidOperationException("This behavior only works with TextBoxes");
        }
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedObject_KeyUp);
    }

    private void AssociatedObject_KeyUp(object sender, KeyEventArgs e) {
        if (e.Key == Key.Enter) {
            TextBox textBox = AssociatedObject as TextBox;

            //This checks for an mvvm style binding and updates the source before invoking the actions.
            BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
            if (expression != null)
                expression.UpdateSource();

            InvokeActions(textBox.Text);
        }
    }
}

MyUserControl.xaml

<UserControl
    ...
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:b="clr-namespace:MyNameSpace.Interactivity"
    ...
    <TextBox>
        <i:Interaction.Triggers>
            <b:TextBoxEnterKeyTrigger>
                <b:ExecuteCommandAction Command="{Binding MyCommand}" />
            </b:TextBoxEnterKeyTrigger>
        </i:Interaction.Triggers>
    </TextBox>
    ...
</UserControl>

Attached Properties Approach

EnterKeyDown.cs

public sealed class EnterKeyDown {

    #region Properties

    #region Command

    public static ICommand GetCommand(DependencyObject obj) {
        return (ICommand)obj.GetValue(CommandProperty);
    }

    public static void SetCommand(DependencyObject obj, ICommand value) {
        obj.SetValue(CommandProperty, value);
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(EnterKeyDown),
            new PropertyMetadata(null, OnCommandChanged));

    #endregion Command

    #region CommandArgument

    public static object GetCommandArgument(DependencyObject obj) {
        return (object)obj.GetValue(CommandArgumentProperty);
    }

    public static void SetCommandArgument(DependencyObject obj, object value) {
        obj.SetValue(CommandArgumentProperty, value);
    }

    public static readonly DependencyProperty CommandArgumentProperty =
        DependencyProperty.RegisterAttached("CommandArgument", typeof(object), typeof(EnterKeyDown),
            new PropertyMetadata(null, OnCommandArgumentChanged));

    #endregion CommandArgument

    #region HasCommandArgument


    private static bool GetHasCommandArgument(DependencyObject obj) {
        return (bool)obj.GetValue(HasCommandArgumentProperty);
    }

    private static void SetHasCommandArgument(DependencyObject obj, bool value) {
        obj.SetValue(HasCommandArgumentProperty, value);
    }

    private static readonly DependencyProperty HasCommandArgumentProperty =
        DependencyProperty.RegisterAttached("HasCommandArgument", typeof(bool), typeof(EnterKeyDown),
            new PropertyMetadata(false));


    #endregion HasCommandArgument

    #endregion Propreties

    #region Event Handling

    private static void OnCommandArgumentChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
        SetHasCommandArgument(o, true);
    }

    private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
        FrameworkElement element = o as FrameworkElement;
        if (element != null) {
            if (e.NewValue == null) {
                element.KeyDown -= new KeyEventHandler(FrameworkElement_KeyDown);
            }
            else if (e.OldValue == null) {
                element.KeyDown += new KeyEventHandler(FrameworkElement_KeyDown);
            }
        }
    }

    private static void FrameworkElement_KeyDown(object sender, KeyEventArgs e) {
        if (e.Key == Key.Enter) {
            DependencyObject o = sender as DependencyObject;
            ICommand command = GetCommand(sender as DependencyObject);

            FrameworkElement element = e.OriginalSource as FrameworkElement;
            if (element != null) {
                // If the command argument has been explicitly set (even to NULL)
                if (GetHasCommandArgument(o)) {
                    object commandArgument = GetCommandArgument(o);

                    // Execute the command
                    if (command.CanExecute(commandArgument)) {
                        command.Execute(commandArgument);
                    }
                }
                else if (command.CanExecute(element.DataContext)) {
                    command.Execute(element.DataContext);
                }
            }
        }
    }

    #endregion
}

MyUserControl.xaml

<UserControl
    ...
    xmlns:b="clr-namespace:MyNameSpace.Interactivity"
    ...
    <TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}"
             b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" />
    ...
</UserControl>
于 2011-03-11T00:07:40.723 に答える
4

昨日、この同じ問題に遭遇し、カスタム トリガーを使用して解決しました。最初は少し多すぎるように思えるかもしれませんが、この一般的なパターンは、ビューで直接イベント ハンドラーを使用して達成していた多くのこと (ダブル クリック イベントなど) に使用できることがわかりました。最初のステップは、後で必要になるため、パラメーターを受け入れることができるトリガー アクションを作成することです。

public class ExecuteCommandAction : TriggerAction<FrameworkElement>
{
    public string Command { get; set; }

    protected override void Invoke(object o)
    {
        if (Command != null)
        {
            object ctx = AssociatedObject.DataContext;
            if (ctx != null)
            {
                var cmd = ctx.GetType().GetProperty(Command)
                    .GetValue(ctx, null) as ICommand;
                if (cmd != null && cmd.CanExecute(o))
                {
                    cmd.Execute(o);
                }
            }
        }
    }
}

次のステップは、トリガーを作成することです。さまざまなタイプのキー押下をキャプチャするためのより一般的なものにするために、基本クラスでいくつかの興味深いことを行うことができますが、ここでは単純にしておきます。

public class TextBoxEnterKeyTrigger: TriggerBase<UIElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.KeyUp += AssociatedObject_KeyUp;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
    }

    void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            TextBox textBox = AssociatedObject as TextBox;
            object o = textBox == null ? null : textBox.Text;
            if (o != null)
            {
                InvokeActions(o);
            }
        }
    }
}

TextBox 値へのデータ バインディングが設定されていても、テキスト ボックスがフォーカスを失っていないため、プロパティ変更イベントは発生しないことに注意してください。このため、TextBox.Text プロパティの値をコマンドに渡しています。最後の手順は、XAML でこの機能を使用することです。上記のコードを含む名前空間だけでなく、Interactivity 名前空間も必ず含める必要があります。

<UserControl
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:common="clr-namespace:My.UI;assembly=My.UI">
    <TextBox Text="{Binding Path=MyText, Mode=TwoWay}" IsEnabled="{Binding CanMyCommand}">
        <i:Interaction.Triggers>
            <common:TextBoxEnterKeyTrigger>
                <common:ExecuteCommandAction Command=MyCommand" />
            </common:TextBoxEnterKeyTrigger>
        </i:Interaction.Triggers>
    </TextBox>
</UserControl>
于 2011-03-09T12:37:13.930 に答える
2

アプリケーションでscottrudyのコードを使用しましたが、テキストボックスのテキストはビューモデルクラスのプロパティにバインドされており、テキストボックスがまだフォーカスを失っていないため、ENTERキーを押した後にコマンドが呼び出されるまでにこのプロパティは更新されません。したがって、これを解決するために、AssociatedObject_KeyUp メソッドの InvokeActions(o) のすぐ上に次のコード スニペットを追加し、更新されたテキスト プロパティがビューモデル クラスで更新されます。

                    BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty);
                bindingExpression.UpdateSource();
于 2011-12-09T18:44:32.320 に答える
-1

私の頭の上に..イベント引数をコマンドに渡すことができ、ViewModelでe.KeyPress = Keys.Enterかどうかを確認できます..これは実際にはコードではありません:)私はこのコンピューターにVSを持っていません..これはむしろ考え :)

于 2011-01-29T09:52:34.263 に答える