撤消日记:集中控制被撤消
我正在为 WPF 数据输入屏幕编写一个撤消日志,它将跟踪所有控件的更改。 当用户选择“撤消”时,我不仅要恢复最新的更改,还要将焦点放回其值正在恢复的控件中。 我正在努力寻找重新集中注意力的最佳方法。
我的 ViewModel 将是处理撤消日志记录的部分:ViewModel 的属性设置器将在更新 DataModel 之前捕获一些“之前”状态。 无论如何,“之前”状态需要包含足够的信息,以便我能够稍后将焦点放回。
为了便于说明,假设有两个数据输入字段:地址和城市。 ViewModel 为每个属性都有一个属性,并且 View 为每个属性都有一个绑定到相应 ViewModel 属性的 TextBox。
让我们按照以下示例进行操作,其中用户刚刚在“地址”字段中输入了一个值,然后单击“城市”字段。 我使用默认的 UpdateSourceTrigger.LostFocus 行为,因此当地址文本框失去焦点时,地址更改会被保存。 到目前为止,我已经就如何解决这个问题提出了三种不同的想法,但我对 WPF 的了解不够详细,无法知道如何使它们中的任何一个发挥作用。
我可以忘记 MVVM 风格的数据绑定,并挂钩编辑控件的 LostFocus 事件(或者添加附加行为,或者创建一个包装 TextBox 的自定义控件,或者...)。 在 LostFocus 事件处理程序中,我可以创建一个撤消帧,其中包含对事件发送者的引用。 后来,在撤消之后,我只关注我保存的引用的控件。 这可能是我在 WinForms 中所做的,但在 WPF 中我宁愿坚持使用 ViewModel 模式——我宁愿让日志逻辑存在于 ViewModel 中而不是视图中,如果没有其他的话,是为了可测试性。 所以这个选项不是我的第一选择。
在 ViewModel 的属性设置器中,我可以捕获正在设置的 ViewModel 属性的名称(本例中为“Address”),并将该名称存储在撤消框架中。 稍后,在“撤消”时,我可以遍历视图中的所有控件,查找我能找到的第一个与名为“Address”的属性绑定的内容。 一旦我找到这样的控件,我就会给予它焦点。 这对于我的需要来说已经足够了,因为我不希望有多个控件绑定到同一个 ViewModel 属性。 问题是这需要深入研究绑定表达式,这是我不知道该怎么做的事情。 (它还会引入更多基于名称的后期绑定,如果我重构,这些绑定可能会中断。)
当我的 ViewModel 将更改添加到撤消堆栈时,它可以要求视图层(通过接口)创建一个知道的 Memento哪个控件具有焦点。 在撤消时,日志会要求视图恢复该纪念品。 这里的问题是,当我的 ViewModel 的属性被设置并且我添加撤消框架时,键盘焦点已经移动到城市文本框,因此“创建备忘录”需要比“在哪里”更棘手当前的键盘焦点现在”,我不知道如何完成这个技巧。
有人对上述任何一项工作有任何建议,或者有可能效果更好的替代方法吗?
I'm writing an undo journal for my WPF data-entry screen, which will track changes across all the controls. When the user selects Undo, I want to not only revert the latest change, but put focus back in the control whose value is being reverted. I'm struggling with the best way to put that focus back.
My ViewModel will be the part that handles the undo journaling: the ViewModel's property setters will capture some "before" state before updating the DataModel. One way or another, that "before" state needs to include enough information for me to be able to put the focus back later.
For illustration, let's say there are two data-entry fields: Address and City. The ViewModel has a property for each, and the View has a TextBox for each that's bound to the corresponding ViewModel property.
Let's follow the example where the user has just typed a value into the Address field, and then clicked on the City field. I'm using the default UpdateSourceTrigger.LostFocus behavior, so the Address change gets saved when the Address TextBox loses focus. I've got three different ideas so far on how to approach this, but I don't know enough details about WPF to know how to make any of them work.
I could forget about MVVM-style databinding, and hook the edit controls' LostFocus events (or add an attached behavior, or make a custom control that wraps a TextBox, or...). In the LostFocus event handler, I could create an undo frame that includes a reference to the event's sender. Later, after Undo, I just focus the control whose reference I saved. This is probably what I would have done in WinForms, but in WPF I'd rather stick with the ViewModel pattern -- I'd rather have the journaling logic live in the ViewModel than the View, for testability if nothing else. So this option isn't my first choice.
In my ViewModel's property setters, I could capture the name of the ViewModel property that's being set ("Address" in this example), and store that name in the undo frame. Later, on Undo, I could iterate through all the controls in the View, looking for the first one I can find that has something bound to a property named Address. As soon as I find one such control, I give it focus. This would be good enough for what I need, since I don't expect to have more than one control bound to the same ViewModel property. The problem is that this would require digging into binding expressions, which is something I don't know how to do. (It would also introduce more name-based late binding that could break if I refactor.)
When my ViewModel adds the change to the undo stack, it could ask the View layer (via an interface) to create a Memento that knows which control has focus. On Undo, the journal would ask the View to restore that Memento. The problem here is that, by the time my ViewModel's property is getting set and I'm adding the undo frame, the keyboard focus has already moved to the City TextBox, so the "create memento" would need to be trickier than just "where's the current keyboard focus right now", and I'm not sure how to accomplish that trick.
Anyone have any suggestions on making any of the above work, or have alternate approaches that might work better?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我将从你的第二种方法开始。 然而,我不会深入挖掘绑定列表,而是将控件的突出显示属性硬编码为 VM 属性。
例如,这是我的虚拟机:
然后,将 Price 属性绑定到 TextBox,并将 TextBox 的背景绑定到 PriceHighlighted(带有值转换器)。 现在,VM 可以完全控制视图的反应方式。 当用户执行“撤消”操作时,VM 可以将除您要突出显示的那个之外的所有 xxxHightlighted 设置为 false。
I would start with your second approach. However, instead of digging through the binding list, I would hard code the control's highlight property to a VM property.
For example, this is my VM:
Then, bind Price property to a TextBox and the TextBox's background to PriceHighlighted (with a value converter). Now VM has the total control of how the view should react. When the user does "Undo", VM can set all xxxHightlighted to false except the one you want to highlight.