【发布时间】:2016-02-02 22:00:07
【问题描述】:
示例项目的来源: https://github.com/AntwanReno/navi
我在 WPF MVVM 中做了一个项目。它有三个子项目:WPF 应用程序、ViewModels (PCL) 和 Domain (PCL)。 WPF 只是一个 Window 与一个 Frame 和两个 Pages。我将展示代码,但建议在 repo 中克隆/分叉我准备好的示例。
这是 WPF 客户端的代码:
App.xaml.cs
namespace NaviWPFApp
{
using System.Windows;
using NaviWPFApp.Views;
using NaviWPFApp.Views.Pages;
public partial class App : Application
{
public static NavigationService Navigation;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
Navigation = new NavigationService(mainWindow.MyFrame);
Navigation.Navigate<FirstPage>();
}
}
}
App.xaml 只是:
<Application x:Class="NaviWPFApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NaviWPFApp"
x:Name="Application">
<Application.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
</Application.Resources>
</Application>
我有一个带框架的主窗口和两个非常相似的页面(没有代码隐藏):
<Window x:Class="NaviWPFApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="NaviWPFApp" Height="300" Width="300">
<Grid>
<Frame x:Name="MyFrame" Margin="10" />
</Grid>
</Window>
<Page x:Class="NaviWPFApp.Views.FirstPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Title="FirstPage"
DataContext="{Binding FirstPageViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
<Button Command="{Binding GoToSecondPageCommand}" Height="30" Content="Go to second page" />
</Grid>
</Page>
<Page x:Class="NaviWPFApp.Views.SecondPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="SecondPage"
DataContext="{Binding Path=SecondPageViewModel, Source={StaticResource ViewModelLocator}}">
<StackPanel Margin="0, 100, 0, 0">
<Button Command="{Binding CountSomethingCommand}" Content="Count something" Height="30" />
<Button Command="{Binding BackToFirstPageCommand}" Content="Go back to page 1" Height="30" />
</StackPanel>
</Page>
我的客户还有两个额外的类 ViewModelLocator 和 NavigationService - 用于页面之间的导航:
namespace NaviWPFApp
{
using NaviWPFApp.ViewModels.Pages;
public class ViewModelLocator
{
public FirstPageViewModel FirstPageViewModel => new FirstPageViewModel(App.Navigation);
public SecondPageViewModel SecondPageViewModel => new SecondPageViewModel(App.Navigation);
}
}
namespace NaviWPFApp
{
using System;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using NaviWPFApp.ViewModels.Common;
public class NavigationService : INavigationService
{
readonly Frame frame;
public NavigationService(Frame frame)
{
this.frame = frame;
}
public void GoBack()
{
frame.GoBack();
}
public void GoForward()
{
frame.GoForward();
}
public bool Navigate(string page)
{
var type = Assembly.GetExecutingAssembly().GetTypes().SingleOrDefault(a => a.Name.Equals(page));
if (type == null) return false;
var src = Activator.CreateInstance(type);
return frame.Navigate(src);
}
public bool Navigate<T>(object parameter = null)
{
var type = typeof(T);
return Navigate(type, parameter);
}
public bool Navigate(Type source, object parameter = null)
{
var src = Activator.CreateInstance(source);
return frame.Navigate(src, parameter);
}
}
}
这是我的 ViewModels(便携式) 项目:
UI中每个Page只有两个ViewModel类,INavigationService(NavigationService的实现和UI客户端我什么都不想知道),MyObservableObject和MyCommand。
MyObservableObject和MyCommand是INotifyPropertyChanged和ICommand接口的典型实现。
所以这是一个界面和两个视图模型:
public interface INavigationService
{
void GoForward();
void GoBack();
bool Navigate(string page);
}
namespace NaviWPFApp.ViewModels
{
public class FirstPageViewModel : MyObservableObject
{
private readonly INavigationService navigationService;
public FirstPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
public MyCommand GoToSecondPageCommand
{
get { return new MyCommand(x => navigationService.Navigate("SecondPage")); }
}
}
}
namespace NaviWPFApp.ViewModels
{
public class SecondPageViewModel : MyObservableObject
{
private readonly INavigationService navigationService;
private readonly BusinessLogic businessLogic;
public SecondPageViewModel(INavigationService navigationService, BusinessLogic businessLogic = null)
{
this.navigationService = navigationService;
this.businessLogic = businessLogic;
}
public MyCommand BackToFirstPageCommand
{
get { return new MyCommand(x => navigationService.Navigate("FirstPage")); }
}
public MyCommand CountSomethingCommand
{
get { return new MyCommand(x => businessLogic?.CountSomething()); }
}
}
}
还有我的业务逻辑,就是这样:
public class BusinessLogic
{
private int counter = 0;
public bool CountSomething()
{
return ++counter > 10;
}
}
依赖很简单:Domain什么都不知道,除了它自己的操作,ViewModel知道Domain,而对View和View一无所知。 . 好吧,这是我的问题——它知道 ViewModel,但 View 应该知道 Domain 吗?我将解释我的担忧,但这就是我的意思:
第一个关注点: 如您所见,导航全部在 ViewModel 中完成,业务逻辑仅在第二页视图模型中使用。 SecondPage 视图不需要了解逻辑。
第二个问题:因为我一直在尝试使用依赖注入,所以我想在程序开始时创建我的域对象(我只需要一个)。所以在protected override void OnStartup(StartupEventArgs e),所以在视图中。而且我不知道如何将它传递给在ViewModelLocator 中创建的第二个视图模型。
所以我的问题是:如何转换此代码,使其更加面向 ViewModel?我只想将我的域对象注入 ViewModel(它所属的地方),而不是 View。
感谢您的建议!
【问题讨论】:
-
tl;博士;对不起,伙计。你的问题是什么?看起来您已经在使用视图模型了。所以我看不出你在问什么。
-
我不能回答你的问题,但我可以推荐使用caliburn.micro,它是一个轻量级的 MVVM 框架。使用它可以自动解决一些设计问题。它带来了自己的 IoC 容器和许多其他有用的功能。