2011-01-29 26 views
15

ENTER düğmesine basıldığında TextBox komutunu çağırmak istiyorum. Aşağıdaki XAML:XAML'de "ENTER" Tuşuna Basıldığında Komutu Çağır

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

göz önünde bulundurun ve MyCommand aşağıdaki gibidir:

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

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

Yukarıdaki tuşlarla her tuşa basıldığında komutum çağrılır. Komutu yalnızca ENTER tuşuna basıldığında yalnızca çağırmak için nasıl kısıtlayabilirim?

Ekspresyon Karışımı ile Koşulları kullanabileceğimi, ancak öğelerin sınırlandırılmış gibi göründüğünü ve etkinlik argümanlarını dikkate alamadığını biliyorum.

Ayrıca Systems.Windows.Interactivity uygulamasının üzerine inşa edilen SLEX uygulamasına da sahibim ve ihtiyacım olanı yapabilirim. Bir başka düşüncem de kendi tetikleyicimi yazmaktır, ancak umarım dış araç takımları kullanmadan bunu yapmanın bir yolu vardır.

cevap

16

Ben özel ile (I +1 verdik hangi) scottrudy yaklaşımını benim ilk yaklaşıma sadık kaldıkça yaklaşımı tetikler gibi. Ben doğrudan ICommand bağlamak mümkün yansıma bilgileri yerine bağımlılık özelliklerini kullanmak için aşağıda değiştirilmiş bir sürümünü dahil ediyorum. İsterseniz System.Windows.Interactivity kullanmaktan kaçınmak için ekli özellikleri kullanarak bir yaklaşım da dahil ediyorum. İkinci yaklaşıma yönelik uyarı, bir etkinlikten çoklu çağrılar özelliğini kaybetmenizdir, ancak daha genel olarak uygulayabilirsiniz.


Özel Tetikleyiciler Yaklaşımı

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> 

Ekli Özellikler Yaklaşımı

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

Sadece şunu söylemek istedim, bu benim sorunum için harika bir çözüm. Çok teşekkürler :) – Rumplin

+1

Şimdi bir sorunum var, enter tuşuna bastığımda, değer henüz ayarlanmadı ve komut çalıştırıldı. Yardım et? – Rumplin

+0

@Rumplin - Hangi yaklaşımı kullanıyorsunuz (örn. Özel Tetikleyiciler veya Eklenmiş Özellikler)? Yeni bir soru yayınlamanızın en iyi yolu olacağına inanıyorum ama bu yazıya bir gönderide başvurmalısınız. – bitxwise

-1

Aklımın üst kısmında .. Komuta için olay hatalarını ve ViewModel'den daha fazlasını kontrol edebilirsiniz. E.KeyPress = Keys.Enter… bu gerçekten kod değil :) Bu bilgisayarda VS'm yok .. Bu daha ziyade bir fikir :)

+0

Bu e.Key == ENTER eğer komutu çağırmak keyUp veya metin kutusuna KeyPress olayı üzerine ve olay işleyicisinde çengel daha iyi olurdu. Yani bu, komutalamanın kaçınması amaçlanan sıkı bir bağlantıyı gerektirirdi ... ve XAML'de bir çözüm istiyordum; ViewModel'deki anahtarın kontrol edilmesi, basit bir şekilde, görünümde (görünüm modelinden ziyade) kolaylıkla yapılabilen bir kodun ele alınması için başka bir katman ekler. Ayrıca, bu tür olay işleme özel tetik yaklaşımı için zaten =) – bitxwise

4

dün aynı sorun koştum ve özel tetikleyicileri kullanarak bunu çözmüş olduğunu. İlk başta biraz fazla görünebilir, ancak bu genel modelin, olay işleyicilerini doğrudan bir görünümde (çift tıklama etkinlikleri gibi) kullanarak gerçekleştirdiğim birçok şeyi yapmak için kullanılabilir olduğunu buldum. İlk adım, daha sonra ihtiyaç duyacağımızdan, bir parametreyi kabul edebilecek bir tetikleyici eylem oluşturmaktır.

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

sonraki adım tetiği oluşturmaktır. Farklı türdeki temel baskıları yakalamak için daha genel bir yöntem haline getirmek için temel sınıflarla bazı ilginç şeyler yapabilirsiniz, ancak bunu basit tutacağız. sizin metin kutusu odak kaybetmemiş çünkü bir veri sizin TextBox değerine yerinde bağlayıcı sahip olsa bile

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

unutmayın, mülkiyet değiştirilen olayı olmaz. Bu nedenle TextBox.Text özelliğinin değerini komutuna iletiyorum. Son adım, bu özelliği XAML'nizde kullanmaktır. Etkileşim ad alanını ve kodunuzu içeren ad alanını yukarıdan içerdiğinden emin olmanız gerekir.

<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 bir görünüm modelinde yer almamalıdır - Başka bir yaklaşım ile birlikte cevabım onu ​​modifiye ettim. İlginç – bitxwise

+0

.. Ben ExecuteCommandAction sorun vardı ve i attı: InvokeCommandAction ben zaten başvuru beri ve bir şampiyon gibi çalıştı. – itchi

2

aml ı ancak, benim metin kutusu metin ViewModel sınıftaki bazı özelliğine bağlı benim uygulamada scottrudy kodunu kullanılan ve bu özellik benim metin kutusu azının çünkü pressiong tuşuna basın sonra çağrılır zaman komutuyla güncelleştirilmez Henüz odaklanmadı. Yani, bu çözülmesi için, ben sadece InvokeActions AssociatedObject_KeyUp yönteminde (o) ve güncel metin özelliği ViewModel sınıfında güncellendiğini yukarıda Aşağıdaki kod parçacıkları ekledi.

21

İfade karışımında KeyTrigger vardır.

<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.Interactivity ve Microsoft.Expression.Interactions montajları resmi Nuget package WPF için kullanılabilir.

+0

Biliyorum, biraz geç kaldım, ama bu bir cevap olmalı. – Dennis

+0

Ah, Asıl mesaj gönderdiğimde ve soruyu cevapladığımda bunların uygun olmadığını düşünüyorum. Yanıtı teknoloji değişiklikleri olarak güncellemek için SO üzerinde geleneksel mi? – bitxwise

+0

bu çalışmayabilir, çünkü 'ın üst sınıfı ** System.Windows.TriggerBase ** iken, ' ın üst sınıfı ise ** sistemidir. Windows.Interactivity.TriggerBase **, bu yüzden ** KeyTrigger ** ** Trigger ** 'a eklenemez. Hem XAML hem de .cs dosyasında denedim, ikisi de başarısız. – user1108069

İlgili konular