TextBox
的默认数据绑定模式是 TwoWay
,只有在 TextBox
失去焦点时才会将文本提交到属性。
是否有一种简单的 XAML 方式,在按下 Enter 键后自动进行数据绑定?我知道在代码后台很容易实现,但想象一下如果这个 TextBox
在某个复杂的 DataTemplate
中,那么这样做就不太方便了。
你可以通过创建一个附加行为实现纯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"。)
这是我解决问题的方法。我创建了一个特殊的事件处理程序,进入了代码后台:
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 中工作。现在可以将这个事件处理程序添加到需要此功能的所有文本框中。KeyBinding
来触发这个方法,因为我的UI有一个“默认”控件来捕获回车键。你需要在这个文本框中捕获它,以阻止它向上传播到“默认”控件的UI树中。 - CAD bloke我不相信有任何“纯XAML”的方法来完成你所描述的操作。你可以设置一个绑定,使其在TextBox中的文本更改时更新(而不是在TextBox失去焦点时),方法是设置UpdateSourceTrigger属性,就像这样:
<TextBox Name="itemNameTextBox"
Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />
你可以轻松地创建一个从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}" />
如果将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
的依赖。这取决于您的代码准则有多严格。
这不是对原问题的回答,而是对@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
)的资源中,始终限制范围。以下是我认为非常简单明了的方法,比添加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>
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
Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding Ok}"/>
</TextBox.InputBindings>
</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>
UpdatePropertySourceWhenEnterPressed
从一个有效值更改为另一个有效值时,您不必要地取消订阅和重新订阅PreviewKeyDown
事件。相反,您只需要检查e.NewValue
是否为null
。如果不是null
,则订阅;否则,如果为null
,则取消订阅。 - Nicholas Miller