在WPF中切换视图,高级设计问题

3
我目前正在开发一个使用WPF编写的UI。我非常喜欢WPF的一点是它能帮助你编写更加松散、隔离的UI组件。但是在WPF中,有一个痛点就是这些隔离的UI组件有时需要相互通信 : )。这可能是由于我的UI经验相对较少,特别是在WPF方面(我不是新手,但我的大部分工作比UI设计更低级)。
无论何时,UI的中心区域都会显示三个视图之一,实现为UserControl,我们称之为A、B和C视图。
用户将在不同的时间切换这些视图,并且有多种切换视图的方式(这对客户来说很好,但目前在代码设计方面会导致一些问题)。
现在,每个视图切换机制都会执行自己的操作以过渡到另一个视图。某个单例类负责存储数据并在视图之间进行通信。我不喜欢这样,因为它很混乱,容易出错,而且单例类对UI的细节了解得太多了。我想尽可能地消除它。
今天我遇到了一个与切换视图的时间有关的错误。简单来说,当一个视图被卸载时,另一个视图需要执行一些清理工作,但是这个清理会删除另一个视图所需的一些数据。如果清理在另一个视图加载后运行,就会出现问题。你明白我的意思吗?很混乱。
我正在尝试退后一步,想象一种不同的方式来加载这些视图所需的数据以完成它们的工作。你们中的一些经验丰富的UI/WPF人员肯定遇到过类似的问题。我有一些想法,但我希望有人能在这里向我提供更简洁的方法。我不喜欢依赖操作顺序(在高层次上)让代码正常工作。非常感谢您提供的任何帮助。

你提到了 MVVM,那你在使用 View-First 还是 ViewModel-First 的方式来实现 MVVM?在我看来这很重要,因为这会影响最佳的方法。 - Reed Copsey
@Reed:Viewmodel first,谢谢Reed。 - Ed S.
3个回答

5
我建议使用某种父ViewModel来处理CurrentView。如果您感兴趣,我之前写了一个例子here

基本上,父视图将有一个List<ViewModelBase> AvailablePages,一个ViewModelBase CurrentPage和一个ICommand ChangePageCommand

如何选择显示这些内容取决于您。我喜欢的方法是使用ContentControl,将其Content绑定到CurrentPage,并使用DataTemplates根据存储在CurrentPage中的ViewModel来确定应该显示哪个View


+1 这基本上就是我的方法。唯一需要补充的是,没有提供一个干净的方式来处理子视图请求更改另一个子视图的情况,除非提供额外的信息。这可以通过许多方式轻松处理,包括为子级提供共享VM接口并在父级VM上订阅,或使用某种形式的消息传递和/或服务来处理VM之间的通信。 - Reed Copsey
如果是这种情况,我通常会使用类似PRISM的“EventAggregator”或MVVM Light的“Messenger”来广播“ChangeViewMessage”,并让处理当前视图的任何控件订阅这些消息并处理它们。我实际上也写了一些关于ViewModel之间通信的内容 :) http://rachel53461.wordpress.com/2011/06/05/communication-between-viewmodels-with-mvvm/ - Rachel
是的 - 这两种方法都是一种有效的消息/服务方法,如果您将在应用程序中多个地方执行此操作,则这是一个很好的选择。 - Reed Copsey
我确实使用Prism的EventAggregator,我的一个想法非常相似。非常感谢您抽出时间在这里回答我,我非常喜欢这种方法。 - Ed S.
你会如何处理每个页面的叠加层?在 CM 中,由于有许多陷阱,这是一个很大的痛点。 - GorillaApe

2

Rachel's post 很好地概括了我的基本方法。不过,根据您的评论,我想补充一些事情,您可能想在此考虑。

请注意,这都是基于 ViewModel-first 方法假定的,正如评论中所提到的。

用户将在各种时间之间切换这些视图,并且有多种切换视图的方式(这对客户很有效,但目前在代码设计方面会带来一些痛苦)。

这不应该在设计上造成痛苦。关键在于有一个单一、一致的方式来请求“当前 ViewModel”更改,而 View 将自动跟随。View 中使用的实际机制可以是任何东西——更改 VM 应该是一致的。

如果正确完成,设计中应该很少有痛苦,并且在 View 实际操作方面具有很大的灵活性。

现在每个视图切换机制都会对过渡到另一个视图做出自己的处理。某个单例类负责存储数据和在视图之间通信。我不喜欢这种方式,它很混乱、容易出错,而且单例类对 UI 的细节了解得太多了。我想尽可能地消除它。
这就是协调 ViewModel 真正能够简化事情的地方。它不需要单例,因为它有效地“拥有”各个视图的个体 ViewModel。这里有一个相当简单的选项,即在 ViewModel 上实现一个包含事件的接口 - ViewModel 可以引发事件(我会根据意图而非“视图更改”来命名事件)。协调 VM 将订阅每个子 VM,并根据事件在适当内容的基础上更改其“CurrentItem”属性(针对活动 VM)。完全不需要任何 UI 细节。
今天我遇到了一个与视图切换时间有关的错误。简单来说,一个视图在卸载时需要执行一些清理工作,但是这些清理工作会擦除其他视图所需的一些数据。如果清理在加载其他视图后运行,就会出现问题。你明白我的意思了吗?很混乱。
这段话的意思是:“这个代码需要进行重构。ViewModel 不应该清理它不拥有的数据。如果出现这种情况,就意味着 VM 正在清理本应单独管理的数据。协调 ViewModel 可能是处理这个问题的一种方式,但如果没有更多信息,这非常困难。”
“我不喜欢依赖操作顺序(在高级别上)来使我的代码正常工作。” 这是正确的思考方式。如果可以避免,在代码中不应该有任何对操作顺序的依赖,因为这会使生活变得更简单。
“我正在尝试退后一步,想象一种不同的方法来加载这些视图所需的数据以完成它们的工作。”
Rachel和我在这里提倡的方法实际上与我在MVVM系列中用于实现主细节视图的方法相同。这里的好处是,“详细信息”部分的视图不必总是相同类型的ViewModel或View-如果您使用一个绑定到属性的ContentPresenter,该属性仅为Object(或VM共享的接口),则可以通过在运行时更改属性值来轻松切换完全不同的视图。

我必须选择一个人来授予额外的15分,Rachel首先发布了一般方法,但我真的很感激你花时间把所有这些都写出来。这听起来对我来说是正确的方法,我将很快实施它。再次感谢和+1。 - Ed S.

1
我的建议是使用一个主视图模型来协调所有事情(不要使用静态/单例),然后使用子视图模型来传输数据。这样可以保持您所需要的解耦,提供可测试性,并允许您控制每个对象的数据何时更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接