2011-01-29 9 views
15

TextBoxでEnterを押したときにコマンドを呼び出すとします。以下のXAML考えてみましょう:XAMLで「Enter」キーが押されたときの呼び出しコマンド

<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は、Systems.Windows.Interactivity実装の上に構築された独自のInvokeCommandAction実装を提供し、必要な処理を行うことができます。別の考慮事項は、私自身のトリガを書くことですが、私は外部ツールキットを使用せずにそれを行う方法があることを望んでいます。

答えて

16

私は、私の初期のアプローチにとどまるので、私はカスタムトリガアプローチでscottrudyのアプローチ(私は+1を与えました)が好きです。私は、ICommandに直接バインドすることができるように、リフレクション情報の代わりに依存関係プロパティを使用するために、以下の変更されたバージョンを含めています。私は、必要に応じてSystem.Windows.Interactivityの使用を避けるために、添付されたプロパティを使用する方法も含めています。後者のアプローチへの注意点は、イベントからの複数の呼び出しの機能を失うことですが、より一般的に適用することができます。


カスタムトリガーアプローチ

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> 

添付プロパティは、アプローチ

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.x

<UserControl 
    ... 
    xmlns:b="clr-namespace:MyNameSpace.Interactivity" 
    ... 
    <TextBox b:EnterKeyDown.Command="{Binding AddNewDetailCommand}" 
      b:EnterKeyDown.CommandArgument="{Binding Path=Text,RelativeSource={RelativeSource Self}}" /> 
    ... 
</UserControl> 
+0

これは私の問題の大きな解決策であると言いたいだけです。多くのありがとう:) – Rumplin

+1

私は今、私が入力を押して、値がまだ設定されていないと、コマンドが実行される問題があります。助けて? – Rumplin

+0

@Rumplin - どのアプローチを使用していますか(カスタムトリガーまたは添付プロパティ)?私はあなたが新しい質問を投稿するのがあなたのポストにこれを参照するのが最善であると考える組織のためだと思います。 – bitxwise

-1

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

+0

これは、テキストボックスのKeyUpまたはKeyPressイベントにフックするよりも優れていません。イベントハンドラでは、e.Key == ENTERの場合はコマンドを呼び出します。だから、これはコマンドラインが避けることを意図している緊密な結合を必要とするでしょう...そして私はXAMLの解決策を求めていました。 ViewModelでキーをチェックするだけで、(ビューモデルではなく)ビューで簡単に実行できるコードビハインドのイベントを処理する別のレイヤーが追加されます。さらに、そのようなイベント処理は、ビューモデルでは行わないでください)= – bitxwise

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でこの機能を使用することです。インタラクティブな名前空間と上のコードを含む名前空間を必ず含める必要があります。

<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> 
+0

+1カスタムトリガーアプローチ - 別のアプローチと一緒に私の答えで修正しました。 – bitxwise

+0

面白い..私はExecuteCommandActionに問題があり、私は参照を持っていたので私はInvokeCommandActionを投げた。それはチャンピオンのように働いた。 – itchi

2

AMLしかし私は、私のテキストボックスのテキストはviewmodelのクラスのいくつかのプロパティにバインドされて自分のアプリケーションにscottrudyのコードを使用し、このプロパティは、私のテキストボックスは「hasnのでpressiongキーを入力した後に呼び出された時のコマンドによって更新取得されていませんまだフォーカスを失った。そこで、これを解決するために、AssociatedObject_KeyUpメソッドのInvokeActions(o)の直上に次のコードスニペットを追加し、更新されたテキストプロパティをviewmodelクラスで更新しています。

    BindingExpression bindingExpression = (textBox).GetBindingExpression(TextBox.TextProperty); 
       bindingExpression.UpdateSource(); 
21

発現ブレンドにKeyTriggerがあります。

<UserControl 
xmlns:i="clr-namespace:System.Windows.Interactivity; 
     assembly=System.Windows.Interactivity" 
xmlns:iex="clr-namespace:Microsoft.Expression.Interactivity.Input; 
      assembly=Microsoft.Expression.Interactions" ...>  
    <TextBox> 
     <i:Interaction.Triggers> 
      <iex:KeyTrigger Key="Enter"> 
       <i:InvokeCommandAction Command="{Binding PasswordLoginCommand}" /> 
      </iex:KeyTrigger> 
     </i:Interaction.Triggers> 
    </TextBox>  
</UserControl> 

System.Windows.InteractivityMicrosoft.Expression.Interactionsアセンブリ公式Nuget packageにWPFのために用意されています。

+0

私は少し遅れていることは知っていますが、これは答えになるはずです。 – Dennis

+0

ああ、私は最初に投稿して質問に答えたときに、これらが利用可能だとは思わない。技術の変化に応じて答えを更新するのが慣例ですか? – bitxwise

+0

'の親クラスは** System.Windows.TriggerBase **なので、' 'の親クラスは** Systemなので、これはうまくいかないかもしれません。 Windows.Interactivity.TriggerBase **ですので、** KeyTrigger **を** Triggers **に追加することはできません。 XAMLと.csファイルの両方で試してみましたが、どちらも失敗しました。 – user1108069

関連する問題