Привязать TextBox к нажатию клавиши Enter
Привязка данных по умолчанию на TextBox
является TwoWay
и он передает текст в свойство только тогда, когда TextBox
потерял фокус
Есть ли какой-нибудь простой способ XAML для привязки данных, когда я нажимаю клавишу Enter на TextBox
?. Я знаю, что это довольно легко сделать в коде, но представьте, если это TextBox
внутри какой-то сложной DataTemplate
,
12 ответов
Вы можете создать себе чистый 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).
Вот как я решил эту проблему. Я создал специальный обработчик событий, который вошел в код:
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. Этот один обработчик событий теперь можно добавить ко всем текстовым полям, которым требуется эта функция.
Я не верю, что есть какой-то "чистый XAML" способ сделать то, что вы описываете. Вы можете настроить привязку так, чтобы она обновлялась всякий раз, когда текст в TextBox изменяется (а не когда TextBox теряет фокус), устанавливая свойство UpdateSourceTrigger, например так:
<TextBox Name="itemNameTextBox"
Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />
Если вы установите UpdateSourceTrigger в "Явное", а затем обработаете событие TextBox PreviewKeyDown (ища клавишу Enter), то вы можете достичь того, что вы хотите, но для этого потребуется код-позади. Возможно, какое-то прикрепленное свойство (аналогично моему свойству EnterKeyTraversal) будет работать для вас.
Вы можете легко создать свой собственный элемент управления, унаследованный от 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}" />
Если вы объедините решения Бена и 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
в вашей виртуальной машине. Это зависит от того, насколько строгие правила вашего кода.
Это не ответ на оригинальный вопрос, а скорее продолжение принятого ответа @Samuel Jack. Я сделал следующее в моем собственном заявлении, и был в восторге от элегантности решения Самуила. Он очень чистый и многоразовый, так как может использоваться на любом контроле, а не только на TextBox
, Я думал, что это должно быть передано сообществу.
Если у вас есть окно с тысячей TextBoxes
что все требуют обновления источника привязки при вводе, вы можете присоединить это поведение ко всем из них, включив ниже XAML в ваш Window
Resources
вместо того, чтобы прикреплять его к каждому TextBox. Сначала вы должны реализовать прикрепленное поведение, как указано в сообщении Самуила.
<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>
При необходимости вы всегда можете ограничить область видимости, поместив стиль в ресурсы одного из дочерних элементов окна (т. Е. Grid
), который содержит целевые текстовые поля.
Вот подход, который мне кажется довольно простым и более простым, чем добавление AttachedBehaviour (которое также является допустимым решением). Мы используем UpdateSourceTrigger по умолчанию (LostFocus for TextBox), а затем добавляем привязку InputBinding к клавише ввода, привязанной к команде.
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.
Это работает для меня:
<TextBox
Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding Ok}"/>
</TextBox.InputBindings>
</TextBox>
Более просто, просто установите UpdateSourceTrigger
в PropertyChanged
в вашем TextBox
Связывание без добавления чего-либо в коде позади. Именно так:
<TextBox Text="{Binding Path=BoundProperty, UpdateSourceTrigger=PropertyChanged}"/>
Меня устраивает.
Если вы используете 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();
}
}
Я лично думаю, что расширение разметки - более чистый подход.
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>
Здесь довольно элегантно ответили, используя прикрепленное поведение, мой предпочтительный метод для чего угодно.
WPF как сделать так, чтобы текстовое поле теряло фокус после нажатия Enter
Другое решение (не использующее xaml, но все же довольно чистое, я думаю).
class ReturnKeyTextBox : TextBox
{
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.Key == Key.Return)
GetBindingExpression(TextProperty).UpdateSource();
}
}