我发现了一个有趣的问题,我在WinForms中首次发现,并在Silverlight中再次找到,而且在数据绑定方面也很可能是WPF。
我有一个包含多个标签的标签控件。当用户单击选项卡时,每次都应该有效,然后才允许用户从选项卡切换。
例如,用户位于更新的文本框中。在控件失去焦点之前,不会刷新文本框的绑定。当光标从控件移开时会发生焦点丢失,并且焦点将被赋予另一个控件。
在这种情况下,用户选中一个控件(让我们使用此示例的文本框),并更新文本框。此时数据绑定尚未刷新控件,因此VM尚未看到更改。然后,用户使用鼠标单击控件的下一个选项卡。
此时事情变得有趣。我使用了PreviewSelectionChanged(Telerik RadTabControl),因为我想在跳转到下一个标签之前检查一下,它还让我能够取消事件。
但是,当我查看VM时,在这种情况下,它仍然没有更新的数据。我看到虚拟机很干净,然后继续跳转到下一个标签页。
然而,只要此事件结束,数据绑定就会刷新,并且VM会更新。现在怎么办?事件不同步!当鼠标用于单击下一个选项卡时,文本框应该丢失焦点,刷新它的绑定,然后单击选项卡的预览!我们赶紧回去说哎呀,我们没有及时赶上那个!
我认为我找到了一个有趣的解决这个问题的方法 - 但我并不是百分之百确定它会100%有效。我取消当前事件,但后来我使用Dispatcher并创建一个委托指向另一个方法,该方法具有与当前事件相同的签名。 Dispatcher会将此消息添加到消息泵中,此时此消息泵(希望?)会在VM更新的消息后面...
我的两个问题是: 1)我假设当鼠标离开控件时文本框控件没有刷新,或者被触发的进程太慢,因此在数据绑定之前预览消息在泵上 - 无论哪种方式我都看到这个是一个重大问题。
2)解决方法是一个很好的解决方案吗?
答案 0 :(得分:2)
好的,首先回答问题1:
仅仅因为鼠标离开了文本框区域,并不意味着文本框失去了焦点。一旦其他东西得到关注,它就会失去焦点。例如,如果您将鼠标移出文本框并单击页面上的其他控件(它可以是从滚动查看器到另一个文本框等的任何内容),那么您的文本框将失去焦点。
现在,基于此,事件不会以错误的顺序发生。会发生什么;你在另一个标签上的点击事件触发文本框失去焦点(并发生数据绑定)和移动到下一帧,并基于此,你基本上得到一个竞争条件,其中移动到下一个选项卡发生在进行数据绑定之前。
关于问题2:
您可以做的是,将UpdateSourceTrigger设置为Explicit,然后您将被迫拥有某种text_changed事件并手动更新绑定。
您可以阅读有关here的更多信息。它可能不是最完整的解释,但却是一个很好的起点。
另一方面,您可以将某些事件与文本框关联,并强制文本框失去对这些事件的关注(例如鼠标移出)。
答案 1 :(得分:1)
只是一个想法:为什么不在VM的PropertyChanged事件中做所有事情?
protected override void OnThisViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
if(e.PropertyName == "WhateverProperty") {
//Do your magic here for whatever you want to set
}
}
将您的TabItem绑定到将控制的集合是否被禁用。
<sdk:TabControl>
<sdk:TabItem IsEnabled="{Binding SomeProperty, Converter={AmIDisabledOrWhatConverter}}" />
</sdk:TabControl>
这样,只要在vm中链接属性,就会触发所有内容。没有更多的时间问题,因为一切都在虚拟机上。
只是我的两分钱。
答案 2 :(得分:1)
这里存在设计缺陷,您正试图解决缺陷而不是修复缺陷。您不必弄清楚如何取消选项卡上的Click事件。该选项卡不应该首先处理Click事件。
一般来说,如果用户点击控件不合法,则不应启用该控件。应该禁用该选项卡,直到视图模型的状态有效。
您的视图模型应该公开一个用于导航到下一个选项卡的命令,并且该选项卡应该绑定到该命令。只有当前选项卡上的视图模型的状态有效时,命令的CanExecute
方法才会返回true。
这不能解决您的其他问题,即Silverlight不支持UpdateSourceTrigger="PropertyChanged"
开箱即用。但这是一个已解决的问题(here就是一个例子)。
请注意,如果您实现了在应用程序中处理此向导式导航的命令,那么您可以在路上更改视图以使用除选项卡控件之外的其他内容(例如,使用导航按钮,如实际向导,或者像Telerik的PanelBar这样的东西,而不必使用事件处理程序。
答案 3 :(得分:0)
更改绑定以包含UpdateSourceTrigger =“PropertyChanged”。
这将确保您的数据源在每次击键时更新,而不仅仅是LostFocus。
答案 4 :(得分:0)
MyOwnTextBox()
{
this.TextChanged += (s, e) => UpdateText();
}
private void UpdateText()
{
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null && be.ParentBinding.Mode == BindingModes.TwoWay)
{
be.UpdateSource();
}
}
我正在使用这个类,它会动态更新我的绑定,但是存在空字符串和空值的问题,如果你的目标属性是可以为空的字符串,那么这将更新目标属性中的空字符串。您可以通过使用某种字符串转换器来获得解决方法,在可以为空的字符串的情况下,它将使用null而不是空字符串。