Bind TextBox po naciśnięciu klawisza Enter

108

Domyślne wiązanie danych TextBoxjest włączone TwoWayi zatwierdza tekst do właściwości tylko wtedy, gdyTextBox utracił fokus.

Czy istnieje prosty sposób XAML na wykonanie powiązania danych po naciśnięciu Enterklawisza TextBox?. Wiem, że w kodzie znajdującym się za nim jest to dość łatwe, ale wyobraź sobie, że TextBoxjest to wewnątrz jakiegoś złożonego kodu DataTemplate.

Jobi Joy
źródło

Odpowiedzi:

138

Możesz stworzyć podejście oparte na czystym XAML, tworząc dołączone zachowanie .

Coś takiego:

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

Następnie w XAML ustawiasz InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertywłaściwość na tę, którą chcesz zaktualizować po Enternaciśnięciu klawisza. Lubię to

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

(Musisz tylko upewnić się, że zawiera odwołanie do przestrzeni nazw clr xmlns dla „b” w elemencie głównym pliku XAML, wskazując na przestrzeń nazw, w której umieścisz InputBindingsManager).

Samuel Jack
źródło
11
Aby zaoszczędzić przyszłym czytelnikom kilka minut na majsterkowaniu, właśnie użyłem tego w moim projekcie, a przykładowy kod XAML podany powyżej nie działał poprawnie. Źródło było aktualizowane przy każdej zmianie postaci. Zmiana „UpdateSourceTrigger = PropertyChanged” na „UpdateSourceTrigger = Explicit” rozwiązała problem. Teraz wszystko działa zgodnie z życzeniem.
ihake
1
@ihake: Myślę, że zalecana przez Ciebie zmiana również zapobiegnie aktualizacji uwagi straconej
VoteCoffee
4
UpdateSourceTrigger = PropertyChanged może być niewygodne podczas wpisywania liczby rzeczywistej. Na przykład „3.” powoduje problem, gdy jest powiązany z pływakiem. Nie polecam określania UpdateSourceTrigger (lub ustawienia LostFocus) oprócz dołączonego zachowania. To daje to, co najlepsze z obu światów.
David Hollinshead,
2
Wspaniała robota! Mały wybór: gdy UpdatePropertySourceWhenEnterPressedzmienia się z prawidłowej wartości na inną, PreviewKeyDownniepotrzebnie anulujesz subskrypcję i ponownie subskrybujesz wydarzenie. Zamiast tego wystarczy sprawdzić, czy tak e.NewValuejest, nullczy nie. Jeśli nie null, zapisz się; w przeciwnym razie nullzrezygnuj z subskrypcji.
Nicholas Miller
1
Uwielbiam to rozwiązanie, dziękuję za opublikowanie! Dla każdego, kto musi dołączyć to zachowanie do wielu TextBoxów w Twojej aplikacji, opublikowałem rozszerzenie do tej odpowiedzi, w jaki sposób możesz to bardzo łatwo osiągnąć. Zobacz post tutaj
Nik
50

W ten sposób rozwiązałem ten problem. Stworzyłem specjalny program obsługi zdarzeń, który wszedł do kodu za:

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

Następnie właśnie dodałem to jako program obsługi zdarzeń KeyUp w XAML:

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

Procedura obsługi zdarzeń używa swojego senderodwołania, aby spowodować zaktualizowanie własnego powiązania. Ponieważ program obsługi zdarzeń jest niezależny, powinien działać w złożonym DataTemplate. Ten jeden program obsługi zdarzeń można teraz dodać do wszystkich pól tekstowych, które wymagają tej funkcji.

Ben
źródło
8
Coś mi mówi, że to niedoceniany post. Wiele odpowiedzi jest popularnych, ponieważ są one „XAML-y”. Ale ten wydaje się oszczędzać miejsce w większości przypadków.
j riv
3
+1 Pozwala to również na pozostawienie w spokoju UpdateSourceTrigger na wypadek, gdybyś już skrupulatnie ustawił swoje wiązania TextBox, aby już zachowywały się tak, jak chcesz (ze stylami, walidacją, dwukierunkowo itp.), Ale obecnie po prostu nie otrzymają danych wejściowych po naciśnięciu Enter .
Jamin
2
To najczystsze podejście, jakie znalazłem i moim zdaniem powinno zostać oznaczone jako odpowiedź. Dodano dodatkowe zerowe sprawdzenie nadawcy, sprawdzenie Key.Return (klawisz Enter na mojej klawiaturze zwraca Key.Return) i Keyboard.ClearFocus (), aby usunąć fokus z TextBox po zaktualizowaniu właściwości źródła. Wprowadziłem zmiany w odpowiedzi, która oczekuje na wzajemną weryfikację.
Oystein
2
Zgadzam się z komentarzami powyżej. Ale dla mnie bardziej odpowiednie było wydarzenie KeyDown
Allender
1
Użyłem KeyBindingw języku XAML do uruchomienia tej metody, ponieważ mój interfejs użytkownika ma kontrolkę „Default”, która przechwytuje klawisz Enter. Musisz go przechwycić w tym TextBox, aby zatrzymać propagację drzewa interfejsu użytkownika do kontrolki „Default”.
Facet z CAD
46

Nie wierzę, że istnieje „czysty XAML” sposób na zrobienie tego, co opisujesz. Możesz skonfigurować powiązanie, aby było aktualizowane za każdym razem, gdy zmieni się tekst w TextBox (zamiast gdy TextBox utraci fokus), ustawiając właściwość UpdateSourceTrigger , na przykład:

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

Jeśli ustawisz UpdateSourceTrigger na „Explicit”, a następnie obsłużysz zdarzenie PreviewKeyDown TextBox (szukając klawisza Enter), możesz osiągnąć to, co chcesz, ale wymagałoby to użycia kodu. Może jakiś załączonym własności (podobny do mojego EnterKeyTraversal mienia) pracy woudld dla Ciebie.

Matt Hamilton
źródło
3
Ta odpowiedź może być prostsza niż ta oznaczona jako odpowiedź, ale ma pewne ograniczenia. Obraz, który chcesz sprawdzić w funkcji set powiązanej właściwości (sprawdzanie, czy dane wejściowe są prawidłowe). Nie chcesz wywoływać tej funkcji sprawdzania po każdym naciśnięciu klawisza przez użytkownika, prawda (zwłaszcza nie, gdy funkcja zajmuje trochę czasu).
sth_Weird
25

Możesz łatwo utworzyć własną kontrolkę dziedziczącą po TextBox i używać jej ponownie w całym projekcie.

Coś podobnego powinno działać:

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

Może istnieć sposób na obejście tego kroku, ale w przeciwnym razie powinieneś połączyć się w ten sposób (używając jawnego):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
Ryan Versaw
źródło
9
W WPF / Silverlight nigdy nie powinieneś używać dziedziczenia - psuje style i nie jest tak elastyczny jak Attached Behaviors. Na przykład w przypadku dołączonych zachowań możesz mieć zarówno znak wodny, jak i UpdateOnEnter w tym samym polu tekstowym.
Michaił
14

Jeśli połączysz rozwiązania Bena i ausadmin, otrzymasz rozwiązanie bardzo przyjazne dla 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>

... co oznacza, że ​​przekazujesz TextBoxsiebie jako parametr do Command.

Prowadzi to do tego, że Commandwyglądasz tak (jeśli używasz DelegateCommandimplementacji w stylu A w swojej maszynie wirtualnej):

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

Ta Commandimplementacja może być używana dla dowolnego, TextBoxa co najważniejsze, bez kodu w kodzie, chociaż możesz chcieć umieścić to w swojej własnej klasie, aby nie było żadnych zależności na System.Windows.ControlsTwojej maszynie wirtualnej. Zależy to od tego, jak surowe są wytyczne dotyczące kodu.

toadflakz
źródło
Miły. Bardzo czysto. Jeśli w grę wchodzi multibinding, może być konieczne użycie BindingOperations.GetBindingExpressionBase (tBox, prop); a następnie binding.UpdateTarget ();
VoteCoffee
4

Oto podejście, które wydaje mi się dość proste i łatwiejsze niż dodanie AttachedBehaviour (które jest również prawidłowym rozwiązaniem). Używamy domyślnego UpdateSourceTrigger (LostFocus for TextBox), a następnie dodajemy InputBinding do klawisza Enter, powiązanego z poleceniem.

XAML wygląda następująco

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

Następnie metody Command są

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

TextBox jest powiązany z Property

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

Jak dotąd wydaje się, że działa to dobrze i przechwytuje zdarzenie Enter Key w TextBox.

ausadmin
źródło
4

To nie jest odpowiedź na pierwotne pytanie, ale raczej rozszerzenie zaakceptowanej odpowiedzi @Samuel Jack. Zrobiłem co następuje we własnej aplikacji i byłem pod wrażeniem elegancji rozwiązania Samuela. Jest bardzo czysty i nadaje się do wielokrotnego użytku, ponieważ można go używać na dowolnym sterowaniu, nie tylkoTextBox . Pomyślałem, że należy się tym podzielić ze społecznością.

Jeśli masz okno z tysiącem, TextBoxesktóre wszystkie wymagają zaktualizowania źródła powiązania w Enter, możesz dołączyć to zachowanie do wszystkich, włączając poniższy kod XAML do swojego, Window Resourceszamiast dołączać go do każdego TextBox. Najpierw musisz oczywiście zaimplementować załączone zachowanie zgodnie z postem Samuela .

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

W razie potrzeby zawsze możesz ograniczyć zakres, umieszczając styl w zasobach jednego z elementów potomnych okna (tj. A Grid), który zawiera docelowe pola tekstowe.

Nik
źródło
2

Jeśli używasz MultiBinding z TextBox, musisz użyć BindingOperations.GetMultiBindingExpressionmetody zamiast 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
źródło
1

To działa dla mnie:

        <TextBox                 
            Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Ok}"/>
            </TextBox.InputBindings>
        </TextBox>
Valery
źródło
0

Osobiście uważam, że posiadanie rozszerzenia znaczników jest czystszym podejściem.

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>
ja do ciebie
źródło
0

Inne rozwiązanie (nie używam xaml, ale myślę, że nadal jest całkiem czysty).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Hauk
źródło