Screens and Conductors是一个简单的主题,但需要一些思维飞跃,并且需要您在理解它们之前涵盖它们的所有部分。相信我,您花时间阅读这篇文章是非常值得的 - 它们非常强大,非常值得投入时间。
一个好的起点是查看 ViewModel 生命周期。
想象一个选项卡式界面——类似于 Visual Studio,它有一个(非常简单的)外壳(包含菜单、工具栏等)和一个包含编辑器选项卡的 TabControl。在 Stylet 中,每个编辑器选项卡都将由其自己的 ViewModel 提供支持。
现在,其中一个 ViewModel 将通过实例化开始其生命。接下来,它将被显示。之后,它可能会根据当前处于活动状态的选项卡显示或隐藏,然后最终关闭。就在它关闭之前,它有机会阻止关闭以提示您保存文件。
简而言之,这就是 ViewModel 的生命周期:它被创建,然后被激活(显示给用户)。之后,它可以被停用(仍然存在但未显示)并再次激活任意次数,然后最终被关闭(在被询问是否准备好关闭之后)。
值得注意的是,如果 ViewModel 实现了IDisposable
,那么它将在其父级关闭后被释放(除非父级的DisposeChildren
属性为 false)。
现在,ViewModel 不会神奇地知道它何时显示、隐藏或关闭。必须告诉它。这就是指挥的角色。
简单来说,Conductor 是一个 ViewModel,它拥有另一个 ViewModel,并且知道如何管理它的生命周期。
在我们的 Visual Studio 示例中,Conductor 将是拥有显示编辑器 ViewModel 的 TabControl 的 ViewModel,因此可能是 Shell ViewModel。每当用户选择一个新的编辑器选项卡时,Conductor 将停用旧选项卡并激活新选项卡。当用户关闭一个选项卡时,Conductor 会告诉该选项卡它已关闭,然后决定下一个要显示的选项卡,并激活它。
就是这样,真的。ViewModel 有一个生命周期,它由拥有 ViewModel 的 Conductor 实现。
到目前为止,这是相当抽象的——让我们进入细节。
正如我们在上面看到的,ViewModel 的生命周期由 Conductor 调用该 ViewModel 上的方法来管理。这些方法是在一组独立的接口中定义的——如果您实现接口,并且 ViewModel 的管理是一个 Conductor,该方法将被调用。如果需要,您可以选择所需的界面。
有一个名为的总体接口IScreen
将它们组合在一起,还有一个名为的默认实现Screen
。这表现得非常好,您可能永远不需要实现自己的 - 但如果您愿意,您可以。
IScreenState
:用于激活、停用和关闭 ViewModel。具有Activate
、Deactivate
和Close
methods 方法,以及用于跟踪屏幕状态变化的事件和属性。IGuardClose
: 用于询问 ViewModel 是否可以关闭。有CanCloseAsync
方法。IViewAware
:有时 ViewModel 需要知道它的视图(何时附加,它是什么,等等)。这个接口允许通过一个View
属性和一个AttachView
方法。IHaveDisplayName
: 有财产DisplayName
。此名称用作使用WindowManager显示的窗口和对话框的标题,并且对于 TabControls 之类的东西也很有用。IChild
:对于 ViewModel 来说,知道什么 Conductor 正在管理它(例如请求关闭它)可能是有利的。如果 ViewModel 实现了IChild
,它会被告知。请注意,无法保证调用 Activate、Deactivate 和 Close 的顺序 - ViewModel 可以连续激活两次,然后在不被停用的情况下关闭。ViewModel 会注意到这些事情,并做出相应的反应。Stylet 就是Screen
这样做的。
Screen
如果您愿意,我们鼓励您重写一些虚拟方法:
OnInitialActivate
: 第一次激活屏幕时调用,以后不会再调用。用于设置您不想在构造函数中设置的内容。OnActivate
:屏幕激活时调用。仅当 Screen 尚未激活时才会被调用。OnDeactivate
:当屏幕停用时调用。仅当 Screen 尚未停用时才会调用。OnClose
:屏幕关闭时调用。只会被调用一次。只会在屏幕停用时调用。OnViewLoaded
:在视图的Loaded
事件被触发时调用。CanCloseAsync
: 当 Conductor 想知道 Screen 是否可以关闭时调用。默认情况下, retuns Task.FromResult(this.CanClose)
,但您可以在此处添加自己的异步逻辑。CanClose``CanCloseAsync
:默认调用。这只是一种方便。如果您想决定是否可以同步关闭,请覆盖CanClose
. 如果您想异步决定,请覆盖CanCloseAsync
.RequestClose(bool? dialogResult = null)
: 当你想请求你自己的 Conductor 关闭时调用它。如果您在对话框中显示,则使用 DialogResult 参数。Screen 派生自PropertyChangedBase,因此很容易引发 PropertyChanged 通知。
您可能会发现所有 ViewModel 都是 Screen 的子类。这并不是说它们必须是 - 您可以创建自己的 实现IScreen
,或者从上面挑选您想要实现的接口 - 但它既方便又强大。
导体有多种风格,每种都有自己的用例。conductor 可以拥有一个 ViewModel(想想一次显示一个页面的导航),或多个 ViewModel。具有多个 ViewModel 的视图模型可以一次只有一个处于活动状态(想想上面 Visual Studio 示例中的 TabControl),或者所有的视图模型(想想具有许多独立元素的网格)。Conductor 还可以添加行为,例如记录他们显示了哪些 ViewModel(对导航有用)。
与Screen
类一样,Stylet 定义了一些导体感兴趣的接口,以及一些实现(取决于你想要的导体行为的种类),当然你可以实现你自己的。
主要接口是IConductor
,它代表您将与之交互的导体。它有以下方法
ActivateItem(T item)
:拿取给定的项目,并激活它。这是否会停用先前的项目由指挥决定。DeactivateItem(T item)
:拿取给定的项目,并将其停用。这是否激活另一个项目由指挥决定。CloseItem(T item)
:拿走给定的项目,然后关闭它。这是否会导致激活另一个项目来取代它的位置是导体特定的。具有单个活动项目的指挥(无论他们可能有多少非活动项目)也实现IHaveActiveItem
,它具有单一属性ActiveItem
。
如果项目实现Parent
了IChild
. 所有内置的导体都额外实现了IChildDelegate
,它允许孩子请求关闭它(通过调用CloseItem
)。在默认Screen
实现中,调用Screen.RequestClose
将导致屏幕调用CloseItem
其父级(提供其父级 implement IChildDelegate
),这反过来又导致其父级(如果存在)关闭它。
Stylet 带有一些内置的conductors ,它们以多种直观的方式执行。
所有这些导体都派生自Screen
,允许导体轻松拥有其他导体。这意味着您可以以任何您想要的方式组成您的导体和屏幕。
Conductor
这个非常简单的 conductor 拥有一个 ViewModel(类型T
),它公开为ActiveItem
. 该ActivateItem
方法用于用ActiveItem
新的 ViewModel 实例替换当前的,并将激活新项目并关闭旧项目。每当Condcutor
被激活时,它就会激活它的ActiveItem
; 同样,它ActiveItem
分别在停用或关闭时停用和关闭。
当询问它是否可以关闭时(CanCloseAsync
调用时),它返回ActiveItem
返回的任何内容,如果没有则返回 true ActiveItem
。
也ActiveItem
可以直接设置,和调用它的效果一样ActivateItem
。
这个 Conductor 的 ViewModel 看起来像这样——一个 ContentControl 绑定到 conductor 的 ActiveItem:
<Window x:Class="MyNamespace.ConductorViewModel"
xmlns:s="https://github.com/canton7/Stylet" ....>
<ContentControl s:View.Model="{Binding ActiveItem}"/>
Window>
Conductor.Collection.OneActive
该指挥拥有许多物品,但一次只能激活一个。通过这种方式,它模拟了 TabControl 的行为 - 许多选项卡可以同时存在,但一次只能显示一个。
它拥有一个T
名为的集合Items
,其中之一更幸运地成为了ActiveItem
。调用ActivateItem
会将传递的项目添加到Items
集合中,并将激活它并将其设置为ActiveItem
;如果ActiveItem
之前已设置,则其旧值将被停用,并保留在Items
集合中。
在项目上调用DeactivateItem
或CloseItem
将分别导致该项目停用和关闭。由于它不再处于活动状态,因此它不能保持为ActiveItem
- 相反,另一个项目被选择为ActiveItem
,并被激活并设置为这样。默认情况下,新的ActiveItem
是存在于Items
集合中的项目之前被停用/关闭的那个。
Items
如果需要,可以直接操作集合。也ActiveItem
可以直接设置,与调用传递那个item效果一样ActivateItem
。
使用此导体的带有 TabControl 的 ViewModel 可能如下所示(简短版本见下文):
<TabControl ItemsSource="{Binding Items}" SelectedItem="{Binding ActiveItem}" DisplayMemberPath="DisplayName">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False"/>
DataTemplate>
TabControl.ContentTemplate>
TabControl>
然而,这有点啰嗦,因此 Stylet 为您提供了一种可以做同样事情的样式。这意味着您可以改为:
Conductor.Collection.AllActive
该导体与 非常相似Conductor
,只是它没有单个ActiveItem
。相反,它只有Item
. 当一个项目被激活(使用ActivateItem
)时,它被添加到这个集合中,当它被关闭时,它被从这个集合中删除。
调用DeactivateItem
将就地停用该项目,而不会将其从Items
集合中删除。
集合Items
也可以直接操作。任何添加的项目都将被激活,任何删除的项目都将被关闭。
一个典型的用例可能是使用 ItemsControl,其中所有项目都同时可见。以这种方式使用 ItemsControl 的 ViewModel 可能看起来像这样(同样,请参见下面的简短版本):
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False"/>
DataTemplate>
ItemsControl.ItemTemplate>
ItemsControl>
由于这非常冗长,Stylet 提供了一种样式来为您设置这些属性:
<ItemsControl Style="{StaticResource StyletConductorItemsControl}"/>
Conductor.StackNavigation
Conductor
这个 conductor 是和之间的混合体Conductor
,它提供了一些额外的东西:基于堆栈的导航。
它有一个单一的ActiveItem
,但也保留了过去活跃项目的(私人)历史。当您激活一个新项目时,前一个项目ActiveItem
将被停用,并被推送到历史堆栈中。调用GoBack()
将关闭当前的ActiveItem
,并重新激活此历史堆栈中的顶部项目,并将其设置为新的ActiveItem
。
如果您调用CloseItem
current ActiveItem
,则效果相同。如果您调用CloseItem
历史堆栈中存在的任何项目,该项目将被关闭并从历史堆栈中删除。调用Clear()
将关闭并从历史堆栈中删除所有项目。
WindowConductor
这个有点古怪,因为它是内部的,你不会直接与它交互,但我把它包含在这里是为了感兴趣。每当您使用显示对话框或窗口时WindowManager
(这包括 Stylet 在您首次启动应用程序时显示的窗口),一个新的WindowConductor
管理它的生命周期。每当您的窗口或对话框最小化时,它就会被停用。每当它最大化时,它就会被激活。如果您的 ViewModel 请求关闭它(见RequestClose
上文),则WindowConductor
处理它。同样,如果用户自己关闭了您的窗口,则会WindowConductor
询问您的 ViewModel 是否已准备好关闭。
项目原地址:https://github.com/canton7/Stylet
当前文档原地址:https://github.com/canton7/Stylet/wiki/Screens-and-Conductors
上一篇:WPF的MVVM框架Stylet开发文档 10. 执行:调度到 UI 线程
下一篇:WPF的MVVM框架Stylet开发文档 12.可绑定集合BindableCollection