【发布时间】:2010-10-08 11:18:22
【问题描述】:
TextBox 上的默认数据绑定是 TwoWay,它仅在 TextBox 失去焦点时将文本提交到属性。
当我按下TextBox 上的Enter 键时,是否有任何简单的XAML 方法可以进行数据绑定?我知道在后面的代码中很容易做到,但想象一下,如果这个 TextBox 在一些复杂的 DataTemplate 中。
【问题讨论】:
标签: c# wpf xaml .net-3.5 textbox
TextBox 上的默认数据绑定是 TwoWay,它仅在 TextBox 失去焦点时将文本提交到属性。
当我按下TextBox 上的Enter 键时,是否有任何简单的XAML 方法可以进行数据绑定?我知道在后面的代码中很容易做到,但想象一下,如果这个 TextBox 在一些复杂的 DataTemplate 中。
【问题讨论】:
标签: c# wpf xaml .net-3.5 textbox
您可以通过创建attached behaviour 来使自己成为纯 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 的命名空间)。
【讨论】:
UpdatePropertySourceWhenEnterPressed 从有效值更改为不同的有效值时,您将不必要地取消订阅和重新订阅PreviewKeyDown 事件。相反,您只需要检查e.NewValue 是否为null。如果不是null,则订阅;否则,如果null,则取消订阅。
这就是我解决这个问题的方法。我创建了一个特殊的事件处理程序,它进入了后面的代码:
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 有一个“默认”控件可以捕获回车键。您需要在此 TextBox 中捕获它,以阻止它将 UI 树传播到“默认”控件。
我不相信有任何“纯 XAML”方式来做你所描述的事情。您可以通过设置UpdateSourceTrigger 属性来设置绑定,以便在 TextBox 中的文本更改时(而不是在 TextBox 失去焦点时)进行更新,如下所示:
<TextBox Name="itemNameTextBox"
Text="{Binding Path=ItemName, UpdateSourceTrigger=PropertyChanged}" />
如果您将 UpdateSourceTrigger 设置为“Explicit”,然后处理 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();
}
}
}
}
可能有办法绕过这一步,否则你应该像这样绑定(使用显式):
<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。
这导致您的 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 .这取决于您的代码准则有多严格。
【讨论】:
在我看来,这是一种非常简单的方法,并且比添加 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
并且TextBox绑定到Property
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 事件。
【讨论】:
这不是对原始问题的回答,而是@Samuel Jack 对accepted answer 的扩展。我在自己的应用程序中执行了以下操作,并且对 Samuel 解决方案的优雅感到敬畏。它非常干净,非常可重复使用,因为它可以用于任何控件,而不仅仅是TextBox。我认为这应该与社区分享。
如果您有一个包含一千个 TextBoxes 的窗口,所有这些都需要在 Enter 时更新绑定源,您可以通过将下面的 XAML 包含到您的 Window Resources 而不是附加,将此行为附加到所有这些它到每个文本框。首先,您当然必须按照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>
如果需要,您始终可以通过将样式放入包含目标文本框的 Window 子元素之一的资源(即Grid)来限制范围。
【讨论】:
这对我有用:
<TextBox
Text="{Binding Path=UserInput, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding Ok}"/>
</TextBox.InputBindings>
</TextBox>
【讨论】:
如果您在 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();
}
}
【讨论】:
使用附加行为非常优雅地回答了这里,这是我对几乎所有事情的首选方法。
【讨论】:
我个人认为使用标记扩展是一种更简洁的方法。
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>
【讨论】:
另一种解决方案(不使用 xaml,但我认为仍然很干净)。
class ReturnKeyTextBox : TextBox
{
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.Key == Key.Return)
GetBindingExpression(TextProperty).UpdateSource();
}
}
【讨论】: