2011-01-29 60 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,它提供了它自己的InvokeCommandAction實現,該實現建立在Systems.Windows.Interactivity實現的基礎上,並且可以做我所需要的。另一個考慮是寫我自己的觸發器,但我希望有一種方法可以在不使用外部工具包的情況下完成。

回答

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 AML

<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 2011-09-08 11:58:38

-1

在我的腦海之上..你可以傳遞事件參數給命令,而不是在ViewModel中檢查是否e.KeyPress = Keys.Enter ..這不是真的代碼:)我沒有在這臺計算機上的VS ..這是一個想法:)

+0

這不會比鉤住文本框的KeyUp或KeyPress事件更好,並且事件處理程序在e.Key == ENTER時調用該命令。所以,這需要緊密耦合,指揮意在避免...並且我在XAML中尋求解決方案;檢查ViewModel中的鍵只是添加了另一個層來處理一個事件,後面的代碼很容易在視圖(而不是視圖模型)中完成。此外,這樣的事件處理不應該發生在視圖模型中,無論如何=) – bitxwise 2011-01-29 10:08:06

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

+1爲自定義觸發器方法 - 我已經在我的答案中與其他方法一起修改了它。 – bitxwise 2011-03-10 23:43:10

+0

有趣的..我有問題的ExecuteCommandAction並扔在我:InvokeCommandAction,因爲我有參考,無論如何,它的工作就像一個冠軍。 – itchi 2011-06-24 22:35:01

2

我用scottrudy的代碼在我的應用然而,我的文本框的文本被綁定到視圖模型類中的一些財產,這財產是沒有得到的時候命令更新pressiong回車鍵後,因爲我的文本框不是招」被調用目前失去了重點。所以,爲了解決這個問題,我在AssociatedObject_KeyUp方法的InvokeActions(o)上面添加了以下代碼片段,並且更新後的text屬性在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。

相關問題