我想了解構建MVVM解決方案的最佳實踐。MVVM解決方案結構
目前我爲View和ViewModel創建了一個單獨的項目。我的視圖項目引用了我的ViewModel項目。到現在爲止還挺好。在實現導航時,我的ViewModel類需要訪問RootFrame才能瀏覽,但是RootFrame駐留在View項目中的App.xaml中。所以我在這裏有一個循環依賴問題。
是否有我應該使用的推薦結構?我可以將這一切都整合到一個大項目中,但爲了分離View和ViewModel,我覺得單獨項目是一種更好的方法。
我想了解構建MVVM解決方案的最佳實踐。MVVM解決方案結構
目前我爲View和ViewModel創建了一個單獨的項目。我的視圖項目引用了我的ViewModel項目。到現在爲止還挺好。在實現導航時,我的ViewModel類需要訪問RootFrame才能瀏覽,但是RootFrame駐留在View項目中的App.xaml中。所以我在這裏有一個循環依賴問題。
是否有我應該使用的推薦結構?我可以將這一切都整合到一個大項目中,但爲了分離View和ViewModel,我覺得單獨項目是一種更好的方法。
當談到實現導航我的ViewModel類需要的RootFrame
這是一個錯誤的假設訪問。
您可以使用負責在發佈者(ViewModels)和訂閱者(負責打開視圖的某個對象)之間分發郵件的郵件代理(單個對象)。
大多數MVVM框架都有這樣的代理。
關於依賴性
代理的單一職責是引發事件。所以,一般來說,它公開了一些可以由發佈者調用的方法和一些可以由訂閱者註冊的事件。
在MVVM中,您可以使用此機制讓ViewModel引發一個事件,該事件表示應該打開一個View並預訂此事件的View Manager。視圖管理器應該能夠實例化視圖並附加正確的ViewModel。
爲防止ViewManager需要引用所有Views和ViewModels,可以傳遞給事件邏輯名稱(只是一個字符串),並讓View Manager使用反射或靜態列表查找匹配的View(Model)類型在配置文件中。
這樣你就不需要任何循環引用引用。事實上,當你發現你需要的引用與MVVM中的正確依賴關係相反時,你應該首先懷疑設置,然後考慮爲View和/或ViewModel使用基類。
MVVM沒有最佳實踐,因爲它是一種設計模式,每個人都根據自己的偏好實施不同的設計模式。我已經看到了很多不同的實現,但從未在單獨的項目中看到過視圖和視圖模型。我建議將它們保存在同一個項目的不同文件夾中,並將它們放在不同的命名空間中。
例如您的視圖模型可以走了的ViewModels文件夾並命名空間中的MyProject.ViewModels
你的意見可以在瀏覽文件夾中去,要在命名空間MyProject.Views
與同爲模型,如果你正在使用他們
我會等待,看看是否有創建一個視圖,視圖模型單獨項目的方式。如果這是不可能的,那麼MVVM似乎並不是一個真正的模塊化架構,因爲對視圖的徹底檢查仍然需要您在項目中進行修改;而在自己的項目中觀看會使這種檢修更容易。 – n00b
查看Rachel在this post的好回答。我也喜歡分開我的Views和ViewModels,因爲那時我知道什麼時候我搞砸了MVVM的基本規則。
您的ViewModel不應該有對視圖的任何引用,但視圖必須具有對ViewModel的引用。舉個例子,我的自定義閃屏工廠(這兩個重要行是「VAR視圖模型...」和「VAR閃屏......」):
namespace MyCompany.Factories
{
using System.Threading;
using MyCompany.Views;
using MyCompany.ViewModels;
public static class SplashScreenFactory
{
public static SplashScreenViewModel CreateSplashScreen(
string header, string title, string initialLoadingMessage, int minimumMessageDuration)
{
var viewModel = new SplashScreenViewModel(initialLoadingMessage, minimumMessageDuration)
{
Header = header,
Title = title
};
Thread thread = new Thread(() =>
{
var splashScreen = new SplashScreenView(viewModel);
splashScreen.Topmost = true;
splashScreen.Show();
splashScreen.Closed += (x, y) => splashScreen.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
return viewModel;
}
}
}
該工廠項目有兩個引用MyCompany.ViewModels and MyCompany.Views。 Views項目只能參考MyCompany.ViewModels。
我們首先從我們的調用者中啓動ViewModel(在這種情況下,來自SplashScreenFactory;或者如果你願意,也許從App.xaml.cs;我更喜歡使用我自己的Bootstrapper類,因爲這個討論之外的原因)。
然後,我們通過將ViewModel引用傳遞給View的構造函數來啓動View。這被稱爲Dependency Injection。您可能還需要爲窗口類編寫行爲,以便從ViewModel關閉窗口。所以現在我能做的,從我的引導程序類中的以下內容:
/// <summary>
/// Called from this project's App.xaml.cs file, or from the Deals Main Menu
/// </summary>
public class Bootstrapper
{
private SplashScreenViewModel _splashScreenVM;
public Bootstrapper()
{
// Display SplashScreen
_splashScreenVM = SplashScreenFactory.CreateSplashScreen(
"MyCompany Deals", "Planning Grid", "Creating Repositories...", 200);
// Overwrite GlobalInfo parameters and prepare an AuditLog to catch and log errors
ApplicationFactory.AuditedDisplay(
Assembly.GetExecutingAssembly().GetName(),
() =>
{
// Show overwritten version numbers from GlobalInfo on SplashScreen
_splashScreenVM.VersionString = string.Format("v{0}.{1}.{2}",
GlobalInfo.Version_Major, GlobalInfo.Version_Minor, GlobalInfo.Version_Build);
// Initiate ViewModel with new repositories
var viewModel = new PlanningGridViewModel(new MyCompany.Repositories.PlanningGridHeadersRepository(),
new MyCompany.Repositories.PlanningGridLinesRepository(),
_splashScreenVM);
// Initiate View with ViewModel as the DataContext
var view = new PlanningGridView(viewModel);
// Subscribe to View's Activated event
view.Activated += new EventHandler(Window_Activated);
// Display View
view.ShowDialog();
});
}
/// <summary>
/// Closes SplashScreen when the Window's Activated event is raised.
/// </summary>
/// <param name="sender">The Window that has activated.</param>
/// <param name="e">Arguments for the Activated event.</param>
private void Window_Activated(object sender, EventArgs e)
{
_splashScreenVM.ClosingView = true;
}
注意,我通過訂閱查看的激活事件,然後關閉該視圖與「ClosingView」布爾INotifyProperty,設置有在閃屏的視圖控件該視圖的「Close」DependencyProperty依次(聽起來很複雜,但是一旦你瞭解了DependencyProperties,就變得簡單了)。
問題是,不要試圖從RootFrame驅動你的導航。只需view.Show()RootFrame,並從RootFrameViewModel控制其餘部分。
希望這有助於:-)
嗨Erno,鑑於我的解決方案結構,我不確定消息代理會幫助。即使我將經紀人打包在自己的項目中,我相信我會在項目之間產生循環依賴關係,因爲經紀人項目需要同時參考View和ViewModel項目,並且ViewModel項目需要參考經紀人項目。您是否可以驗證或提供一個經紀人可以幫助解決我現在的解決方案結構的例子? – n00b
我加入到我的答案 –
謝謝埃爾諾。你有什麼樣的樣本可以聯繫我嗎? – n00b