Bind TextBox on Enter-key press

The default databinding on TextBox is TwoWay and it commits the text to the property only when TextBox lost its focus.

Is there any easy XAML way to make the databinding happen when I press the Enter key on the TextBox?. I know it is pretty easy to do in the code behind, but imagine if this TextBox is inside some complex DataTemplate.

112358 次浏览

我不相信有任何“纯 XAML”的方法来完成您所描述的工作。通过设置 更新源触发器属性,您可以设置一个绑定,以便当 TextBox 中的文本发生变化(而不是当 TextBox 失去焦点时)时,绑定就会更新,如下所示:

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

如果您将 UpdateSourceTrigger 设置为“ Explick”,然后处理 TextBox 的 PreviewKeyDown 事件(寻找回车键) ,那么您可以实现您想要的,但它需要代码隐藏。也许某种附加属性(类似于我的 进入钥匙穿越属性)会为您工作。

You can make yourself a pure XAML approach by creating an 依恋行为.

就像这样:

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

(您只需要确保在 XAML 文件的根元素中包含“ b”的 xmlns clr 名称空间引用,指向您将 InputBindingsManager 放入的名称空间)。

您可以轻松地创建从 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();
}
}
}
}

可能有一种方法可以绕过这个步骤,但是否则您应该像下面这样绑定(使用 Explect) :

<custom:SubmitTextBox
Text="{Binding Path=BoundProperty, UpdateSourceTrigger=Explicit}" />

如果您在 TextBox 中使用 MultiBinding,则需要使用 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();
}
}

这就是我解决这个问题的方法。我创建了一个特殊的事件处理程序,它进入了后面的代码:

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

Then I just added this as a KeyUp event handler in the XAML:

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

事件处理程序使用它的 sender引用来更新它自己的绑定。因为事件处理程序是自包含的,所以它应该在复杂的 DataTemplate 中工作。这个事件处理程序现在可以添加到所有需要这个特性的文本框中。

Here is an approach that to me seems quite straightforward, and easier that adding an AttachedBehaviour (which is also a valid solution). We use the default UpdateSourceTrigger (LostFocus for TextBox), and then add an InputBinding to the Enter Key, bound to a command.

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

到目前为止,这似乎工作得很好,并在 TextBox 中捕获 Enter Key 事件。

如果将 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看起来像这样(如果您在 VM 中使用 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,最好的是代码隐藏中没有代码,尽管您可能希望将它放在它自己的类中,这样在 VM 中就不会有对 System.Windows.Controls的依赖。这取决于您的代码准则有多严格。

这对我有用:

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

在这里用附加行为非常优雅地回答了这个问题,我对几乎任何事情都喜欢使用这种方法。

WPF 如何让文本框在点击回车后失去焦点

This is not an answer to the original question, but rather an extension of the 接受的答案 by @Samuel Jack. I did the following in my own application, and was in awe of the elegance of Samuel's solution. It is very clean, and very reusable, as it can be used on any control, not just the TextBox. I thought this should be shared with the community.

如果你有一个包含1000个 TextBoxes的窗口,所有这些都需要在回车时更新绑定源,你可以通过将下面的 XAML 包含到你的 Window Resources中而不是将它附加到每个 TextBox 中来将这种行为附加到所有的窗口。当然,首先您必须按照 Samuel's post实现附加的行为。

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

如果需要,您可以通过将 Style 放到包含目标 TextBox 的 Window 的子元素(即 Grid)的参考资料中来限制范围。

我个人认为使用标记扩展是一种更清晰的方法。

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>

A different solution (not using xaml but still quite clean I think).

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