创建步骤
创建Mvvm结构的开发环境很简单,首先在View层的App.xaml文件中引用ViewModel层,设置全局资源。
其次,给主窗体设置数据源并设置绑定
接着,在ViewModel层新建定位器和主窗体绑定的类
定位器中相关代码
如果是使用NuGet导入MvvmLight,就会自动创建好了这个ViewModelLocator类,在类的构造函数中会带有这个
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
关于这个我也不清楚有什么作用,毕竟SimpleIoc.Default中就继承了IServiceLocator。
这里使用的SimpleIoc,可以去以下链接浏览,这里对mvvmLight做了详细的介绍:
https://www.cnblogs.com/maanshancss/p/5794780.html
简要介绍一下,就是使用了SimpleIoc容器,使用依赖注入的方式将ViewModel类注册进去容器中,也可将一些服务,公共全局变量注册进去,例如:
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IDataService, DataService>();//公共变量交换器
SimpleIoc.Default.Register<MainWindowViewModel>();//主窗口ViewModel
SimpleIoc.Default.Register<TextViewModel>();//测试窗口ViewModel
//创建导航服务,并将导航服务注册进容器
var navigationService = this.CreateNavigationService();
SimpleIoc.Default.Register<INavigationService>(() => navigationService);
}
/// <summary>
/// 主窗口
/// </summary>
public MainWindowViewModel MainWindowViewModel
{
get
{
return ServiceLocator.Current.GetInstance<MainWindowViewModel>();
}
}
/// <summary>
/// 测试窗口
/// </summary>
public TextViewModel TextViewModel
{
get
{
return ServiceLocator.Current.GetInstance<TextViewModel>();
}
}
/// <summary>
/// 创建导航服务
/// </summary>
/// <returns></returns>
private INavigationService CreateNavigationService()
{
var navigationService = new NavigationService();
//主窗口
navigationService.Configure(ViewNames.MAIN_VIEW, new Uri("/MainWindow.xaml", UriKind.RelativeOrAbsolute));
//测试窗口页
navigationService.Configure(ViewNames.TEXT_VIEW, new Uri("/TextView.xaml", UriKind.RelativeOrAbsolute));
return navigationService;
}
在这里只是在ViewModelLocator构造函数中将ViewModel注册进 SimpleIoc容器,而创建导航服务的作用在于设置各个页面的地址使用Key值标识,后续可以通过Key值进行定位导航的是那个页面,则会跳转到这个key所指向的View。以下为封装的一个导航服务类,主要处理页面导航的一些记录以及实现界面跳转和回退等。
/// <summary>
/// 封装导航服务
/// </summary>
public class NavigationService : ViewModelBase, INavigationService
{
#region 成员变量
/// <summary>
/// 历史列表
/// </summary>
private readonly List<string> historic;
/// <summary>
/// 当前导航页
/// </summary>
private string currentPageKey;
/// <summary>
/// 是否正在导航
/// </summary>
private bool isNavigating;
/// <summary>
/// 导航页面地址Key
/// </summary>
private readonly Dictionary<string, Uri> pageUrlByKey;
/// <summary>
/// 导航页单例Key
/// </summary>
private readonly Dictionary<string, Page> pageInstancesByKey;
/// <summary>
/// 当前框架容器
/// </summary>
private Frame currentFrame = null;
/// <summary>
/// 导航参数
/// </summary>
public object Parameter { get; private set; }
#endregion
/// <summary>
/// 构造函数
/// </summary>
public NavigationService()
{
pageUrlByKey = new Dictionary<string, Uri>();
pageInstancesByKey = new Dictionary<string, Page>();
historic = new List<string>();
}
/// <summary>
/// 当前导航页面Key
/// </summary>
public string CurrentPageKey
{
get
{
return currentPageKey;
}
private set
{
Set(() => CurrentPageKey, ref currentPageKey, value);
}
}
/// <summary>
/// 回退
/// </summary>
public void GoBack()
{
if (historic.Count > 1)
{
historic.RemoveAt(historic.Count - 1);
NavigateTo(historic.Last(), "Back");
}
}
/// <summary>
/// 跳转下一个页面
/// </summary>
/// <param name="pageKey"></param>
public void NavigateTo(string pageKey)
{
NavigateTo(pageKey, "Next");
}
/// <summary>
/// 页面跳转
/// </summary>
/// <param name="pageKey">页面配置的key</param>
/// <param name="parameter">参数</param>
public void NavigateTo(string pageKey, object parameter)
{
if (isNavigating)
{
return;
}
isNavigating = true;
try
{
lock (pageUrlByKey)
{
if (!pageUrlByKey.ContainsKey(pageKey))
{
throw new ArgumentException(string.Format("No such page: {0} ", pageKey), "pageKey");
}
if (currentFrame == null)
{
var frame = FindControlHelper.Instance.GetChildObject<Frame>(Application.Current.MainWindow, "MainFrame");
if (frame != null)
{
this.currentFrame = frame;
this.currentFrame.Navigated -= CurrentFrame_Navigated;
this.currentFrame.Navigated += CurrentFrame_Navigated;
}
else
{
throw new Exception("frame is null");
}
}
bool reloadPage = false;
if (parameter != null)
{
bool.TryParse(parameter.ToString(), out reloadPage);
}
if (this.currentFrame != null)
{
if (this.pageInstancesByKey.ContainsKey(pageKey) && reloadPage == false)
{
this.currentFrame.NavigationService.Navigate(this.pageInstancesByKey[pageKey]);
}
else
{
this.currentFrame.Source = pageUrlByKey[pageKey];
this.CurrentPageKey = pageKey;
}
}
Parameter = parameter;
if (parameter.ToString().Equals("Next"))//记录可以用于回退功能的页面跳转历史
{
historic.Add(pageKey);
}
CurrentPageKey = pageKey;
}
}
finally
{
isNavigating = false;
}
}
/// <summary>
/// 配置导航页面地址与Key值字典
/// </summary>
/// <param name="key">Key值</param>
/// <param name="pageType">页面地址</param>
public void Configure(string key, Uri pageType)
{
lock (pageUrlByKey)
{
if (pageUrlByKey.ContainsKey(key))
{
pageUrlByKey[key] = pageType;
}
else
{
pageUrlByKey.Add(key, pageType);
}
}
}
#region 辅助方法
/// <summary>
/// 框架中执行页面跳转后
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CurrentFrame_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
Page page = e.Content as Page;
this.AddPage(CurrentPageKey, page);
}
/// <summary>
/// 添加实例了的页面以及key值
/// </summary>
/// <param name="key">key</param>
/// <param name="instance">页面</param>
private void AddPage(string key, Page page)
{
lock (pageInstancesByKey)
{
if (!pageInstancesByKey.ContainsKey(key) && page != null)
{
if (this.Parameter == null)
{
pageInstancesByKey.Add(key, page);
}
else
{
bool reloadPage = false;
bool.TryParse(this.Parameter.ToString(), out reloadPage);
if (!reloadPage)
{
pageInstancesByKey.Add(key, page);
}
}
}
}
}
/// <summary>
/// 获取导航的页面
/// </summary>
/// <param name="parent">导航对象(页面)</param>
/// <param name="name">对象名称(页面名称)</param>
/// <returns></returns>
private FrameworkElement GetDescendantFromName(DependencyObject parent, string name)
{
var count = VisualTreeHelper.GetChildrenCount(parent);
if (count < 1)
{
return null;
}
for (var i = 0; i < count; i++)
{
var frameworkElement = VisualTreeHelper.GetChild(parent, i) as FrameworkElement;
if (frameworkElement != null)
{
if (frameworkElement.Name == name)
{
return frameworkElement;
}
frameworkElement = GetDescendantFromName(frameworkElement, name);
if (frameworkElement != null)
{
return frameworkElement;
}
}
}
return null;
}
#endregion
}
而下面这些将ViewModel作为属性通过单例实例化自身的代码并不会都在程序启动就实例,而是使用到哪个页面,这个页面绑定的数据源是哪个ViewModel,通过服务定位模式,将会来ViewModelLocator中找到这个ViewModel的属性执行该属性的这段单例。
程序运行的步骤如下:
依赖注入理解
/// <summary>
/// 主窗口
/// </summary>
public MainWindowViewModel MainWindowViewModel
{
get
{
return ServiceLocator.Current.GetInstance<MainWindowViewModel>();
}
}
/// <summary>
/// 测试窗口
/// </summary>
public TextViewModel TextViewModel
{
get
{
return ServiceLocator.Current.GetInstance<TextViewModel>();
}
}
从上面代码可以看到ViewModel实例的方式是通过单例的方式,单例方式不能够想new一个类那样传递参数。
那如果我的ViewModel构造函数需要传参整么办。这就用到了SimpleIoc中的依赖注入了,在一开始我们就将相关的参数注册进了容器中,这个时候实例化ViewModel的时候,就会自动找到参数类型符合的变量作为构造参数去执行。当然如果构造函数所需的参数在容器中没有,则将会自动创建一个该类型的参数
启动程序后,这个值显示到了界面,这个界面的参数就是通过依赖注入原理在ViewModel中进行传递的。
再次推荐两个能够提供理解MvvmLigh和依赖注入、服务定位模式(Service Locator Pattern)的链接
https://www.cnblogs.com/maanshancss/p/5794780.html
https://www.cnblogs.com/gaochundong/archive/2013/04/12/service_locator_pattern.html
比较推荐第一个链接吧。
还有一个关于这个服务定位我是出于懵逼的状态的,这个SimpleIoc自身就继承了,为什么还要去使用ServiceLocator
public ViewModelLocator()
{
//ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IDataService, DataService>();//公共变量交换器
SimpleIoc.Default.Register<MainWindowViewModel>();//主窗口ViewModel
SimpleIoc.Default.Register<TextViewModel>();//测试窗口ViewModel
//创建导航服务
var navigationService = this.CreateNavigationService();
SimpleIoc.Default.Register<INavigationService>(() => navigationService);
}
/// <summary>
/// 主窗口
/// </summary>
public MainWindowViewModel MainWindowViewModel
{
get
{
//return ServiceLocator.Current.GetInstance<MainWindowViewModel>();
return SimpleIoc.Default.GetInstance<MainWindowViewModel>();//这样使用也可以
}
}