Binden Sie die TextBox beim Drücken der Eingabetaste

108

Die Standarddatenbindung TextBoxist, TwoWayund der Text wird nur dann in die Eigenschaft übernommen, wenn TextBoxder Fokus verloren geht.

Gibt es eine einfache XAML-Möglichkeit, die Datenbindung durchzuführen, wenn ich die EnterTaste auf dem drücke TextBox? Ich weiß, dass es im Code dahinter ziemlich einfach ist, aber stellen Sie sich vor, dass dies TextBoxin einem Komplex liegt DataTemplate.

Jobi Joy
quelle

Antworten:

137

Sie können sich einen reinen XAML-Ansatz machen, indem Sie ein angehängtes Verhalten erstellen .

Etwas wie das:

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

Dann setzen Sie in Ihrer XAML die InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertyEigenschaft auf diejenige, die Sie aktualisieren möchten, wenn Sie die EnterTaste drücken. So was

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

(Sie müssen nur sicherstellen, dass das Stammelement Ihrer XAML-Datei eine xmlns-CLR-Namespace-Referenz für "b" enthält, die auf den Namespace verweist, in den Sie den InputBindingsManager eingefügt haben.)

Samuel Jack
quelle
11
Um zukünftigen Lesern ein paar Minuten des Spielens zu ersparen, habe ich dies nur in meinem Projekt verwendet, und das oben bereitgestellte Beispiel-XAML hat nicht richtig funktioniert. Die Quelle wurde bei jeder Zeichenänderung aktualisiert. Durch Ändern von "UpdateSourceTrigger = PropertyChanged" in "UpdateSourceTrigger = Explicit" wurde das Problem behoben. Jetzt funktioniert alles wie gewünscht.
Ihake
1
@ihake: Ich denke, Ihre empfohlene Änderung wird auch verhindern, dass die Aktualisierung des Fokus verloren geht
VoteCoffee
4
UpdateSourceTrigger = PropertyChanged kann beim Eingeben einer reellen Zahl unpraktisch sein. Zum Beispiel "3." verursacht ein Problem, wenn es an einen Schwimmer gebunden ist. Ich würde empfehlen, UpdateSourceTrigger (oder die Einstellung auf LostFocus) nicht zusätzlich zum angehängten Verhalten anzugeben. Dies gibt das Beste aus beiden Welten.
David Hollinshead
2
Ausgezeichnete Arbeit! Tiny nit-pick: Wenn der UpdatePropertySourceWhenEnterPressedWechsel von einem gültigen Wert zu einem anderen gültigen Wert erfolgt, können Sie das PreviewKeyDownEreignis unnötig abbestellen und erneut abonnieren . Stattdessen müssen Sie nur prüfen, ob das e.NewValueist nulloder nicht. Wenn nicht null, abonnieren Sie; Andernfalls, wenn null, dann abbestellen.
Nicholas Miller
1
Ich liebe diese Lösung, danke, dass du sie veröffentlicht hast! Für alle, die dieses Verhalten an zahlreiche Textfelder in Ihrer Anwendung anhängen müssen, habe ich eine Erweiterung dieser Antwort veröffentlicht, in der erläutert wird, wie Sie dies sehr einfach erreichen können. Siehe den Beitrag hier
Nik
50

So habe ich dieses Problem gelöst. Ich habe einen speziellen Event-Handler erstellt, der in den Code dahinter eingeht:

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

Dann habe ich dies als KeyUp-Ereignishandler in der XAML hinzugefügt:

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

Der Ereignishandler verwendet seine senderReferenz, um zu veranlassen, dass seine eigene Bindung aktualisiert wird. Da der Ereignishandler in sich geschlossen ist, sollte er in einer komplexen DataTemplate funktionieren. Dieser eine Ereignishandler kann jetzt allen Textfeldern hinzugefügt werden, die diese Funktion benötigen.

Ben
quelle
8
Etwas sagt mir, dass dies ein unterschätzter Beitrag ist. Viele Antworten sind beliebt, weil sie "XAML-y" sind. Aber dieser scheint in den meisten Fällen Platz zu sparen.
j riv
3
+1 Auf diese Weise können Sie auch UpdateSourceTrigger in Ruhe lassen, falls sich Ihre TextBox-Bindungen bereits sorgfältig so verhalten haben, wie Sie es möchten (mit Stilen, Validierung, Zweiwege usw.), aber derzeit nach Eingabe der Eingabetaste keine Eingaben erhalten .
Jamin
2
Dies ist der sauberste Ansatz, den ich gefunden habe, und sollte meiner Meinung nach als Antwort markiert werden. Es wurde eine zusätzliche Nullprüfung für den Absender, eine Prüfung für Key.Return (Eingabetaste auf meiner Tastatur gibt Key.Return zurück) und Keyboard.ClearFocus () hinzugefügt, um den Fokus aus der TextBox nach dem Aktualisieren der Quelleigenschaft zu entfernen. Ich habe die Antwort bearbeitet, die auf die Überprüfung durch Fachkollegen wartet.
Oystein
2
Stimmen Sie den obigen Kommentaren zu. Aber für mich war KeyDown Event angemessener
Allender
1
Ich habe KeyBindingdiese Methode in der XAML verwendet, um sie auszulösen, da meine Benutzeroberfläche über ein "Standard" -Steuerelement verfügt, das die Eingabetaste abfängt. Sie müssen es in dieser TextBox abfangen, um zu verhindern, dass es den UI-Baum an das Steuerelement "Standard" weiterleitet.
CAD Kerl
46

Ich glaube nicht, dass es eine "reine XAML" -Methode gibt, um das zu tun, was Sie beschreiben. Sie können eine Bindung so einrichten, dass sie aktualisiert wird, wenn sich der Text in einer TextBox ändert (und nicht, wenn die TextBox den Fokus verliert), indem Sie die UpdateSourceTrigger- Eigenschaft wie folgt festlegen :

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

Wenn Sie UpdateSourceTrigger auf "Explicit" setzen und dann das PreviewKeyDown-Ereignis der TextBox behandeln (nach der Eingabetaste suchen), können Sie das erreichen, was Sie möchten, aber es würde Code-Behind erfordern. Vielleicht würde eine Art angehängte Eigenschaft (ähnlich meiner EnterKeyTraversal- Eigenschaft) für Sie funktionieren.

Matt Hamilton
quelle
3
Diese Antwort ist möglicherweise einfacher als die als Antwort gekennzeichnete, weist jedoch einige Einschränkungen auf. Bild, das Sie in der Set-Funktion der gebundenen Eigenschaft überprüfen möchten (Überprüfung, ob die Eingabe gültig ist). Sie möchten diese Überprüfungsfunktion nicht nach jeder vom Benutzer gedrückten Taste aufrufen, oder (insbesondere nicht, wenn die Funktion einige Zeit in Anspruch nimmt).
sth_Weird
25

Sie können ganz einfach ein eigenes Steuerelement erstellen, das von TextBox erbt, und es im gesamten Projekt wiederverwenden.

Ähnliches sollte funktionieren:

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

Es kann eine Möglichkeit geben, diesen Schritt zu umgehen, aber ansonsten sollten Sie wie folgt binden (mit Explicit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
Ryan Versaw
quelle
9
In WPF / Silverlight sollten Sie niemals Vererbung verwenden - es bringt Stile durcheinander und ist nicht so flexibel wie angehängte Verhaltensweisen. Bei angehängten Verhaltensweisen können Sie beispielsweise sowohl Wasserzeichen als auch UpdateOnEnter im selben Textfeld haben.
Mikhail
14

Wenn Sie die Lösungen von Ben und ausadmin kombinieren, erhalten Sie eine sehr MVVM-freundliche Lösung:

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

... was bedeutet, dass Sie das TextBoxselbst als Parameter an das übergeben Command.

Dies führt dazu, dass Sie so Commandaussehen (wenn Sie eine DelegateCommandImplementierung im Stil Ihrer VM verwenden):

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

Diese CommandImplementierung kann für jeden TextBoxund am besten keinen Code im CodeBehind verwendet werden, obwohl Sie dies möglicherweise in eine eigene Klasse einfügen möchten, damit System.Windows.Controlsin Ihrer VM keine Abhängigkeiten bestehen . Dies hängt davon ab, wie streng Ihre Code-Richtlinien sind.

toadflakz
quelle
Nett. Sehr sauber. Wenn eine Mehrfachbindung beteiligt ist, müssen Sie möglicherweise BindingOperations.GetBindingExpressionBase (tBox, prop) verwenden. und dann bindend.UpdateTarget ();
VoteCoffee
4

Hier ist ein Ansatz, der mir recht einfach und einfacher erscheint als das Hinzufügen eines AttachedBehaviour (was auch eine gültige Lösung ist). Wir verwenden den Standard-UpdateSourceTrigger (LostFocus für TextBox) und fügen dann der Eingabetaste eine Eingabebindung hinzu, die an einen Befehl gebunden ist.

Das xaml ist wie folgt

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

Dann sind die Befehlsmethoden

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

Und die TextBox ist an die Eigenschaft gebunden

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

Bisher scheint dies gut zu funktionieren und fängt das Enter Key-Ereignis in der TextBox ab.

ausadmin
quelle
4

Dies ist keine Antwort auf die ursprüngliche Frage, sondern eine Erweiterung der akzeptierten Antwort von @Samuel Jack. Ich habe in meiner eigenen Bewerbung Folgendes getan und war beeindruckt von der Eleganz von Samuels Lösung. Es ist sehr sauber und sehr wiederverwendbar, da es auf jeder Steuerung verwendet werden kann, nicht nur auf der TextBox. Ich dachte, das sollte mit der Community geteilt werden.

Wenn Sie ein Fenster mit tausend Fenstern haben TextBoxes, die alle zum Aktualisieren der Bindungsquelle bei der Eingabe erforderlich sind, können Sie dieses Verhalten an alle anhängen, indem Sie die unten stehende XAML in Ihre Window Resourceseinfügen, anstatt sie an jede Textbox anzuhängen. Zuerst müssen Sie natürlich das angehängte Verhalten gemäß Samuels Beitrag implementieren .

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

Sie können den Bereich bei Bedarf jederzeit einschränken, indem Sie den Stil in die Ressourcen eines der Griduntergeordneten Elemente des Fensters (dh a ) einfügen, das die Zieltextfelder enthält.

Nik
quelle
2

Wenn Sie MultiBinding mit Ihrer TextBox verwenden, müssen Sie BindingOperations.GetMultiBindingExpressionstattdessen die Methode verwenden 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();
     }
}
Akjoshi
quelle
1

Das funktioniert bei mir:

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

Ich persönlich halte eine Markup-Erweiterung für einen saubereren Ansatz.

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>
mir zu dir
quelle
0

Eine andere Lösung (nicht mit xaml, aber trotzdem ziemlich sauber, denke ich).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Hauk
quelle