12 wpf dependency-injection window inversion-of-control mvvm
我一直在阅读Mark Seeman关于.NET中依赖注入的书,我很难在WPF应用程序中配置组合根.
我的容器将在应用程序启动方法中注册:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var container = new Container();
container.Configure(r =>
{
r.For<IAccountServices>().Use<AccountServicesProxy>();
r.For<MainWindow>().Use<MainWindow>();
});
}
Run Code Online (Sandbox Code Playgroud)
这有意义,因为应用程序启动代表我的组合根.
我的应用程序中的WPF窗口基于视图模型.视图模型使用构造函数注入.例如,我可以通过注入实现来构成视图模型IAccountServices.
在创建主窗口时,我可以在OnStartup方法中执行以下操作:
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Show();
Run Code Online (Sandbox Code Playgroud)
一旦我进入主窗口,我可能想要打开另一个窗口.到目前为止,我已经能够提出一种方法,即创建一个窗口工厂并请求窗口工厂解析窗口的实例.我必须确保窗口工厂在每个可能需要打开新窗口的视图模型中都可用.在我看来,这与在我的应用程序中传递IoC容器一样糟糕(服务定位器反模式会浮现在脑海中).
这种方法对你来说是否正确?我的直觉告诉我这是错的,但我还没有想出一个更好的方法来实现这个目标.
Ana*_*aev 12
我认为在实现行为模式(例如a Mediator等)之前,需要决定一个通用模式以便于应用程序结构.为此目的,即为创建独立窗口,非常适合的Abstract factory模式.
可以ViewModel使用诸如的方法在侧面实现窗口的创建IDialogService.但我认为这个任务应该在侧面实现View,因为Window对象指的是View而不是ViewModel.因此,您必须创建MVVM样式架构,它允许使用设计模式创建独立窗口.
我创建了一个项目,在该项目中使用附加的行为Abstract factory创建一个Window View.Abstract factory还实现了Singleton模式以创建全局访问点并确保新构造的对象的唯一性.附加行为隐式实现了模式Decorator,它是在XAML一侧使用的抽象工厂的包装器.对于a Abstract factory不引用位于其中的对象ViewModel使用代理模式,该模式是具有DataTemplate而没有DataType的ContentControl.还使用Command模式在对象之间进行独立操作.因此,该项目使用以下模式:
项目结构如下所示:

在附加的行为中附加了依赖属性Name,该属性在新窗口的名称中传输.对于他注册PropertyChangedEvent,这是一个调用Make方法的抽象工厂:
private static void IsFactoryStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var window = sender as Window;
if (window == null)
{
return;
}
if (e.NewValue is String && String.IsNullOrEmpty((string)e.NewValue) == false)
{
_typeWindow = (string)e.NewValue;
if (_typeWindow != null)
{
var newWindow = WindowFactory.Instance.Make(_typeWindow);
newWindow.Show();
}
}
}
Run Code Online (Sandbox Code Playgroud)
WindowFactory 与Singleton模式一起看起来像这样:
public class WindowFactory : IWindowFactory
{
#region WindowFactory Singleton Instance
private static WindowFactory _instance = null;
private static readonly object padlock = new object();
public static WindowFactory Instance
{
get
{
lock (padlock)
{
if (_instance == null)
{
_instance = new WindowFactory();
}
return _instance;
}
}
}
#endregion
public Window Make(string TypeWindow)
{
if (TypeWindow.Equals("WindowOneViewProxy"))
{
var windowOne = new Window();
windowOne.Width = 450;
windowOne.Height = 250;
windowOne.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowOne.Title = TypeWindow;
windowOne.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowOne;
}
else if (TypeWindow.Equals("WindowTwoViewProxy"))
{
var windowTwo = new Window();
windowTwo.Width = 500;
windowTwo.Height = 200;
windowTwo.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowTwo.Title = TypeWindow;
windowTwo.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowTwo;
}
else if (TypeWindow.Equals("WindowThreeViewProxy"))
{
var windowThree = new Window();
windowThree.Width = 400;
windowThree.Height = 140;
windowThree.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowThree.Title = TypeWindow;
windowThree.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowThree;
}
else
throw new Exception("Factory can not create a: {0}" + TypeWindow);
}
}
Run Code Online (Sandbox Code Playgroud)
对于属性Window.ContentTemplate设置DataTemplate来自资源.ContentTemplate负责可视化表示,为了从ViewModel绑定属性,需要将对象设置为Content.但在这种情况下,将Abstract factory引用ViewModel,并避免它们并使用代理模式,如下所示:
WindowOneProxyView
<DataTemplate x:Key="WindowOneViewProxy">
<ContentControl ContentTemplate="{StaticResource WindowOneViewRealObject}">
<ViewModels:WindowOneViewModel />
</ContentControl>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)
WindowOneViewRealObject
<DataTemplate x:Key="WindowOneViewRealObject" DataType="{x:Type ViewModels:WindowOneViewModel}">
<Grid>
<Label Content="{Binding Path=WindowOneModel.TextContent}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="Beige" />
<Button Content="One command"
Width="100"
Height="30"
HorizontalAlignment="Center"
Command="{Binding OneCommand}" />
</Grid>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)
在DataTemplate代理中没有指定DataType,但它是在真实对象中.
在MainViewModel命令中只需设置窗口名称,它将为附加行为提供输入:
MainModel
public class MainModel : NotificationObject
{
#region TypeName
private string _typeName = null;
public string TypeName
{
get
{
return _typeName;
}
set
{
_typeName = value;
NotifyPropertyChanged("TypeName");
}
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
MainViewModel
public class MainViewModel
{
#region MainModel
private MainModel _mainModel = null;
public MainModel MainModel
{
get
{
return _mainModel;
}
set
{
_mainModel = value;
}
}
#endregion
#region ShowWindowOneCommand
private ICommand _showWindowOneCommand = null;
public ICommand ShowWindowOneCommand
{
get
{
if (_showWindowOneCommand == null)
{
_showWindowOneCommand = new RelayCommand(param => this.ShowWindowOne(), null);
}
return _showWindowOneCommand;
}
}
private void ShowWindowOne()
{
MainModel.TypeName = "WindowOneViewProxy";
}
#endregion
#region ShowWindowTwoCommand
private ICommand _showWindowTwoCommand = null;
public ICommand ShowWindowTwoCommand
{
get
{
if (_showWindowTwoCommand == null)
{
_showWindowTwoCommand = new RelayCommand(param => this.ShowWindowTwo(), null);
}
return _showWindowTwoCommand;
}
}
private void ShowWindowTwo()
{
MainModel.TypeName = "WindowTwoViewProxy";
}
#endregion
#region ShowWindowThreeCommand
private ICommand _showWindowThreeCommand = null;
public ICommand ShowWindowThreeCommand
{
get
{
if (_showWindowThreeCommand == null)
{
_showWindowThreeCommand = new RelayCommand(param => this.ShowWindowThree(), null);
}
return _showWindowThreeCommand;
}
}
private void ShowWindowThree()
{
MainModel.TypeName = "WindowThreeViewProxy";
}
#endregion
public MainViewModel()
{
MainModel = new MainModel();
}
}
Run Code Online (Sandbox Code Playgroud)
MainWindow 看起来像:
<Window x:Class="WindowFactoryNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WindowFactoryNamespace.ViewModels"
xmlns:AttachedBehaviors="clr-namespace:WindowFactoryNamespace.AttachedBehaviors"
AttachedBehaviors:WindowFactoryBehavior.Name="{Binding Path=MainModel.TypeName}"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<this:MainViewModel />
</Window.DataContext>
<WrapPanel>
<Button Content="WindowOne"
Margin="10"
Command="{Binding ShowWindowOneCommand}" />
<Button Content="WindowTwo"
Margin="10"
Command="{Binding ShowWindowTwoCommand}" />
<Button Content="WindowThree"
Margin="10"
Command="{Binding ShowWindowThreeCommand}" />
</WrapPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)
View-ViewModel第一个窗口的测试看起来像这样(它们几乎完全相同):
WindowOneModel
public class WindowOneModel : NotificationObject
{
#region TextContent
private string _textContent = "Text content for WindowOneView";
public string TextContent
{
get
{
return _textContent;
}
set
{
_textContent = value;
NotifyPropertyChanged("TextContent");
}
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
WindowOneViewModel
public class WindowOneViewModel
{
#region WindowOneModel
private WindowOneModel _windowOneModel = null;
public WindowOneModel WindowOneModel
{
get
{
return _windowOneModel;
}
set
{
_windowOneModel = value;
}
}
#endregion
#region OneCommand
private ICommand _oneCommand = null;
public ICommand OneCommand
{
get
{
if (_oneCommand == null)
{
_oneCommand = new RelayCommand(param => this.One(), null);
}
return _oneCommand;
}
}
private void One()
{
WindowOneModel.TextContent = "Command One change TextContent";
}
#endregion
public WindowOneViewModel()
{
WindowOneModel = new WindowOneModel();
}
}
Run Code Online (Sandbox Code Playgroud)
该项目可在此处获得
link.
Output
MainWindow

WindowOne

WindowTwo

WindowThree

在 99% 的情况下,通过构造函数推送容器实例是一个坏主意,因为容器是一个服务定位器。这种方法的主要缺点是:
以 MVVM 方式创建窗口的方法有很多种:
| 归档时间: |
|
| 查看次数: |
1843 次 |
| 最近记录: |