導(dǎo)航和視圖管理
本主題解釋了如何在不同的應(yīng)用程序視圖之間實(shí)現(xiàn)導(dǎo)航,以及如何構(gòu)建View-ViewModel關(guān)系。
標(biāo)準(zhǔn)導(dǎo)航服務(wù)
DevExpress MVVM框架包括許多,您可以利用它們來實(shí)現(xiàn)不同應(yīng)用模塊(視圖)之間的導(dǎo)航。
使用任何MVVM服務(wù)包括三個(gè)主要步驟:
1.在視圖中注冊(cè)服務(wù)時(shí)可以全局注冊(cè)(它可以從任何應(yīng)用程序視圖中獲得)或本地注冊(cè)(如果您打算僅從此模塊中使用它)。
2.在ViewModel中聲明一個(gè)屬性來檢索已注冊(cè)服務(wù)的實(shí)例。
C#:
public class ViewLocator : IViewLocator { object IViewLocator.Resolve(string name, params object[] parameters) { object viewModel = paremeters.Length==3 ? parameters[0] : null; object parameter = parameters.Length==3 ? parameters[1] : null; object parentViewModel = (paremeters.Length==3) ? paremeters[2] : paremeters[0] ; if(name == nameof(CustomersView)) return new CustomersView() //... return null; } }
3.調(diào)用ViewModel中Service實(shí)例的公共API。
例如,主應(yīng)用程序的視圖有MvvmContext組件,它將主應(yīng)用程序的表單(視圖)鏈接到“Form1ViewModel”ViewModel。
C#:
// View mvvmContext1.ViewModelType = typeof(mvvmNavi.Form1ViewModel); // ViewModel [POCOViewModel()] public class Form1ViewModel { //... }
VB.NET:
' View mvvmContext1.ViewModelType = GetType(mvvmNavi.Form1ViewModel) Private Sub InitializeBindings() Dim fluent = mvvmContext1.OfType(Of Form1ViewModel)() End Sub End Class ' ViewModel <POCOViewModel()> Public Class Form1ViewModel '... End Class
該應(yīng)用程序還有兩個(gè)UserControl,每個(gè)都有自己的MvvmContext組件,UserControl的視圖鏈接到它相應(yīng)的ViewModel。
C#:
public partial class ViewA : UserControl { MVVMContext mvvmContext; public ViewA() { mvvmContext = new MVVMContext(); mvvmContext.ContainerControl = this; mvvmContext.ViewModelType = typeof(ViewAViewModel); } } public class ViewAViewModel { } public partial class ViewB : UserControl { MVVMContext mvvmContext; public ViewB() { mvvmContext = new MVVMContext(); mvvmContext.ContainerControl = this; mvvmContext.ViewModelType = typeof(ViewBViewModel); } } public class ViewBViewModel { }
VB.NET:
Partial Public Class ViewA Inherits UserControl Private mvvmContext As MVVMContext Public Sub New() mvvmContext = New MVVMContext() mvvmContext.ContainerControl = Me mvvmContext.ViewModelType = GetType(ViewAViewModel) End Sub End Class Public Class ViewAViewModel End Class Partial Public Class ViewB Inherits UserControl Private mvvmContext As MVVMContext Public Sub New() mvvmContext = New MVVMContext() mvvmContext.ContainerControl = Me mvvmContext.ViewModelType = GetType(ViewBViewModel) End Sub End Class Public Class ViewBViewModel End Class
提示:上面的代碼初始化了MvvmContext組件,并設(shè)置了它們的ViewModelType屬性,只是為了舉例說明。在實(shí)際的應(yīng)用程序中,建議在設(shè)計(jì)時(shí)將組件放在 Forms 和 UserControls上,并使用智能標(biāo)簽菜單來設(shè)置ViewModels。
下面的例子說明了如何根據(jù)您的任務(wù)選擇和使用不同的DevExpress服務(wù):
例1:DocumentManager選項(xiàng)卡
主應(yīng)用程序表單(視圖)有一個(gè)空的文檔管理器,任務(wù)是將UserControls A和B顯示為DocumentManager選項(xiàng)卡(文檔)。
要管理DocumentManager文檔,請(qǐng)使用并在主視圖中注冊(cè)它:
C#:
public Form1() { InitializeComponent(); //. . . var service = DocumentManagerService.Create(tabbedView1); service.UseDeferredLoading = DevExpress.Utils.DefaultBoolean.True; mvvmContext1.RegisterDefaultService(service); }
VB.NET:
Public Sub Form1() InitializeComponent() '. . . Dim service = DocumentManagerService.Create(tabbedView1) service.UseDeferredLoading = DevExpress.Utils.DefaultBoolean.True mvvmContext1.RegisterDefaultService(service) End Sub
在主ViewModel中,實(shí)現(xiàn)一個(gè)屬性來檢索注冊(cè)服務(wù)的實(shí)例:
C#:
[POCOViewModel()] public class Form1ViewModel { protected IDocumentManagerService DocumentManagerService { get { return this.GetService<IDocumentManagerService>(); } } }
VB.NET:
<POCOViewModel()> Public Class Form1ViewModel Protected ReadOnly Property DocumentManagerService() As IDocumentManagerService Get Return Me.GetService(Of IDocumentManagerService)() End Get End Property End Class
DocumentManagerService.CreateDocument和DocumentManagerService.FindDocumentById方法允許您創(chuàng)建和定位文檔,然后可以調(diào)用IDocument.Show方法來顯示它們。
C#:
// main ViewModel public void CreateDocument(object id, string documentType, string title) { var document = DocumentManagerService.FindDocumentById(id); if (document == null) { document = DocumentManagerService.CreateDocument( documentType, parameter: null, parentViewModel: this); document.Id = id; document.Title = title; } document.Show(); }
VB.NET:
' main ViewModel Public Sub CreateDocument(ByVal id As Object, ByVal documentType As String, ByVal title As String) Dim document = DocumentManagerService.FindDocumentById(id) If document Is Nothing Then document = DocumentManagerService.CreateDocument(documentType, parameter:= Nothing, parentViewModel:=Me) document.Id = id document.Title = title End If document.Show() End Sub
這個(gè)核心方法可以在各種場景中使用。
- 創(chuàng)建一個(gè)帶有特定UserControl的新文檔,并在應(yīng)用程序啟動(dòng)時(shí)加載它:
C#:
// main ViewModel readonly static object ViewA_ID = new object(); readonly static object ViewB_ID = new object(); public void CreateDocumentA() { CreateDocument(ViewA_ID, "ViewA", "UserControl A"); } public void CreateDocumentB() { CreateDocument(ViewB_ID, "ViewB", "UserControl B"); } // main View var fluent = mvvmContext1.OfType<Form1ViewModel>(); fluent.WithEvent(this, "Load").EventToCommand(x => x.CreateDocumentA);
VB.NET:
' main ViewModel Private ReadOnly Shared ViewA_ID As New Object() Private ReadOnly Shared ViewB_ID As New Object() Public Sub CreateDocumentA() CreateDocument(ViewA_ID, "ViewA", "UserControl A") End Sub Public Sub CreateDocumentB() CreateDocument(ViewB_ID, "ViewB", "UserControl B") End Sub ' main View Dim fluent = mvvmContext1.OfType(Of Form1ViewModel)() fluent.WithEvent(Me, "Load").EventToCommand(Function(x) x.CreateDocumentA)
為每個(gè)UserControl創(chuàng)建一個(gè)文檔,并在啟動(dòng)時(shí)加載所有這些文檔。
C#:
// main ViewModel public void CreateAllDocuments() { CreateDocument(ViewA_ID, "ViewA", "UserControl A"); CreateDocument(ViewB_ID, "ViewB", "UserControl B"); } // main View var fluent = mvvmContext1.OfType<Form1ViewModel>(); fluent.WithEvent(this, "Load").EventToCommand(x => x.CreateAllDocuments);
VB.NET:
' main ViewModel Public Sub CreateAllDocuments() CreateDocument(ViewA_ID, "ViewA", "UserControl A") CreateDocument(ViewB_ID, "ViewB", "UserControl B") End Sub ' main View Dim fluent = mvvmContext1.OfType(Of Form1ViewModel)() fluent.WithEvent(Me, "Load").EventToCommand(Function(x) x.CreateAllDocuments)
- 將UI元素(例如,Ribbon按鈕)綁定到一個(gè)命令,該命令創(chuàng)建一個(gè)具有特定UserControl的新文檔。
C#:
// main ViewModel public void CreateDocument(object id, string documentType, string title) { var document = DocumentManagerService.CreateDocument( documentType, parameter: null, parentViewModel: this); document.Id = id; document.Title = title; document.Show(); } public void CreateDocumentA() { CreateDocument(new object(), "ViewA", "UserControl A"); } public void CreateDocumentB() { CreateDocument(new object(), "ViewB", "UserControl B"); } // main View fluent.BindCommand(bbiCreateDocA, x => x.CreateDocumentA); fluent.BindCommand(bbiCreateDocB, x => x.CreateDocumentB);
VB.NET:
' main ViewModel Public Sub CreateDocument(ByVal id As Object, ByVal documentType As String, ByVal title As String) Dim document = DocumentManagerService.CreateDocument(documentType, parameter:= Nothing, parentViewModel:=Me) document.Id = id document.Title = title document.Show() End Sub Public Sub CreateDocumentA() CreateDocument(New Object(), "ViewA", "UserControl A") End Sub Public Sub CreateDocumentB() CreateDocument(New Object(), "ViewB", "UserControl B") End Sub ' main View fluent.BindCommand(bbiCreateDocA, Function(x) x.CreateDocumentA) fluent.BindCommand(bbiCreateDocB, Function(x) x.CreateDocumentB)
示例2:導(dǎo)航框架
主表單(視圖)有一個(gè)空的NavigationFrame組件,該組件可以存儲(chǔ)多個(gè)頁面,但一次只允許用戶查看一個(gè)頁面。要用頁面填充該組件并實(shí)現(xiàn)導(dǎo)航,請(qǐng)使用NavigationService。
- 全球服務(wù)注冊(cè):
C#:
// main View var service = NavigationService.Create(navigationFrame1); mvvmContext1.RegisterDefaultService(service);
VB.NET:
' main View Dim service = NavigationService.Create(navigationFrame1) mvvmContext1.RegisterDefaultService(service)
- 檢索Service實(shí)例的屬性:
C#:
// main ViewModel protected INavigationService NavigationService { get { return this.GetService<INavigationService>(); } }
VB.NET:
' main ViewModel Protected ReadOnly Property NavigationService() As INavigationService Get Return Me.GetService(Of INavigationService)() End Get End Property
- 導(dǎo)航:
C#:
// main View var fluent = mvvmContext.OfType<RootViewModel>(); fluent.WithEvent(mainView, "Load") .EventToCommand(x => x.OnLoad); // main ViewModel public void OnLoad() { NavigationService.Navigate("ViewA", null, this); }
VB.NET:
' main View Private fluent = mvvmContext.OfType(Of RootViewModel)() fluent.WithEvent(mainView, "Load").EventToCommand(Function(x) x.OnLoad) ' main ViewModel public void OnLoad() NavigationService.Navigate("ViewA", Nothing, Me)
Navigate方法可以接受參數(shù)作為它的第二個(gè)參數(shù),這允許您在導(dǎo)航模塊之間傳遞任何數(shù)據(jù)。DevExpress Demo Center示例演示了如何將先前活動(dòng)模塊的名稱傳遞給當(dāng)前選擇的視圖,注意在這個(gè)例子中,全局服務(wù)注冊(cè)允許每個(gè)子ViewModel使用這個(gè)服務(wù)的API。
示例3:情態(tài)形式
在本例中,子視圖在其他應(yīng)用程序窗口上方顯示為單獨(dú)的表單。要做到這一點(diǎn),請(qǐng)使用WindowedDocumentManagerService服務(wù)。
- 本地注冊(cè):
C#:
// main View var service = WindowedDocumentManagerService.Create(mainView); service.DocumentShowMode = WindowedDocumentManagerService.FormShowMode.Dialog; mvvmContext.RegisterService(service);
VB.NET:
' main View Dim service = WindowedDocumentManagerService.Create(mainView) service.DocumentShowMode = WindowedDocumentManagerService.FormShowMode.Dialog mvvmContext.RegisterService(service)
- 檢索Service實(shí)例的屬性:
C#:
// main ViewModel protected IDocumentManagerService WindowedDocumentManagerService { get { return this.GetService<IDocumentManagerService>(); } }
VB.NET:
' main ViewModel Protected ReadOnly Property WindowedDocumentManagerService() As IDocumentManagerService Get Return Me.GetService(Of IDocumentManagerService)() End Get End Property
- 導(dǎo)航:
C#:
// main View var fluent = mvvmContext.OfType<MainViewModel>(); fluent.BindCommand(showBtn, x => x.ShowAcceptDialog); // main ViewModel int id = 0; public void ShowAcceptDialog() { var viewModel = ViewModelSource.Create(() => new ViewAViewModel()); var document = WindowedDocumentManagerService.FindDocumentById(id); if(document == null) { document = WindowedDocumentManagerService.CreateDocument(string.Empty, viewModel: viewModel); document.Id = id; document.Title = "Accept Dialog"; } document.Show(); }
VB.NET:
' main View Dim fluent = mvvmContext.OfType(Of MainViewModel)() fluent.BindCommand(showBtn, Function(x) x.ShowAcceptDialog) ' main ViewModel Private id As Integer = 0 Public Sub ShowAcceptDialog() Dim viewModel = ViewModelSource.Create(Function() New ViewAViewModel()) Dim document = WindowedDocumentManagerService.FindDocumentById(id) If document Is Nothing Then document = WindowedDocumentManagerService.CreateDocument(String.Empty, viewModel:= viewModel) document.Id = id document.Title = "Accept Dialog" End If document.Show() End Sub
- 結(jié)束情態(tài)形式:
C#:
public class ChildViewModel : IDocumentContent { public void Close() { // Closes the document. DocumentOwner?.Close(this); } public IDocumentOwner DocumentOwner { get; set; } public object Title { get; set; } void IDocumentContent.OnClose(CancelEventArgs e) { /* Do something */ } void IDocumentContent.OnDestroy() { /* Do something */ } }
VB.NET:
Public Class ChildViewModel Implements IDocumentContent Public Sub Close() ' Closes the document. DocumentOwner?.Close(Me) End Sub Public Property DocumentOwner() As IDocumentOwner Public Property Title() As Object Private Sub IDocumentContent_OnClose(ByVal e As CancelEventArgs) Implements IDocumentContent.OnClose ' Do something End Sub Private Sub IDocumentContent_OnDestroy() Implements IDocumentContent.OnDestroy ' Do something End Sub End Class
ViewType屬性
如果您遵循命名約定(“ModuleX”視圖的ViewModel被稱為“ModuleXViewModel”),并且視圖/ViewModel位于相同的命名空間中,則上述示例中顯示的MVVM服務(wù)的默認(rèn)使用就足夠了,否則框架將無法定位與給定ViewModule相關(guān)的視圖。要解決這個(gè)問題,需要用ViewType屬性修飾Views,來顯式地設(shè)置View-ViewModel關(guān)系。
C#:
[DevExpress.Utils.MVVM.UI.ViewType("AccountCollectionView")] public partial class AccountsView { // ... } [DevExpress.Utils.MVVM.UI.ViewType("CategoryCollectionView")] public partial class CategoriesView { // ... } [DevExpress.Utils.MVVM.UI.ViewType("TransactionCollectionView")] public partial class TransactionsView { // ... }
VB.NET:
<DevExpress.Utils.MVVM.UI.ViewType("AccountCollectionView")> Partial Public Class AccountsView ' ... End Class <DevExpress.Utils.MVVM.UI.ViewType("CategoryCollectionView")> Partial Public Class CategoriesView ' ... End Class <DevExpress.Utils.MVVM.UI.ViewType("TransactionCollectionView")> Partial Public Class TransactionsView ' ... End Class
單獨(dú)程序集中的視圖
當(dāng)視圖位于單獨(dú)的程序集中或具有自定義構(gòu)造函數(shù)時(shí),ViewType屬性是不夠的。在這些情況下,請(qǐng)使用以下方法之一:
IViewService
將導(dǎo)航服務(wù)實(shí)例轉(zhuǎn)換為DevExpress.Utils.MVVM.UI.IViewService接口。
C#:
var service = DevExpress.Utils.MVVM.Services.DocumentManagerService.Create(tabbedView1); var viewService = service as DevExpress.Utils.MVVM.UI.IViewService; mvvmContext1.RegisterService(service);
VB.NET:
Dim service = DevExpress.Utils.MVVM.Services.DocumentManagerService.Create(tabbedView1) Dim viewService = TryCast(service, DevExpress.Utils.MVVM.UI.IViewService) mvvmContext1.RegisterService(service)
之后,處理QueryView事件并根據(jù)所需的視圖類型動(dòng)態(tài)分配視圖。
C#:
viewService.QueryView += (s, e) => { if(e.ViewType == "View1") e.Result = new Views.View1(); //... };
VB.NET:
AddHandler viewService.QueryView, Sub(s, e) If e.ViewType = "View1" Then e.Result = New Views.View1() End If '... End Sub
要指定需要哪種視圖類型,您需要在導(dǎo)航ViewModel中實(shí)現(xiàn)相應(yīng)的邏輯。例如,下面的代碼將所有可用的視圖枚舉為Modules集合中的項(xiàng)。
C#:
public class MyNavigationViewModel { protected IDocumentManagerService DocumentManagerService { get { return this.GetService<IDocumentManagerService>(); } } //Lists all available view types public string[] Modules { get { return new string[] { "View1", "View2", "View3" }; } } //Bind this command to required UI elements to create and display a document public void Show(string moduleName) { var document = DocumentManagerService.CreateDocument(moduleName, null, this); if(document != null) { document.Title = moduleName; document.Show();} } }
VB.NET:
Public Class MyNavigationViewModel Protected ReadOnly Property DocumentManagerService() As IDocumentManagerService Get Return Me.GetService(Of IDocumentManagerService)() End Get End Property 'Lists all available view types Public ReadOnly Property Modules() As String() Get Return New String() { "View1", "View2", "View3" } End Get End Property 'Bind this command to required UI elements to create and display a document Public Sub Show(ByVal moduleName As String) Dim document = DocumentManagerService.CreateDocument(moduleName, Nothing, Me) If document IsNot Nothing Then document.Title = moduleName document.Show() End If End Sub End Class
控制APIs
您可以使用導(dǎo)航服務(wù)管理的單個(gè)視圖控件的API。例如,如果視圖應(yīng)該顯示為DocumentManager選項(xiàng)卡,便處理BaseView.QueryControl事件來填充文檔,View類型存儲(chǔ)Document.ControlName屬性值。
C#:
var service = DevExpress.Utils.MVVM.Services.DocumentManagerService.Create(tabbedView1); mvvmContext1.RegisterService(service); tabbedView1.QueryControl += (s, e) => { if(e.Document.ControlName == "View 2") e.Control = new Views.View2(); //... };
VB.NET:
Dim service = DevExpress.Utils.MVVM.Services.DocumentManagerService.Create(tabbedView1) mvvmContext1.RegisterService(service) AddHandler tabbedView1.QueryControl, Sub(s, e) If e.Document.ControlName = "View 2" Then e.Control = New Views.View2() End If '... End Sub
IViewLocator
所有DevExpress導(dǎo)航服務(wù)都使用DevExpress.Utils.MVVM.UI.IViewLocator服務(wù)來查找和管理所需的視圖,您可以創(chuàng)建此服務(wù)的自定義實(shí)現(xiàn)并注冊(cè)它(本地或全局)來更改它與應(yīng)用程序視圖的工作方式。請(qǐng)參閱本文了解如何實(shí)現(xiàn)和注冊(cè)自定義服務(wù):services。
視圖和視圖模型生存期
處置視圖也處置MvvmContext和ViewModel,您既可以實(shí)現(xiàn)IDisposable.Dispose方法,也可以將命令綁定到視圖的HandleDestroyed事件,以便在ViewModel被處置時(shí)執(zhí)行操作。
C#:
// ViewModel public ViewModel() { // Registers a new connection to the messenger. Messenger.Default.Register(...); } public void OnCreate() { // Captures UI-bound services. EnsureDispatcherService(); } public void OnDestroy() { // Destroys a connection to the messanger. Messenger.Default.Unregister(...); } IDispatcherService dispatcher; IDispatcherService EnsureDispatcherService() { return dispatcher ?? (dispatcher = this.GetRequiredService<IDispatcherService>()); } // View (UserControl/Form) fluent.WithEvent(this, nameof(HandleCreated)).EventToCommand(x => x.OnCreate); fluent.WithEvent(this, nameof(HandleDestroyed)).EventToCommand(x => x.OnDestroy);
VB.NET:
Public Sub New() ' Registers a new connection to the messenger. Messenger.Default.Register(...) End Sub Public Sub OnCreate() ' Captures UI-bound services. EnsureDispatcherService() End Sub Public Sub OnDestroy() ' Destroys a connection to the messanger. Messenger.Default.Unregister(...) End Sub Private dispatcher As IDispatcherService Private Function EnsureDispatcherService() As IDispatcherService If dispatcher IsNot Nothing Then Return dispatcher Else dispatcher = Me.GetRequiredService(Of IDispatcherService)() Return dispatcher End If End Function ' View (UserControl/Form) fluent.WithEvent(Me, nameof(HandleCreated)).EventToCommand(Function(x) x.OnCreate) fluent.WithEvent(Me, nameof(HandleDestroyed)).EventToCommand(Function(x) x.OnDestroy)