web-dev-qa-db-fra.com

Lier la zone de texte en appuyant sur la touche Entrée

La liaison de données par défaut sur TextBoxest TwoWayet ne valide le texte dans la propriété que lorsque TextBoxa perdu son focus.

Existe-t-il un moyen XAML facile de créer la liaison de données lorsque j’appuie sur le Enter clé sur la TextBox?. Je sais que c'est assez facile à faire dans le code ci-dessous, mais imaginons que ce TextBoxsoit à l'intérieur d'un complexe DataTemplatename__.

99
Jobi Joy

Vous pouvez créer une approche XAML pure en créant un comportement attaché .

Quelque chose comme ça:

public static class InputBindingsManager
{

    public static readonly DependencyProperty UpdatePropertySourceWhenEnterPressedProperty = DependencyProperty.RegisterAttached(
            "UpdatePropertySourceWhenEnterPressed", typeof(DependencyProperty), typeof(InputBindingsManager), new PropertyMetadata(null, OnUpdatePropertySourceWhenEnterPressedPropertyChanged));

    static InputBindingsManager()
    {

    }

    public static void SetUpdatePropertySourceWhenEnterPressed(DependencyObject dp, DependencyProperty value)
    {
        dp.SetValue(UpdatePropertySourceWhenEnterPressedProperty, value);
    }

    public static DependencyProperty GetUpdatePropertySourceWhenEnterPressed(DependencyObject dp)
    {
        return (DependencyProperty)dp.GetValue(UpdatePropertySourceWhenEnterPressedProperty);
    }

    private static void OnUpdatePropertySourceWhenEnterPressedPropertyChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = dp as UIElement;

        if (element == null)
        {
            return;
        }

        if (e.OldValue != null)
        {
            element.PreviewKeyDown -= HandlePreviewKeyDown;
        }

        if (e.NewValue != null)
        {
            element.PreviewKeyDown += new KeyEventHandler(HandlePreviewKeyDown);
        }
    }

    static void HandlePreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            DoUpdateSource(e.Source);
        }
    }

    static void DoUpdateSource(object source)
    {
        DependencyProperty property =
            GetUpdatePropertySourceWhenEnterPressed(source as DependencyObject);

        if (property == null)
        {
            return;
        }

        UIElement elt = source as UIElement;

        if (elt == null)
        {
            return;
        }

        BindingExpression binding = BindingOperations.GetBindingExpression(elt, property);

        if (binding != null)
        {
            binding.UpdateSource();
        }
    }
}

Ensuite, dans votre XAML, définissez la propriété InputBindingsManager.UpdatePropertySourceWhenEnterPressedProperty sur celle que vous souhaitez mettre à jour lorsque la Enter la touche est enfoncée. Comme ça

<TextBox Name="itemNameTextBox"
         Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}"
         b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed="TextBox.Text"/>

(Vous devez simplement vous assurer d'inclure une référence xmlns clr-namespace pour "b" dans l'élément racine de votre fichier XAML pointant vers le premier espace de noms dans lequel vous avez inséré le InputBindingsManager).

127
Samuel Jack

Je ne crois pas qu'il existe un moyen "pur XAML" de faire ce que vous décrivez. Vous pouvez configurer une liaison de sorte qu'elle soit mise à jour chaque fois que le texte d'une zone de texte change (plutôt que lorsque la zone de texte perd son focus) en définissant la propriété UpdateSourceTrigger , comme ceci:

<TextBox Name="itemNameTextBox"
    Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />

Si vous définissez UpdateSourceTrigger sur "Explicit", puis que vous gérez l'événement PreviewKeyDown de la zone de texte (à la recherche de la touche Entrée), vous pouvez obtenir ce que vous voulez, mais vous aurez besoin de code-behind. Peut-être qu’une sorte de propriété attachée (semblable à mon EnterKeyTraversal property) fonctionnerait pour vous.

46
Matt Hamilton

Voici comment j'ai résolu ce problème. J'ai créé un gestionnaire d'événements spéciaux qui est entré dans le code derrière:

private void TextBox_KeyEnterUpdate(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter)
    {
        TextBox tBox = (TextBox)sender;
        DependencyProperty prop = TextBox.TextProperty;

        BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
        if (binding != null) { binding.UpdateSource(); }
    }
}

Ensuite, je viens d'ajouter ceci en tant que gestionnaire d'événements KeyUp dans le XAML:

<TextBox Text="{Binding TextValue1}" KeyUp="TextBox_KeyEnterUpdate" />
<TextBox Text="{Binding TextValue2}" KeyUp="TextBox_KeyEnterUpdate" />

Le gestionnaire d'événements utilise sa référence sender pour mettre à jour sa propre liaison. Étant donné que le gestionnaire d'événements est autonome, il devrait fonctionner dans un DataTemplate complexe. Ce gestionnaire d'événements unique peut maintenant être ajouté à toutes les zones de texte nécessitant cette fonctionnalité.

37
Ben

Vous pouvez facilement créer votre propre contrôle héritant de TextBox et le réutiliser dans votre projet.

Quelque chose de semblable à ceci devrait fonctionner:

public class SubmitTextBox : TextBox
{
    public SubmitTextBox()
        : base()
    {
        PreviewKeyDown += new KeyEventHandler(SubmitTextBox_PreviewKeyDown);
    }

    void SubmitTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            BindingExpression be = GetBindingExpression(TextBox.TextProperty);
            if (be != null)
            {
                be.UpdateSource();
            }
        }
    }
}

Il y a peut-être un moyen de contourner cette étape, mais sinon vous devriez vous lier comme ceci (en utilisant Explicit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
25
Ryan Versaw

Si vous combinez les solutions Ben et ausadmin, vous obtenez une solution très conviviale pour MVVM:

<TextBox Text="{Binding Txt1, Mode=TwoWay, UpdateSourceTrigger=Explicit}">
    <TextBox.InputBindings>
        <KeyBinding Gesture="Enter" 
                    Command="{Binding UpdateTextBoxBindingOnEnterCommand}"
                    CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}}}" />
    </TextBox.InputBindings>
</TextBox>

... ce qui signifie que vous passez la TextBox elle-même en tant que paramètre à la Command.

Cela donne à votre Command l'apparence suivante (si vous utilisez une implémentation de style DelegateCommand- dans votre VM):

    public bool CanExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
    {
        return true;
    }

    public void ExecuteUpdateTextBoxBindingOnEnterCommand(object parameter)
    {
        TextBox tBox = parameter as TextBox;
        if (tBox != null)
        {
            DependencyProperty prop = TextBox.TextProperty;
            BindingExpression binding = BindingOperations.GetBindingExpression(tBox, prop);
            if (binding != null) 
                binding.UpdateSource();
        }
    }

Cette implémentation Command peut être utilisée pour toute TextBox et surtout pas de code dans le code-behind, bien que vous souhaitiez peut-être mettre cela dans sa propre classe afin qu'il n'y ait aucune dépendance sur System.Windows.Controls dans votre VM. Cela dépend de la sévérité de vos directives de code.

11
toadflakz

Voici une approche qui me semble assez simple et plus facile que d’ajouter un comportement attaché (qui est également une solution valable). Nous utilisons la valeur par défaut UpdateSourceTrigger (LostFocus pour TextBox), puis nous ajoutons un InputBinding à la clé Entrée, liée à une commande.

Le xaml est comme suit

       <TextBox Grid.Row="0" Text="{Binding Txt1}" Height="30" Width="150">
        <TextBox.InputBindings>
            <KeyBinding Gesture="Enter" 
                        Command="{Binding UpdateText1Command}"
                        CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type TextBox}},Path=Text}" />
        </TextBox.InputBindings>
    </TextBox>

Ensuite, les méthodes de commande sont 

Private Function CanExecuteUpdateText1(ByVal param As Object) As Boolean
    Return True
End Function
Private Sub ExecuteUpdateText1(ByVal param As Object)

    If TypeOf param Is String Then
        Txt1 = CType(param, String)
    End If
End Sub

Et la TextBox est liée à la propriété

 Public Property Txt1 As String
    Get
        Return _txt1
    End Get
    Set(value As String)
        _txt1 = value
        OnPropertyChanged("Txt1")
    End Set
End Property

Jusqu'à présent, cela semble bien fonctionner et intercepte l'événement Enter Key dans la zone de texte.

4
ausadmin

Plus simple, définissez simplement UpdateSourceTrigger sur PropertyChanged dans la liaison de votre TextBox sans rien ajouter dans codebehind .

<TextBox Text="{Binding Path=BoundProperty, UpdateSourceTrigger=PropertyChanged}"/>

Ça marche pour moi.

3
Mr.Pidra

Ce n'est pas une réponse à la question initiale, mais plutôt une extension de la réponse acceptée de @Samuel Jack. J'ai fait ce qui suit dans ma propre application et j'étais impressionné par l'élégance de la solution de Samuel. Il est très propre et très réutilisable, car il peut être utilisé sur n’importe quel contrôle, pas seulement le TextBox. Je pensais que cela devrait être partagé avec la communauté.

Si vous avez une fenêtre avec mille TextBoxes nécessitant tous de mettre à jour la source de liaison en entrée, vous pouvez associer ce problème à tous en incluant le XAML ci-dessous dans votre WindowResources au lieu de l'attacher à chaque zone de texte. Tout d'abord, vous devez implémenter le comportement attaché selon message de Samuel , bien sûr. 

<Window.Resources>
    <Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
        <Style.Setters>
            <Setter Property="b:InputBindingsManager.UpdatePropertySourceWhenEnterPressed" Value="TextBox.Text"/>
        </Style.Setters>
    </Style>
</Window.Resources>

Vous pouvez toujours limiter la portée, si nécessaire, en plaçant le style dans les ressources de l'un des éléments enfants de la fenêtre (c'est-à-dire une Grid) contenant les zones de texte cibles.

3
Nik

Si vous utilisez MultiBinding avec votre TextBox, vous devez utiliser la méthode BindingOperations.GetMultiBindingExpression au lieu de BindingOperations.GetBindingExpression.

// Get the correct binding expression based on type of binding
//(simple binding or multi binding.
BindingExpressionBase binding = 
  BindingOperations.GetBindingExpression(element, prop);
if (binding == null)
{
    binding = BindingOperations.GetMultiBindingExpression(element, prop);
}

if (binding != null)
{
     object value = element.GetValue(prop);
     if (string.IsNullOrEmpty(value.ToString()) == true)
     {
         binding.UpdateTarget();
     }
     else
     {
          binding.UpdateSource();
     }
}
2
akjoshi

Cela fonctionne pour moi:

        <TextBox                 
            Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Ok}"/>
            </TextBox.InputBindings>
        </TextBox>
2
Valery

J'ai répondu ici assez élégamment en utilisant les comportements attachés, ma méthode préférée pour presque tout.

WPF comment faire en sorte que la zone de texte ne se concentre plus après avoir appuyé sur Entrée

1
NielW

Personnellement, je pense qu’avoir une extension de balisage est une approche plus propre.

public class UpdatePropertySourceWhenEnterPressedExtension : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new DelegateCommand<TextBox>(textbox => textbox.GetBindingExpression(TextBox.TextProperty).UpdateSource());
    }
}


<TextBox x:Name="TextBox"
             Text="{Binding Text}">
        <TextBox.InputBindings>
            <KeyBinding Key="Enter"
                        Command="{markupExtensions:UpdatePropertySourceWhenEnterPressed}" 
                        CommandParameter="{Binding ElementName=TextBox}"/>
        </TextBox.InputBindings>
</TextBox>
0
metoyou