在Enter键上绑定文本框


108

默认的数据绑定TextBoxTwoWay,仅当TextBox失去焦点时才将文本提交给属性。

当我按?Enter键时,是否有任何简单的XAML方法使数据绑定发生TextBox。我知道在后面的代码中很容易做到,但是请想象这TextBox是否复杂DataTemplate

Answers:


137

通过创建附加行为,可以使自己成为纯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文件的根元素中包含对“ b”的xmlns clr-namespace引用,指向您将InputBindingsManager放入的任何名称空间)。


11
为了节省将来的读者几分钟的时间,我只是在项目中使用了此方法,而上面提供的示例XAML却无法正常工作。每次字符更改都会更新源。将“ UpdateSourceTrigger = PropertyChanged”更改为“ UpdateSourceTrigger = Explicit”可以解决此问题。现在,这一切都可以按需进行了。
ihake

1
@ihake:我认为您建议的更改也将防止由于丢失焦点而更新
VoteCoffee 2014年

4
输入实数时,UpdateSourceTrigger = PropertyChanged可能会带来不便。例如“ 3”。绑定到浮点数时会引起问题。除了附加的行为,我建议不要指定UpdateSourceTrigger(或将其设置为LostFocus)。这提供了两全其美的优势。
David Hollinshead,2015年

2
优秀作品!小巧的选择:当UpdatePropertySourceWhenEnterPressed从有效值更改为其他有效值时,您将PreviewKeyDown不必要地取消订阅并重新订阅该事件。相反,您所需要做的只是检查是否为e.NewValueis null。如果没有null,则订阅;否则,如果null,则退订。
Nicholas Miller

1
我喜欢这个解决方案,感谢您发布!对于需要将此行为附加到应用程序中众多TextBox上的任何人,我都发布了此答案的扩展,其中介绍了如何轻松实现这一目标。在此处查看帖子
Nik

50

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

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中工作。现在可以将此事件处理程序添加到需要此功能的所有文本框中。


8
有人告诉我这是被低估的帖子。许多答案很受欢迎,因为它们是“ XAML-y”。但这似乎在大多数情况下都可以节省空间。
j riv 2015年

3
+1这还可以让您不理会UpdateSourceTrigger,以防万一您已经辛辛苦苦地使TextBox绑定按照您想要的方式运行(使用样式,验证,双向等),但是当前仅在按Enter键后才收到输入。
Jamin

2
这是我发现的最干净的方法,我认为应该将其标记为答案。添加了对发件人的其他空检查,对Key.Return的检查(我的键盘上的Enter键返回Key.Return),以及Keyboard.ClearFocus()在更新source属性后从TextBox移除焦点。我已对等待同行评审的答案进行了编辑。
Oystein

2
同意以上评论。但对我而言,KeyDown事件更合适
Allender

1
KeyBinding在XAML中使用了此方法,因为我的UI具有捕获输入键的“默认”控件。您需要在此TextBox中捕获它,以阻止它在UI树中传播到“默认”控件。
CAD bloke

46

我不相信有任何“纯XAML”方式可以完成您所描述的事情。您可以设置一个绑定,以便通过设置UpdateSourceTrigger属性,在TextBox中的文本发生更改时(而不是在TextBox失去焦点时)进行更新,如下所示:

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

如果将UpdateSourceTrigger设置为“ Explicit”,然后处理TextBox的PreviewKeyDown事件(查找Enter键),则可以实现所需的功能,但是需要代码隐藏。也许某种附加属性(类似于我的EnterKeyTraversal属性)可以为您工作。


3
该答案可能比标记为答案的答案更简单,但是它有一些局限性。您想要在bound属性的set-function中进行某种检查的图像(检查输入是否有效)。您不想在用户按下每个键之后调用该检查功能吗?(特别是当该功能需要一些时间时)。
sth_Weird 2015年

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中,您永远不应使用继承-继承会使样式混乱,并且不如“附加行为”灵活。例如,对于“附加行为”,您可以在同一文本框中同时使用水印和UpdateOnEnter。
米哈伊尔(Mikhail)

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在VM中使用-style实现):

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


真好 很干净。如果涉及多重绑定,则可能需要使用BindingOperations.GetBindingExpressionBase(tBox,prop);。然后binding.UpdateTarget();
VoteCoffee 2014年

4

在我看来,这是一种非常简单的方法,并且比添加AttachedBehaviour(这也是有效的解决方案)更容易。我们使用默认的UpdateSourceTrigger(用于TextBox的LostFocus),然后将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>

然后命令方法是

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

到目前为止,这似乎运行良好,并在TextBox中捕获了Enter Key事件。


4

这不是原始问题的答案,而是@Samuel Jack 接受的答案的扩展。我在自己的应用程序中执行了以下操作,并对Samuel解决方案的优雅敬畏。它非常干净,并且非常可重用,因为它可以在任何控件上使用,而不仅是TextBox。我认为应该与社区分享。

如果您有一个包含成千上万个Window的窗口TextBoxes,这些窗口都需要在Enter时更新绑定源,则可以通过将下面的XAML包括在您的XAML中Window Resources而不是将其附加到每个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包含目标TextBoxes 的Window子元素之一(即a )的资源中来限制范围。


2

如果您将MultiBinding与TextBox一起使用,则需要使用BindingOperations.GetMultiBindingExpressionmethod而不是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>

0

一个不同的解决方案(我不使用xaml,但仍然很干净)。

class ReturnKeyTextBox : TextBox
{
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (e.Key == Key.Return)
            GetBindingExpression(TextProperty).UpdateSource();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.