Answers:
通过创建附加行为,可以使自己成为纯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
is 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
在XAML中使用了此方法,因为我的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();
}
}
}
}
可能有一种绕过此步骤的方法,但是否则,您应该像这样绑定(使用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
。
这会导致您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 中没有依赖项。这取决于您的代码准则有多严格。
在我看来,这是一种非常简单的方法,并且比添加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事件。
这不是原始问题的答案,而是@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 )的资源中来限制范围。
如果您将MultiBinding与TextBox一起使用,则需要使用BindingOperations.GetMultiBindingExpression
method而不是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>