Привязать TextBox к нажатию клавиши Enter

110

По умолчанию привязки на TextBoxэто TwoWayи обязывает текст в собственность только тогда , когда TextBoxпотеряли свое внимание.

Есть ли какой-либо простой способ XAML сделать привязку данных, когда я нажимаю Enterклавишу на TextBox?. Я знаю, что это довольно легко сделать в коде позади, но представьте, если это TextBoxвнутри какого-то сложного DataTemplate.

Джоби Джой
источник

Ответы:

139

Вы можете реализовать подход на чистом XAML, создав прикрепленное поведение .

Что-то вроде этого:

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

Затем в своем XAML вы устанавливаете InputBindingsManager.UpdatePropertySourceWhenEnterPressedPropertyсвойство на то, которое вы хотите обновлять при Enterнажатии клавиши. Как это

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

(Вам просто нужно обязательно включить ссылку на пространство имен xmlns clr для «b» в корневой элемент вашего файла XAML, указывающий на то, в какое пространство имен вы помещаете InputBindingsManager).

Сэмюэл Джек
источник
11
Чтобы сэкономить будущим читателям несколько минут возни, я просто использовал это в своем проекте, и приведенный выше пример XAML работал неправильно. Источник обновлялся при каждой смене персонажа. Изменение «UpdateSourceTrigger = PropertyChanged» на «UpdateSourceTrigger = Explicit» устранило проблему. Теперь все работает как надо.
ihake
1
@ihake: Я думаю, что рекомендованное вами изменение также предотвратит обновление при потере фокуса
VoteCoffee
5
UpdateSourceTrigger = PropertyChanged может быть неудобным при вводе действительного числа. Например, «3». вызывает проблему при привязке к поплавку. Я бы рекомендовал не указывать UpdateSourceTrigger (или установку LostFocus) в дополнение к приложенному поведению. Это дает лучшее из обоих миров.
Дэвид Холлинсхед,
3
Превосходная работа! Маленькая придирка: при UpdatePropertySourceWhenEnterPressedизменении действительного значения на другое действительное значение, вы без необходимости отказываетесь от подписки и повторно подписываетесь на PreviewKeyDownсобытие. Вместо этого все, что вам нужно, это проверить, есть ли e.NewValueэто nullили нет. Если нет null, то подпишитесь; в противном случае если null, то отпишусь.
Николас Миллер
1
Мне нравится это решение, спасибо, что разместили его! Для тех, кому нужно привязать это поведение к многочисленным текстовым полям в вашем приложении, я опубликовал расширение этого ответа о том, как вы можете очень легко этого добиться. См. Сообщение здесь
Nik
51

Вот как я решил эту проблему. Я создал специальный обработчик событий, который вошел в код:

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

Затем я просто добавил это как обработчик событий KeyUp в XAML:

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

Обработчик событий использует свою senderссылку для обновления собственной привязки. Поскольку обработчик событий является автономным, он должен работать в сложном DataTemplate. Этот единственный обработчик событий теперь можно добавить ко всем текстовым полям, которым нужна эта функция.

Бен
источник
8
Что-то мне подсказывает, что это недооцененный пост. Многие ответы популярны, потому что они «XAML-y». Но в большинстве случаев этот, кажется, экономит место.
j riv
3
+1 Это также позволяет вам оставить в покое UpdateSourceTrigger, если вы уже кропотливо настроили привязки TextBox к тому, чтобы вести себя так, как вы хотите (со стилями, проверкой, двусторонним движением и т. Д.), Но в настоящее время просто не получают ввод после нажатия Enter .
Jamin
2
Это самый чистый подход, который я нашел, и, на мой взгляд, его следует отметить как ответ. Добавлена ​​дополнительная нулевая проверка отправителя, проверка Key.Return (клавиша Enter на моей клавиатуре возвращает Key.Return) и Keyboard.ClearFocus () для удаления фокуса из TextBox после обновления свойства источника. Я внес изменения в ответ, ожидающий экспертной оценки.
Oystein
2
Согласитесь с комментариями выше. Но для меня мероприятие KeyDown было более подходящим
Аллендер
1
Я использовал KeyBindingв XAML для запуска этого метода, потому что в моем пользовательском интерфейсе есть элемент управления «По умолчанию», который улавливает клавишу ввода. Вам нужно поймать его в этом TextBox, чтобы остановить его распространение вверх по дереву пользовательского интерфейса до элемента управления «По умолчанию».
CAD парень
46

Я не верю, что существует какой-либо "чистый XAML" способ сделать то, что вы описываете. Вы можете настроить привязку, чтобы она обновлялась всякий раз, когда текст в TextBox изменяется (а не когда TextBox теряет фокус), установив свойство UpdateSourceTrigger , например:

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

Если вы установите для UpdateSourceTrigger значение «Explicit», а затем обработаете событие TextBox PreviewKeyDown (ища клавишу Enter), то вы сможете добиться того, чего хотите, но для этого потребуется код программной части. Возможно, вам подойдет какое-то присоединенное свойство (похожее на мое свойство EnterKeyTraversal).

Мэтт Гамильтон
источник
3
Этот ответ может быть проще, чем ответ, помеченный как ответ, но у него есть некоторые ограничения. Изображение, которое вы хотите проверить в функции установки связанного свойства (проверка правильности ввода). Вы же не хотите вызывать эту функцию проверки после каждой клавиши, нажатой пользователем, не так ли (особенно, когда функция занимает некоторое время)?
sth_Weird
25

Вы можете легко создать свой собственный элемент управления, унаследованный от TextBox, и повторно использовать его в своем проекте.

Что-то похожее на это должно работать:

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

Возможно, есть способ обойти этот шаг, но в противном случае вам следует выполнить привязку следующим образом (используя Explicit):

<custom:SubmitTextBox
    Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />
Райан Версо
источник
9
В WPF / Silverlight никогда не следует использовать наследование - оно портит стили и не так гибко, как Attached Behaviors. Например, с помощью Attached Behaviors у вас может быть как Watermark, так и UpdateOnEnter в одном текстовом поле.
Михаил
14

Если вы объедините решения Ben и ausadmin, вы получите очень дружественное к 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>

... что означает, что вы передаете TextBoxсаму себя как параметр в Command.

Это приводит к тому, что вы Commandвыглядите так (если вы используете DelegateCommandреализацию в стиле в своей виртуальной машине):

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

Эта Commandреализация может использоваться для любого TextBoxкода, а лучше всего - без кода в коде программной части, хотя вы можете поместить его в собственный класс, чтобы System.Windows.Controlsв вашей виртуальной машине не было зависимостей . Это зависит от того, насколько строгие правила вашего кода.

жаба
источник
Ницца. Очень чистый. Если задействовано множественное связывание, вам может потребоваться использовать BindingOperations.GetBindingExpressionBase (tBox, prop); а затем binding.UpdateTarget ();
VoteCoffee
4

Вот подход, который мне кажется довольно простым и более простым, чем добавление AttachedBehaviour (что также является допустимым решением). Мы используем UpdateSourceTrigger по умолчанию (LostFocus для TextBox), а затем добавляем InputBinding к клавише Enter, привязанной к команде.

Xaml выглядит следующим образом

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

Тогда методы Command

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 привязан к свойству

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

Пока что это работает хорошо и улавливает событие Enter Key в TextBox.

аусадмин
источник
4

Это не ответ на исходный вопрос, а скорее продолжение принятого ответа @Samuel Jack. Я сделал следующее в собственном приложении и был поражен элегантностью решения Самуэля. Он очень чистый и многоразовый, так как его можно использовать с любым элементом управления, а не только с TextBox. Я подумал, что этим нужно поделиться с сообществом.

Если у вас есть окно с тысячей, TextBoxesкоторые все требуют обновления источника привязки при вводе, вы можете связать это поведение со всеми из них, включив приведенный ниже XAML в свой, Window Resourcesа не прикрепляя его к каждому текстовому полю. Конечно, сначала вы должны реализовать прикрепленное поведение в соответствии с сообщением Самуэля .

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

При необходимости вы всегда можете ограничить область видимости, поместив стиль в ресурсы одного из дочерних элементов окна (т.е. a Grid), который содержит целевые текстовые поля.

Ник
источник
2

Если вы используете MultiBinding со своим TextBox, вам нужно использовать BindingOperations.GetMultiBindingExpressionметод вместо 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();
     }
}
Акджоши
источник
1

Это работает для меня:

        <TextBox                 
            Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Ok}"/>
            </TextBox.InputBindings>
        </TextBox>
Валерий
источник
0

Я лично считаю, что наличие расширения разметки - более чистый подход.

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>
metoyou
источник
0

Другое решение (не использующее xaml, но все же довольно чистое, я думаю).

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
Хаук
источник