La liaison de données par défaut sur TextBox
est TwoWay
et ne valide le texte dans la propriété que lorsque TextBox
a 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 TextBox
soit à l'intérieur d'un complexe DataTemplate
name__.
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).
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.
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é.
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}" />
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.
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.
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.
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 Window
Resources
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.
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();
}
}
Cela fonctionne pour moi:
<TextBox
Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding Ok}"/>
</TextBox.InputBindings>
</TextBox>
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
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>