在结构图中解析窗口,或者如何在WPF MVVM中管理多个窗口?

在结构图中解析窗口,或者如何在WPF MVVM中管理多个窗口?,wpf,mvvm,dependency-injection,window,inversion-of-control,Wpf,Mvvm,Dependency Injection,Window,Inversion Of Control,我一直在阅读MarkSeeman关于.NET依赖注入的书,我正在努力在WPF应用程序中配置复合根 我的容器将在应用程序启动方法中注册: protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var container = new Container(); container.Configure(r => {

我一直在阅读MarkSeeman关于.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>();
                        });
}
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Show();
一旦我进入主窗口,我可能想打开另一个窗口。到目前为止,我已经找到了一种方法,那就是创建一个窗口工厂,让窗口工厂解析窗口的实例。我必须确保窗口工厂在可能需要打开新窗口的每个视图模型中都可用。在我看来,这就像在我的应用程序周围传递IoC容器一样糟糕(我想到了服务定位器反模式)


你觉得这种方法对吗?我的直觉告诉我这是错误的,但我还没有想出更好的方法来实现这一点。

在99%的情况下,通过构造函数推送容器实例是一个坏主意,因为容器是一个服务定位器。这种方法的主要缺点是:

  • 容器具体实现的依赖性
  • 不清楚类的API,这也会导致脆弱的单元测试
以MVVM方式创建窗口的方法有很多:

  • 使用中介(比如MvvmLight中的IMessenger,Caliburn.Micro中的IEventAggregator)
  • 使用
  • 使用依附行为
  • 使用
  • 使用

  • 我认为,在实现行为模式(如
    中介体
    等)之前,需要确定一种通用模式,以简化应用程序结构。为此,即对于创建独立窗口,非常适合
    抽象工厂
    模式

    可以在
    ViewModel
    一侧使用
    IDialogService
    等方法创建窗口。但是我认为这个任务应该在
    视图
    一侧实现,因为
    窗口
    对象引用的是
    视图
    ,而不是
    视图模型
    。所以,您必须创建MVVM风格的体系结构,它允许使用设计模式创建独立的窗口

    我创建了一个项目,其中一个
    抽象工厂
    使用附加的行为在
    视图
    一侧创建了一个窗口<代码>抽象工厂还实现了单例模式,以创建全局访问点并确保新构造对象的唯一性。附加行为隐式实现模式装饰器,它是XAML端使用的抽象工厂的包装器。对于
    抽象工厂
    不引用位于
    ViewModel中的对象
    使用代理模式,该模式是带有数据模板的ContentControl,不带数据类型。还使用
    命令
    模式在对象之间执行独立操作。因此,该项目使用以下模式:

    • 抽象工厂
    • 独生子女
    • 装饰师
    • 代理
    • 命令
    项目结构如下所示:

    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);
        }
    }
    

    在附件中,行为具有附件依赖属性
    名称
    ,该属性以新窗口的名称传输。对于他来说,注册了
    PropertyChangedEvent
    ,这是一个调用,使方法成为一个抽象工厂:

    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
    与单例模式一起如下所示:

    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);
        }
    }
    
    对于属性
    Window.ContentTemplate
    set DataTemplate from resources<代码>内容模板负责可视化表示,为了从ViewModel绑定属性,需要将对象设置为内容。但是在这种情况下,
    抽象工厂
    将引用ViewModel,为了避免它们,使用代理模式如下:

    WindowOneProxyView

    <DataTemplate x:Key="WindowOneViewProxy">
        <ContentControl ContentTemplate="{StaticResource WindowOneViewRealObject}">
            <ViewModels:WindowOneViewModel />
        </ContentControl>
    </DataTemplate>
    
    DataTemplate
    中,代理未指定数据类型,但它位于真实对象中

    MainViewModel
    中,有一些命令可以简单地设置窗口名称,这将为附加的行为提供输入:

    main型号

    public class MainModel : NotificationObject
    {
        #region TypeName
    
        private string _typeName = null;
    
        public string TypeName
        {
            get
            {
                return _typeName;
            }
    
            set
            {
                _typeName = value;
                NotifyPropertyChanged("TypeName");
            }
        }
    
        #endregion
    }
    
    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();
        }
    }
    
    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
    }
    
    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();
        }
    }
    
    MainViewModel

    public class MainModel : NotificationObject
    {
        #region TypeName
    
        private string _typeName = null;
    
        public string TypeName
        {
            get
            {
                return _typeName;
            }
    
            set
            {
                _typeName = value;
                NotifyPropertyChanged("TypeName");
            }
        }
    
        #endregion
    }
    
    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();
        }
    }
    
    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
    }
    
    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();
        }
    }
    
    main窗口
    如下所示:

    <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>
    
    WindowOneViewModel

    public class MainModel : NotificationObject
    {
        #region TypeName
    
        private string _typeName = null;
    
        public string TypeName
        {
            get
            {
                return _typeName;
            }
    
            set
            {
                _typeName = value;
                NotifyPropertyChanged("TypeName");
            }
        }
    
        #endregion
    }
    
    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();
        }
    }
    
    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
    }
    
    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();
        }
    }
    
    此项目可在此网站获得

    输出

    main窗口

    WindowOne

    WindowTwo

    WindowThree


    IMHO,没有必要为了MVVM纯度而使解决方案过于复杂。您可能会让后续开发人员不理解您的优雅解决方案,并破坏它。事实上,这是一个很好的机会,因为“纯”实现由于复杂性往往不那么可读

    IMHO,任何解决方案,如果一个问题是在一个抽象下永久性地解决的,并且代码开销和使用的简单性都是最低的,那么它比每次使用解决方案时都要有相当大的开销要好,即使实现了“纯度”(它不会起任何作用)。在应用程序中显示对话框的问题必须解决一次,并且将来应该很容易使用它

    组合视图模型是非常好的,通过允许视图模型在没有戏剧的情况下进行交互,可以使生活更轻松

    可以创建一个对话框服务,作为应用程序中所有对话框需求的包装器。可以将对话框服务和需要在窗口中显示的子视图模型注入父视图模型。当需要显示窗口时,请让对话框服务执行此操作,并向其传递视图模型实例和视图名称

    注:未遵守或测试代码

     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.
    }
    
    公共类DialogService:IDialogService
    {
    事件聚合器;
    布尔法塔莱罗;
    //提供一个包装函数,该函数将连接视图和视图模型,并打开
    //对话
    公共窗口ShowCustomDialog(字符串名称,TViewModel viewModel