我想了解构建MVVM解决方案的最佳实践。
目前,我已为View和ViewModel创建了一个单独的项目。我的View项目引用了我的ViewModel项目。到现在为止还挺好。在实现导航时,我的ViewModel类需要访问RootFrame才能导航,但RootFrame位于View项目中的App.xaml中。所以这里有一个循环依赖问题。
我应该使用推荐的结构吗?我可以将这一切都归结为一个大项目,但为了解耦View和ViewModel,我觉得拥有单独的项目是一种更好的方法。
答案 0 :(得分:3)
在实现导航时,我的ViewModel类需要访问RootFrame
这是一个错误的假设。
您可以使用负责在发布者(ViewModel)和订阅者(负责打开视图的某个对象)之间分发消息的消息代理(单个对象)。
大多数MVVM框架都有这样的经纪人。
关于依赖关系
经纪人的唯一责任是筹集活动。因此,一般来说,它暴露了一些可以由发布者调用的方法和一些可以由订阅者注册的事件。
在MVVM中,您可以使用此机制让ViewModel引发一个表示应该打开View的事件和一个订阅此事件的View Manager。视图管理器应该能够实例化视图并附加正确的ViewModel。
要防止ViewManager需要对所有Views和ViewModel的引用,您可以传递给事件逻辑名称(只是一个字符串)并让View Manager通过使用反射或静态列表查找匹配的View(Model)类型配置文件。
这样您就不需要任何循环引用引用。实际上,当您发现需要引用与MVVM中的正确依赖关系时,您应首先怀疑设置,然后考虑使用View和/或ViewModel的基类。
答案 1 :(得分:1)
MVVM没有最佳实践,因为它是一种设计模式,每个人都根据自己的喜好实现不同的设计模式。我已经看到了很多不同的实现,但从未在单独的项目中看到过视图和视图模型。我建议将它们保存在同一个项目的不同文件夹中,并将它们放在不同的命名空间中。
e.g。您的View Models可以进入ViewModels文件夹并位于名称空间MyProject.ViewModels
中您的视图可以放在Views文件夹中,并位于命名空间MyProject.Views
中如果使用模型,那么模型也是如此
答案 2 :(得分:0)
在this post查看Rachel的好答案。我也喜欢将我的Views和ViewModel分开,因为我知道什么时候我搞砸了MVVM基本规则。
您的ViewModel不应该对View有任何引用,但View必须具有对ViewModel的引用。例如,考虑我的自定义SplashScreen工厂(两个重要的行是“ var viewModel ... ”和“ var splashScreen ... ”):
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;
}
}
}
The Factories项目引用了 MyCompany.ViewModels 和 MyCompany.Views 。 Views项目仅引用 MyCompany.ViewModels 。
我们首先从我们的调用者启动ViewModel(在这种情况下,从SplashScreenFactory;或者如果你愿意,可以从App.xaml.cs启动;我更喜欢使用我自己的Bootstrapper类,而不是讨论的原因。)
然后我们通过将ViewModel引用传递给View的构造函数来启动View。这称为Dependency Injection。您可能还需要为Window类编写一个Behavior,以便从ViewModel关闭窗口。所以现在我可以从我的Bootstrapper类中执行以下操作:
/// <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;
}
请注意,我通过订阅View的Activated事件控制SplashScreen的View,然后使用“ClosingView”布尔INotifyProperty关闭视图,该设置依次设置View的“Close”DependencyProperty(听起来很复杂,但是一旦你得到要知道DependencyProperties,它就变得简单了。
重点是,不要试图从RootFrame驱动导航。只需查看.Show()RootFrame,并从RootFrameViewModel控制其余部分。
希望这会有所帮助: - )