[英]Resolving windows in Structure Map or how to manage multiple windows in WPF MVVM?
I have been reading Mark Seeman's book on dependency injection in .NET and I'm struggling to configure composition root in WPF application. 我一直在阅读Mark Seeman关于.NET中依赖注入的书,我正在努力在WPF应用程序中配置组合根。
My container will be registered in the application startup method: 我的容器将在应用程序启动方法中注册:
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>();
});
}
This makes sense as the application startup represents my composition root. 这有意义,因为应用程序启动代表我的组合根。
WPF windows in my application are based on view models. 我的应用程序中的WPF窗口基于视图模型。 View models use constructor injection.
视图模型使用构造函数注入。 Eg I may compose a view model by injecting implementation of
IAccountServices
. 例如,我可以通过注入
IAccountServices
实现来组成视图模型。
When it comes to creating my main window, I can do the following inside of the OnStartup method: 在创建主窗口时,我可以在OnStartup方法中执行以下操作:
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Show();
Once I'm inside of the main window, I might want open up another window. 一旦我进入主窗口,我可能想要打开另一个窗口。 So far I've been able to come up with one way of doing this, which is to create a window factory and ask window factory to resolve instance of the window.
到目前为止,我已经能够提出一种方法,即创建一个窗口工厂并请求窗口工厂解析窗口的实例。 I'll have to make sure that window factory is available in every view model that might need to open a new window.
我必须确保窗口工厂在每个可能需要打开新窗口的视图模型中都可用。 In my mind this is as bad as passing IoC container around my application (service locator anti-pattern comes to mind).
在我看来,这与在我的应用程序中传递IoC容器一样糟糕(服务定位器反模式会浮现在脑海中)。
Does this approach seem right to you? 这种方法对你来说是否正确? My gut feeling tells me that this is wrong, but I haven't come up with a better way of achieving this (yet).
我的直觉告诉我这是错的,但我还没有想出一个更好的方法来实现这个目标。
I think before implement patterns of behavior, such as a Mediator
, and the like, need to decide on a generic pattern for easy application structure. 我认为在实现行为模式之前,例如
Mediator
等,需要决定一个通用模式以便于应用程序结构。 For this purpose, namely, for the create independent windows, well suited Abstract factory
pattern. 为此目的,即为创建独立窗口,非常适合
Abstract factory
模式。
Creation of the windows can be implemented on the side ViewModel
using methods such as IDialogService
. 可以使用诸如
IDialogService
方法在ViewModel
侧实现窗口的创建。 But I think that this task should be implemented on the side View
, because the Window
object refers to the View
and not to ViewModel
. 但我认为这个任务应该在
View
侧面实现,因为Window
对象是指View
而不是ViewModel
。 So, you must create MVVM style architecture that it allows create independent windows using design patterns. 因此,您必须创建MVVM样式架构,它允许使用设计模式创建独立窗口。
I created a project in which an Abstract factory
creates a Window on the side of the View
using the attached behavior. 我创建了一个项目,其中一个
Abstract factory
使用附加的行为在View
的一侧创建一个Window。 Abstract factory
also implements the Singleton pattern to create a global point of access and to ensure the uniqueness of the newly constructed object. Abstract factory
还实现了Singleton模式,以创建全局访问点并确保新构造对象的唯一性。 Attached behavior implicitly implements pattern Decorator who is a wrapper for an abstract factory that is used on the side of XAML. 附加行为隐式实现了模式Decorator,它是在XAML一侧使用的抽象工厂的包装器。 To an
Abstract factory
does not refer to objects which are located in ViewModel
is used a Proxy pattern which is a ContentControl with DataTemplate without DataType. 对于
Abstract factory
,不引用位于ViewModel
对象使用代理模式,该模式是具有不带DataType的DataTemplate的ContentControl。 Also used Command
pattern for independent action between objects. 还使用
Command
模式在对象之间进行独立操作。 As a result, this project uses the following patterns: 因此,该项目使用以下模式:
The project structure looks like this: 项目结构如下所示:
In the attached behavior has attached dependency property Name
, which is transmitted in the name of the new window. 在附加的行为中附加了依赖属性
Name
,它以新窗口的名称传输。 For him registered PropertyChangedEvent
, which is a call Make method an abstract factory: 为他注册了
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();
}
}
}
WindowFactory
together with the Singleton pattern looks like this: 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);
}
}
For the property Window.ContentTemplate
set DataTemplate from resources. 对于属性
Window.ContentTemplate
从资源中设置DataTemplate。 ContentTemplate
is responsible for the visual representation, in order to bind properties from ViewModel, you need to set the object to Content. ContentTemplate
负责可视化表示,为了从ViewModel绑定属性,需要将对象设置为Content。 But in this case, the Abstract factory
reference will to ViewModel, and to avoid them and using the proxy pattern as follows: 但在这种情况下,
Abstract factory
引用将为ViewModel,并避免它们并使用代理模式如下:
WindowOneProxyView
<DataTemplate x:Key="WindowOneViewProxy">
<ContentControl ContentTemplate="{StaticResource WindowOneViewRealObject}">
<ViewModels:WindowOneViewModel />
</ContentControl>
</DataTemplate>
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>
In DataTemplate
proxy is not specified DataType, but it is in the real object. 在
DataTemplate
代理不是指定DataType,而是在真实对象中。
In MainViewModel
has commands to simply set the window name, which will give input for attached behavior: 在
MainViewModel
有命令来简单地设置窗口名称,它将为附加的行为提供输入:
MainModel
public class MainModel : NotificationObject
{
#region TypeName
private string _typeName = null;
public string TypeName
{
get
{
return _typeName;
}
set
{
_typeName = value;
NotifyPropertyChanged("TypeName");
}
}
#endregion
}
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();
}
}
MainWindow
looks as: 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>
Test View-ViewModel
for the first window looks like this (they practically identical): 第一个窗口的Test
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
}
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();
}
}
Output
MainWindow
WindowOne
WindowTwo
WindowThree
IMHO, there is no need to over complicate the solution for the sake of MVVM purity. 恕我直言,为了MVVM纯度,没有必要使解决方案复杂化。 You risk the subsequent developers not understanding your elegant solution and break it.
您可能会冒后续开发人员不理解您的优雅解决方案并将其打破。 In fact there is a good chance of that as "pure" implementations tend to be not that readable because of the complexity.
事实上,由于复杂性,“纯”实现往往不具有可读性,因此很有可能。
IMHO, any solution where a problem is permanently solved under an abstraction with minimal code overhead and simplicity in its usage is better than doing considerable overhead every time the solution is used even if "purity" is achieved(it won't serve any purpose). 恕我直言,任何以抽象方式永久解决问题的解决方案,只需最少的代码开销和简单的使用,就比每次使用解决方案时花费大量开销更好,即使达到“纯度”(它不会用于任何目的) 。 The problem of showing dialog in the application has to be solved once and it should be easy to use it in the future.
在应用程序中显示对话框的问题必须解决一次,并且将来应该很容易使用它。
Composing view models is perfectly fine, and could make life easier by allowing view models to interact without drama 组合视图模型非常精细,通过允许视图模型在没有戏剧性的情况下进行交互,可以使生活更轻松
A dialog service can be created which will act as a wrapper for all your dialog needs in the application. 可以创建一个对话框服务,它将充当应用程序中所有对话框需求的包装器。 You can inject the Dialog Service and the child view models which needs to be displayed in a window, to your parent view model.
您可以将对话框服务和需要在窗口中显示的子视图模型注入父视图模型。 When you need to display the window, ask the Dialog service to do it, passing it the view model instance and view name.
当您需要显示窗口时,请求Dialog服务执行此操作,并将视图模型实例和视图名称传递给它。
Note:code is not complied or tested 注意:代码未被编译或测试
public class DialogService : IDialogService
{
IEventAggregator _eventAggregator;
bool _fatalError;
//Provides a wrapper function which will connect your view and view model and open a
//dialog
public Window ShowCustomDialog<TViewModel>(string name, TViewModel viewModel, bool
modal, double left, double top, Action<bool?> OnClose, int width, int height)
{
if (_fatalError == true)
{
return null;
}
Window view = new Window(name);
if (viewModel != null)
{
view.DataContext = viewModel;
}
if (left != -1.0 && top != -1.0)
{
view.WindowStartupLocation = WindowStartupLocation.Manual;
view.Left = left;
view.Top = top;
}
else
{
view.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
if (width != -1 && height != -1)
{
view.Width = width;
view.Height = height;
}
view.Closed += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(false);
if (OnClose != null)
{
OnClose(e.DialogResult);
}
};
view.Loaded += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(true);
Window window = o as Window;
if (window != null)
{
double dialogWidth = window.ActualWidth;
double screenWidth =
Application.Current.RootVisual.RenderSize.Width;
double dialogLeft = window.Left;
if (dialogLeft + dialogWidth > screenWidth)
{
window.Left = screenWidth - dialogWidth;
}
double dialogHeight = window.ActualHeight;
double screenHeight =
Application.Current.RootVisual.RenderSize.Height;
double dialogTop = window.Top;
if (dialogTop + dialogHeight > screenHeight)
{
window.Top = screenHeight - dialogHeight;
}
}
};
if (modal)
{
view.ShowDialog();
}
else
{
view.Show();
}
return view;
}
//Add more functions. For example to pop up a message box etc.
}
Usage 用法
public class ComposedVM
{
public ViewModelA objA{get;set;}
public ViewModelB objB{get;set;}
IDialogService dialogService{get;set;}
public ComposedVM(ViewModelA a, ViewModelB b, IDialogService dlg )
{
objA = a;
objB = b;
dialogService = dlg
}
public void OnShowWindowACommand()
{
dialogService .ShowCustomDialog<object>(
DialogNames.ViewA/*view name constant*/, objA, true, -1.0, -1.0,
result =>
{
if (result == true)
{
dialogService.ShowMessageDialog(ApplicationStrings.SuccessFulOperation);
}
});
}
}
An event/message based communication can be used between modules. 可以在模块之间使用基于事件/消息的通信。 Using it for related view models in a module is an overkill IMHO.
将它用于模块中的相关视图模型是一种矫枉过正的恕我直言。
Pushing container instance through constructor is a bad idea in 99% of cases, because container is a service locator. 在99%的情况下,通过构造函数推送容器实例是一个坏主意,因为容器是服务定位器。 The main disadvantages of this approach are:
这种方法的主要缺点是:
There are many ways to create window in MVVM fashion: 有许多方法可以用MVVM方式创建窗口:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.