如何不丢失绑定源更新?

Fyo*_*kin 11 c# data-binding wpf binding datasource

假设我有一个带文本框和OK/Cancel按钮的模态对话框.它建立在MVVM上 - 即它有一个ViewModel对象,该对象具有文本框绑定的字符串属性.

说,我在文本框中输入一些文本,然后抓住我的鼠标并单击"确定".一切正常:在单击时,文本框失去焦点,这会导致绑定引擎更新ViewModel的属性.我得到了我的数据,每个人都很开心.

现在假设我不使用鼠标.相反,我只是按下Enter了键盘.这也导致"确定"按钮"点击",因为它被标记为IsDefault="True".但猜猜怎么了?文本框没有按没有在这种情况下失去焦点,因此,绑定引擎仍然天真无知,我没有得到我的数据.党!

同一场景的另一个变体:假设我在主窗口中有一个数据输入表单,在其中输入一些数据,然后点击Ctrl+S"保存".你猜怎么着?我的最新条目没有得到保存!

这可有点通过使用补救UpdateSourceTrigger=PropertyChanged,但是这是不可能的.

一个明显的例子是使用StringFormatwith binding - 当我试图输入时,文本不断跳回"格式化"状态.

我自己遇到的另一个案例是,当我在viewmodel的属性设置器中进行一些耗时的处理时,我只想在用户"完成"输入文本时执行它.

这似乎是一个永恒的问题:我记得自从我开始使用交互式界面以来,从很久以前就试图系统地解决它,但我从来没有成功过.在过去,我总是最终使用某种黑客 - 例如,向每个"演示者"(如"MVP")添加"EnsureDataSaved"方法,并在"关键"点或类似的地方调用它. ..

但是对于WPF的所有酷炫技术以及空洞的炒作,我预计他们会想出一些好的解决方案.

Ric*_*key 3

在关键点,您可以强制绑定推送到您的视图模型:

var textBox = Keyboard.FocusedElement as TextBox;
BindingOperations.GetBindingExpression(textBox, TextBox.TextProperty).UpdateSource();
Run Code Online (Sandbox Code Playgroud)

编辑:

好吧,既然你不想被黑客攻击,我们就必须面对丑陋的事实:

  • 为了实现干净的视图,视图模型公开的属性应该对频繁的绑定更新友好。

我们可以用文本编辑器来类比。如果应用程序是一个绑定到磁盘上文件的巨大文本框,则每次击键都会导致写入整个文件。甚至不需要储蓄的概念。这是完全正确的,但效率极低。我们都立即看到视图模型需要公开一个缓冲区以供视图绑定,这重新引入了保存的概念并在视图模型中强制进行状态处理。

然而,我们发现这仍然不够有效。即使对于中等大小的文件,每次击键时更新整个文件缓冲区的开销也变得难以忍受。接下来,我们在视图模型中公开命令以有效地操作缓冲区,而不会实际与视图交换整个缓冲区。

因此我们得出的结论是,为了通过纯 MVVM 实现效率和响应能力,我们需要公开一个高效的视图模型。这意味着所有文本框都可以绑定到属性,而不会产生不良影响。 但是,这也意味着您必须将状态下推到视图模型中才能处理该问题。这没关系,因为视图模型不是模型;而是视图模型。它的工作是处理视图的需求。

确实,我们可以通过利用绑定焦点变化等快捷方式来快速构建用户界面原型。但是绑定焦点变化可能会在实际应用中产生负面后果,如果是这样,那么我们就不应该使用它。

还有什么选择呢?使属性易于频繁更新。称其为与旧的低效财产相同的名称。使用慢速属性以及取决于视图模型状态的逻辑来实现快速属性。视图模型获取保存命令。它知道快速属性是否已被推送到慢速属性。它可以决定是否将慢速属性同步到模型的时间和地点。

但你会说,我们不是刚刚将 hack 从视图转移到了视图模型吗?不,我们失去了一些优雅和简单,但回到文本编辑器的类比。我们必须解决问题,而视图模型的工作就是解决它。

如果我们想使用纯 MVVM 并且需要效率和响应能力,那么像让我们避免更新绑定源直到元素失去焦点这样蹩脚的启发式方法将无济于事。他们解决的问题和带来的问题一样多。在这种情况下,我们应该让视图模型完成它的工作,即使这意味着增加复杂性。

假设我们接受它,我们如何管理复杂性?我们可以实现一个通用的包装实用程序类来缓冲慢属性并允许视图模型挂钩其 get 和 set 方法。我们的实用程序类可以自动注册保存命令事件,以减少视图模型中的样板代码量。

如果我们做得正确,那么视图模型中足够快以与属性更改绑定一起使用的所有部分都将仍然相同,而其他值得提出问题“这个属性是否太慢了?”的部分将仍然相同。将有少量代码来解决该问题,并且观点并不明智。