導航和視圖管理
本主題解釋了如何在不同的應用程序視圖之間實現(xiàn)導航,以及如何構建View-ViewModel關系。
標準導航服務
DevExpress MVVM框架包括許多服務,您可以利用它們來實現(xiàn)不同應用模塊(視圖)之間的導航。
使用任何MVVM服務包括三個主要步驟:
1.在視圖中注冊服務時可以全局注冊(它可以從任何應用程序視圖中獲得)或本地注冊(如果您打算僅從此模塊中使用它)。
2.在ViewModel中聲明一個屬性來檢索已注冊服務的實例。
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.調用ViewModel中Service實例的公共API。
例如,主應用程序的視圖有MvvmContext組件,它將主應用程序的表單(視圖)鏈接到“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
該應用程序還有兩個UserControl,每個都有自己的MvvmContext組件,UserControl的視圖鏈接到它相應的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組件,并設置了它們的ViewModelType屬性,只是為了舉例說明。在實際的應用程序中,建議在設計時將組件放在 Forms 和 UserControls上,并使用智能標簽菜單來設置ViewModels。
下面的例子說明了如何根據(jù)您的任務選擇和使用不同的DevExpress服務:
例1:DocumentManager選項卡
主應用程序表單(視圖)有一個空的文檔管理器,任務是將UserControls A和B顯示為DocumentManager選項卡(文檔)。
要管理DocumentManager文檔,請使用DocumentManagerService并在主視圖中注冊它:
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中,實現(xiàn)一個屬性來檢索注冊服務的實例:
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)建和定位文檔,然后可以調用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
這個核心方法可以在各種場景中使用。
- 創(chuàng)建一個帶有特定UserControl的新文檔,并在應用程序啟動時加載它:
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)
為每個UserControl創(chuàng)建一個文檔,并在啟動時加載所有這些文檔。
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按鈕)綁定到一個命令,該命令創(chuàng)建一個具有特定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:導航框架
主表單(視圖)有一個空的NavigationFrame組件,該組件可以存儲多個頁面,但一次只允許用戶查看一個頁面。要用頁面填充該組件并實現(xiàn)導航,請使用NavigationService。
- 全球服務注冊:
C#:
// main View var service = NavigationService.Create(navigationFrame1); mvvmContext1.RegisterDefaultService(service);
VB.NET:
' main View Dim service = NavigationService.Create(navigationFrame1) mvvmContext1.RegisterDefaultService(service)
- 檢索Service實例的屬性:
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
- 導航:
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ù)作為它的第二個參數(shù),這允許您在導航模塊之間傳遞任何數(shù)據(jù)。DevExpress Demo Center示例演示了如何將先前活動模塊的名稱傳遞給當前選擇的視圖,注意在這個例子中,全局服務注冊允許每個子ViewModel使用這個服務的API。
示例3:情態(tài)形式
在本例中,子視圖在其他應用程序窗口上方顯示為單獨的表單。要做到這一點,請使用WindowedDocumentManagerService服務。
- 本地注冊:
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實例的屬性:
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
- 導航:
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
- 結束情態(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服務的默認使用就足夠了,否則框架將無法定位與給定ViewModule相關的視圖。要解決這個問題,需要用ViewType屬性修飾Views,來顯式地設置View-ViewModel關系。
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
單獨程序集中的視圖
當視圖位于單獨的程序集中或具有自定義構造函數(shù)時,ViewType屬性是不夠的。在這些情況下,請使用以下方法之一:
IViewService
將導航服務實例轉換為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ù)所需的視圖類型動態(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
要指定需要哪種視圖類型,您需要在導航ViewModel中實現(xiàn)相應的邏輯。例如,下面的代碼將所有可用的視圖枚舉為Modules集合中的項。
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
您可以使用導航服務管理的單個視圖控件的API。例如,如果視圖應該顯示為DocumentManager選項卡,便處理BaseView.QueryControl事件來填充文檔,View類型存儲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導航服務都使用DevExpress.Utils.MVVM.UI.IViewLocator服務來查找和管理所需的視圖,您可以創(chuàng)建此服務的自定義實現(xiàn)并注冊它(本地或全局)來更改它與應用程序視圖的工作方式。請參閱本文了解如何實現(xiàn)和注冊自定義服務:services。
視圖和視圖模型生存期
處置視圖也處置MvvmContext和ViewModel,您既可以實現(xiàn)IDisposable.Dispose方法,也可以將命令綁定到視圖的HandleDestroyed事件,以便在ViewModel被處置時執(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)