绑定文本框到回车键按下事件

123

TextBox 的默认数据绑定模式是 TwoWay,只有在 TextBox 失去焦点时才会将文本提交到属性。

是否有一种简单的 XAML 方式,在按下 Enter 键后自动进行数据绑定?我知道在代码后台很容易实现,但想象一下如果这个 TextBox 在某个复杂的 DataTemplate 中,那么这样做就不太方便了。

12个回答

152

你可以通过创建一个附加行为实现纯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"/>

(您只需确保在XAML文件的根元素中包含一个xmlns clr-namespace引用,指向InputBindingsManager所在的任何命名空间中的"b"。)


11
为了节省未来读者的时间,我在我的项目中使用了这个方法。上面提供的XAML示例并没有正常工作。源代码在每次字符更改时都会更新。将“UpdateSourceTrigger=PropertyChanged”更改为“UpdateSourceTrigger=Explicit”可以解决问题。现在一切按照预期进行。 - ihake
1
@ihake:我认为您推荐的更改也会防止在失去焦点时更新。 - VoteCoffee
6
当输入实数时,使用UpdateSourceTrigger=PropertyChanged可能会不方便。例如,当绑定到一个浮点数时,“3.”会引起问题。我建议除了使用附加行为之外,不要指定UpdateSourceTrigger(或将其设置为LostFocus)。这样可以兼顾两者的优点。 - David Hollinshead
3
干得好!小问题:当UpdatePropertySourceWhenEnterPressed从一个有效值更改为另一个有效值时,您不必要地取消订阅和重新订阅PreviewKeyDown事件。相反,您只需要检查e.NewValue是否为null。如果不是null,则订阅;否则,如果为null,则取消订阅。 - Nicholas Miller
1
我喜欢这个解决方案,感谢您发布它!对于任何需要将此行为附加到应用程序中的多个文本框的人,我在此答案上发布了一个扩展,告诉您如何轻松实现。在此处查看帖子 - Nik
显示剩余2条评论

70

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

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

然后我只需在 XAML 中添加此内容作为 KeyUp 事件处理程序:

<TextBox Text="{Binding TextValue1}" KeyUp="TextBox_KeyEnterUpdate" />
<TextBox Text="{Binding TextValue2}" KeyUp="TextBox_KeyEnterUpdate" />
事件处理程序使用其 sender 引用来更新自己的绑定。由于事件处理程序是自包含的,因此它应该在复杂的 DataTemplate 中工作。现在可以将这个事件处理程序添加到需要此功能的所有文本框中。

10
有些东西告诉我这篇文章被低估了。很多答案因为它们与 XAML 相关而受欢迎。但是这篇文章似乎在大多数情况下可以节省空间。 - j riv
4
这也可以让你不用管UpdateSourceTrigger,如果你已经费尽心思让你的TextBox绑定以你想要的方式工作(样式、验证、双向绑定等),但目前按下Enter键后无法接收输入。 - Jamin
2
这是我找到的最干净的方法,我认为应该标记为答案。在发送者上添加了额外的空值检查,对Key.Return进行了检查(我的键盘上的Enter键返回Key.Return),并添加了Keyboard.ClearFocus()以在更新源属性后从TextBox中移除焦点。我已经对等待同行审查的答案进行了编辑。 - Oystein
2
同意上面的评论。但对我来说,KeyDown事件更合适。 - Allender
1
我在XAML中使用了KeyBinding来触发这个方法,因为我的UI有一个“默认”控件来捕获回车键。你需要在这个文本框中捕获它,以阻止它向上传播到“默认”控件的UI树中。 - CAD bloke
显示剩余3条评论

47

我不相信有任何“纯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中,你永远不应该使用继承——它会混乱样式,并且不如附加行为灵活。例如,使用附加行为,你可以在同一个文本框上同时拥有水印和在输入回车时更新的功能。 - Mikhail Poda

16

如果将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

如果您在VM中使用DelegateCommand实现,则会导致您的Command看起来像这样:

    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的依赖。这取决于您的代码准则有多严格。


1
不错。非常干净。如果涉及到多绑定,您可能需要使用BindingOperations.GetBindingExpressionBase(tBox, prop); 然后调用binding.UpdateTarget(); - VoteCoffee

6

这不是对原问题的回答,而是对@Samuel Jack的接受的答案进行了扩展。我在自己的应用程序中实现了以下内容,并对Samuel的解决方案的优雅感到惊叹。它非常干净,非常可重用,因为它可以用于任何控件,而不仅仅是TextBox。我认为这应该与社区分享。

如果您有一个具有数千个需要在输入完成后更新绑定源的TextBoxes的窗口,则可以通过将下面的XAML添加到您的Window Resources 中而不是将其附加到每个TextBox来将此行为附加到所有TextBox。当然,首先您必须按照Samuel的帖子实现附加行为。

<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)的资源中,始终限制范围。

5

以下是我认为非常简单明了的方法,比添加AttachedBehaviour(也是有效的解决方案)更容易。我们使用默认的UpdateSourceTrigger(对于TextBox是LostFocus),然后添加绑定到一个命令的Enter Key的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

并且文本框与属性绑定

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

目前这个方法似乎很有效,可以捕捉到文本框中的回车事件。


3
这对我有效:
注意:本文中的html标签已被保留。
        <TextBox                 
            Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
            <TextBox.InputBindings>
                <KeyBinding Key="Return" 
                            Command="{Binding Ok}"/>
            </TextBox.InputBindings>
        </TextBox>

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

我个人认为拥有一个标记扩展是更加简洁的方法。

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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接