Domanda

Voglio richiamare un comando quando si preme ENTER in una TextBox. Si consideri il seguente 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>

e che MyCommand è la seguente:

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

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

Con quanto sopra, il mio comando viene richiamato per ogni pressione di un tasto. Come posso restringere il comando da solo invoke quando viene premuto il tasto ENTER?

Mi rendo conto che con Expression Blend posso utilizzare condizioni diverse da quelle sembrano essere limitato agli elementi e non può prendere in considerazione argomenti dell'evento.

Ho anche venire attraverso SLEX che offre la propria implementazione InvokeCommandAction che è costruito in cima alla realizzazione Systems.Windows.Interactivity e può fare quello che ho bisogno. Un'altra considerazione è di scrivere il mio grilletto, ma spero ci sia un modo per farlo senza l'utilizzo di kit di strumenti esterni.

È stato utile?

Soluzione 2

Mi piace l'approccio di scottrudy (a cui ho dato un +1) con i trigger personalizzati avvicinano in quanto rimane fedele al mio approccio iniziale. Sto tra cui una versione modificata di esso seguito per proprietà di dipendenza usare al posto di informazioni riflessione in modo che sia possibile associare direttamente al ICommand. Sono anche inclusi un approccio utilizzando le proprietà collegate per evitare di usare System.Windows.Interactivity se lo si desidera. L'avvertimento di quest'ultimo approccio è che si perde la funzione di molteplici invocazioni da un evento, ma si può applicare più in generale.


Trigger personalizzati 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 avvicinamento

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>

Altri suggerimenti

C'è KeyTrigger in Expression blend.

<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 e Microsoft.Expression.Interactions assemblaggi sono disponibili per WPF nella Gazzetta Nuget pacchetto .

mi sono imbattuto in questo stesso problema ieri e risolto utilizzando i trigger personalizzati. Può sembrare un po 'troppo in un primo momento, ma ho trovato questo schema generale è utilizzabile per fare un sacco di cose che ho usato per realizzare con i gestori di eventi direttamente in una vista (come eventi doppio clic). Il primo passo è quello di creare un'azione di innesco che può accettare un parametro da quando ne avremo bisogno in seguito.

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

Il passo successivo è quello di creare il grilletto. Si potrebbe fare alcune cose interessanti con classi di base per renderlo più generica per catturare diversi tipi di pressioni dei tasti, ma ti mantenerlo semplice.

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

Tieni presente che, anche se si può avere una associazione dati in atto per il vostro valore TextBox, la proprietà cambiato evento non il fuoco perché il testo non ha perso fuoco. Per questo motivo sto passando il valore della proprietà TextBox.Text al comando. L'ultimo passo è quello di utilizzare questa funzione nel vostro XAML. Hai bisogno di essere sicuri di includere il namespace interattività così come lo spazio dei nomi che contiene il codice da sopra.

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

Ho usato il codice di scottrudy nella mia domanda però, il mio testo testo è destinata a qualche proprietà in classe ViewModel e questa proprietà non è sempre aggiornato con il tempo di comando viene richiamato dopo pressiong tasto ENTER perché il mio testo non ha ancora perso messa a fuoco. Così, per risolvere questo, ho aggiunto le seguenti frammenti di codice appena sopra InvokeActions (o) nel metodo AssociatedObject_KeyUp e proprietà di testo aggiornato è sempre aggiornato in classe ViewModel.

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

In cima a mia mente .. Si può passare argomenti di eventi per il comando e controllo che in ViewModel se e.KeyPress = Keys.Enter .. questo non è davvero il codice :) Non ho il mio VS su questo computer .. questo è piuttosto un'idea:)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top