有没有办法在触发命令之前或触发命令时更新绑定?我有一些可以使用命令编辑和保存的文本字段,可通过键盘快捷键访问。由于绑定通常仅在文本字段失去焦点时更新,因此在按键保存数据时不会保留最后一次更改。相反,我必须先从文本字段中跳出以使其更新然后保存。
有没有办法以优雅的方式强制更新?我正在使用 MVVM(但不是任何 MVVM 框架),所以我想将 UI 特定的东西保留在命令代码之外。此外,我真的不想更改绑定以在每次更改时更新,只有在失去焦点时才更新它很好。
如果当前元素是一个TextBox
. 您可以对其他控件执行类似的操作,但根据我的经验,我只遇到过TextBox
.
// if the current focused element is textbox then updates the source.
var focusedElement = Keyboard.FocusedElement as FrameworkElement;
if (focusedElement is TextBox)
{
var expression = focusedElement.GetBindingExpression(TextBox.TextProperty);
if (expression != null) expression.UpdateSource();
}
在您的上TextBox
,您需要UpdateSourceTrigger
在您的文本绑定上设置 ,它定义了何时使用文本框值更新源。默认情况下,它LostFocus
用于Text
属性,这正是正在发生的事情 - 它仅在失去焦点时更新源。您应该设置 to 的值,UpdateSourceTrigger
并且PropertyChanged
每次文本框值更改时都会更新。
例如
<TextBox ... Text="{Binding Foo, UpdateSourceTrigger=PropertyChanged}"/>
Path是使用Binding命令时的默认属性,所以上面等于Path=Foo
这是我用于类似情况的类:
public class TextBoxUpdatesTextBindingOnPropertyChanged : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += TextBox_TextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_TextChanged;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
}
}
XAML:
<TextBox Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=Explicit}">
<i:Interaction.Behaviors>
<h:TextBoxUpdatesTextBindingOnPropertyChanged />
</i:Interaction.Behaviors>
</TextBox>
调用OnPropertyChanged("YourPropertyName")
将提醒视图更新绑定值。
否则请查看此答案:WPF TextBox DataBind on EnterKey press。
我现在通过在请求值之前明确地将焦点设置到其他元素来解决这个问题。这显然会使当前具有焦点的元素失去它并更新绑定。
为了设定焦点,我写了一个附加属性,灵感来自其他问题的答案。此外,连同我的另一个问题,我使这有点自动化。
所以要使用它,我基本上将我的属性附加到一个元素上,在本例中是一个选项卡控件:
<TabControl c:Util.ShouldFocus="{Binding ShouldClearFocus}">
在我的视图模型中,我有一个简单的布尔属性ShouldClearFocus
,它是提高 a 的标准属性PropertyChangedEvent
,因此数据绑定有效。然后我简单地设置ShouldClearFocus
为何true
时我想重置焦点。附加属性会自动设置焦点并再次重置属性值。这样我就可以继续设置ShouldClearFocus
,而不必false
在两者之间进行设置。
附加属性是一个标准实现,它作为其更改处理程序:
public static void ShouldFocusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(bool)e.NewValue || !(obj is FrameworkElement))
return;
FrameworkElement element = (FrameworkElement)obj;
if (element.Focusable)
element.Focus();
// reset value
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(obj, ShouldFocusProperty);
if (bindingExpression != null)
{
PropertyInfo property = bindingExpression.DataItem.GetType().GetProperty(bindingExpression.ParentBinding.Path.Path);
if (property != null)
property.SetValue(bindingExpression.DataItem, false, null);
}
else
SetShouldFocus(obj, false);
}
我在我的项目中使用以下内容。问题是按钮没有收到焦点。
在我的 app.xaml.cs
EventManager.RegisterClassHandler(typeof(Button), ButtonBase.ClickEvent, new RoutedEventHandler(ButtonClick));
private void ButtonClick(object sender, RoutedEventArgs e)
{
if (sender != null && sender is Button)
{
(sender as Button).Focus();
}
}
我认为最好的方法是设置绑定UpdateSourceTrigger=PropertyChanged
,然后使用Reactive Extensions
来限制事件流。这样,属性只会在用户停止输入一段时间后更新(1/2 秒是一个很好的数量)。
这很好,因为该属性不会一直更新,但它也会更新而无需更改焦点。正是你想要的。
这里有一个 MVVM 扩展库,它具有可绑定的 IObservables 。他们在这里的例子正是我所建议的。
你要求优雅:就在这里。而且它也没有那么多工作。
在 UI 上收听命令调用可能会很棘手(如果不是不可能的话,因为到目前为止还没有答案管理它)。我建议使用Messenger
诸如 MvvmLight 中的模式,而不是 hacky 翻转附加属性:
public class ForceUpdateBindingsMessage
{
public ViewModelBase ViewModel { get; }
public ForceUpdateBindingsMessage(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
}
// ViewModel
public ICommand SaveCommand => _saveCommand ?? (_saveCommand = new RelayCommand(() =>
{
// ...push binding updating responsibility to whoever listens to this (desperate) message
MessengerInstance.Send(new ForceUpdateBindingsMessage(this));
// the bindings should be up-to-date now
}));
现在到“UpdateSource”部分,ForceUpdateBindingsMessage
应该由一些父控件处理,我选择MainWindow
让 VeiwModel 的绑定随处更新,同时将更新限制为 invoking 的属性ViewModel
:
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<ForceUpdateBindingsMessage>(this, msg =>
{
UpdateBindingsOfViewModel(ViewModelBase viewModel);
});
}
private void UpdateBindingsOfViewModel(ViewModelBase viewModel)
{
foreach (var tb in this.GetChildren<TextBox>())
{
var binding = tb.GetBindingExpression(TextBox.TextProperty);
if (binding?.DataItem == msg.ViewModel)
{
binding.UpdateSource();
}
}
}
GetChildren<T>
常用的扩展辅助方法在哪里:
public static IEnumerable<T> GetChildren<T>(this Visual parent) where T : Visual
{
if (parent == null)
yield break;
var queue = new Queue<Visual>();
queue.Enqueue(parent);
while (queue.Count > 0)
{
var element = queue.Dequeue();
if (element is T tchild)
yield return tchild;
int numVisuals = VisualTreeHelper.GetChildrenCount(element);
for (int i = 0; i < numVisuals; i++)
{
if (VisualTreeHelper.GetChild(element, i) is Visual child)
{
queue.Enqueue(child);
}
}
}
}