代码之家  ›  专栏  ›  技术社区  ›  Jens

MVVM:如何设计承载不同应用程序的窗口?

  •  1
  • Jens  · 技术社区  · 14 年前

    我正在尝试使用MVVM设计模式实现一个简单的WPF数据分析应用程序,在该模式中,可以使用几种不同的方法来分析某些数据(从文件加载)。

    在第一个屏幕中,用户应该能够选择他喜欢使用的方法。在他完成这些操作并加载数据之后,应该用分析结果替换之前被方法选择屏幕占据的区域。

    当前,我的MainWindowViewModel具有“Object”类型的属性“CurrentViewModel”,该属性可以设置为方法选择视图模型或分析结果视图模型的上一个,然后使用DataTemplate呈现这些结果。

    我面临的问题是,我不知道不同的视图模型应该如何通信。

    • 方法选择屏幕需要 可用方法列表。
    • 主屏幕需要知道是什么方法 选择并选择合适的 显示结果的视图模型。
    • 加载的数据需要以某种方式进入类中进行实际工作,结果视图模型需要知道这一点,以知道从何处获取数据。

    我所能想到的一切都让主窗口视图模型在不同的视图模型和模型类之间进行所有协商。

    我如何优化设计这个?

    3 回复  |  直到 14 年前
        1
  •  2
  •   johnny g    14 年前

    • 首先,我们的应用程序或“shell”需要对“可用”方法进行显式枚举。我们可以提供任何我们想要的方式,无论是显式(通过配置)还是隐式(通过反射)。作为一种偏好,我更喜欢前者,因为它不那么“神奇”。

    • 到第二点,除了显式枚举之外,我们的shell还必须维护从选择到实现的映射。同样,这可以通过多种方式实现,但通常我们的枚举是一个“类型”列表,当选择了一个类型时,我们从工厂请求实现该类型。实现这种模式最简单的方法是利用控制容器的反转,如Castle Windsor、Unity、Ninject等。老实说,我不记得我们在内部使用了什么。

    例如,考虑一下,

    // a simple Plain Old C Object that describes business methods.
    public class BusinessMethod
    {
        // user-friendly name
        public string Name { get; set; }
        // type that actually implements
        public Type ImplementationType { get; set; }
    }
    
    // ... meanwhile, back on the ranch ...
    public void OnBusinessMethodSelection ()
    {
        // 1. if selected 
        if (BusinessMethodList.SelectedItem != null)
        {
    
            // 2. retrieve selected item
            BusinessMethod selected = 
                (BusinessMethod)(BusinessMethodList.SelectedItem);
    
            // 3. request implementation of selected item from
            // IoC container
            object implementation = 
                _container.Resolve (selected.ImplementationType);
        }
    }
    
    • 到你的第三点,我们需要一种不同部分的沟通方式。不幸的是,我们不能依赖于设计时方法(即命令和数据绑定),因此我们必须实现自己的事件聚合服务。基本上是“单例”(如在单例中 静态类)了解订阅服务器和发布服务器,最好是提供强类型参数的实现。对我们来说,幸运的是,许多更伟大的人已经走在我们前面,我们可能从他们的经历中获益。看看肯特·布加特的 Event Hub .

    下面是一个如何使用事件聚合器的示例

    // an example of a strongly typed subject. notice how subject
    // defines content. semanticly, when someone creates and publishes
    // an instance of this subject, they are requesting someone show
    // an analysis view based on data content,
    public class AnalysisSubject
    {
        // subject content, in this case a data result from
        // a business method
        public object Data { get; set; }
    }
    
    public class MainWindow : ISubscriber<AnalysisSubject> ...
    {
    
        // use whatever implementation of an IoC container we like
        // here i assume we abstract from implementation and use a
        // custom interface IContainer that exposes functionality 
        // that we need
        private readonly IContainer _container = null;
        public class MainWindow ()
        {
            // we're teh r00tz! we create an instance of IoC
            // container for use throughout application
            IContainer _container = new CustomContainer ();
    
            // our container exposes both parameterized and
            // type-parameterized resolve methods
            IEventHub events = _container.Resolve<IEventHub> ();
            events.Subscribe<AnalysisSubject> (this);
        }
    
        #region ISubscriber<AnalysisSubject>
    
        // part of strongly typed subscriptions is that we
        // may now handle strongly typed publications! yay!
        public void Receive (AnalysisSubject subject)
        {
            // 1. request to display analysis of data
            Type analysisType = subject.Data.GetType ();
    
            // 2. get view control based on payload type
            // 
            // NOTE: implicit usage below is not consistent
            // with previous invocations, here we are submitting
            // a type of something we already have, and actually
            // want back something that knows how to handle it.
            // most IoC containers can provide this functionality
            // through "facilities" add ons that accept a 
            // parameter\discriminator like below, and produce 
            // something in return.
            Control control = (Control)(_container.Resolve (analysisType));
    
            // [alternatively] if the above is too "magical" where
            // IAnalysisFactory is an interface we define for this
            // express purpose
            //IAnalysisFactory factory = _container.Resolve<IAnalysisFactory> ();
            //Control control = factory.GetAnalysisControlFor (analysisType);
    
            // 3. assign subject data to control
            Control.DataContext = subject.Data;
    
            // 4. display control
        }
    
        #endregion
    
    }
    

    以及出版物的一个例子

    public class SomeBusinessView
    {
    
        private readonly IEventHub _events = null;
    
        // we cannot function without an event aggregator of
        // some kind, so we declare our dependency as a contructor
        // dependency
        public SomeBusinessView (IEventHub events)
        {
            _events = events;
        }
    
        public void DoMyThang ()
        {
            // 1. do some business
            MyBusinessData data = SomeBusinessFunction ();
            // 2. publish complete event
            AnalysisSubject subject = new AnalysisSubject () { Data = data, };
            _events.Publish (subject);
        }
    
    }
    
        2
  •  1
  •   Jeff Gwaltney    14 年前

    每个视图通常都会得到一个视图模型。如果您在一个窗口中处理嵌套的用户控件,使用多个视图模型(每个控件一个)可能会造成杀伤力过度。

    如果每个控件都有一个viewModel,并且它们之间的通信断开连接,那么您可以拥有一个通用于所有viewModel的通用模型,或者拥有一个全局事件提供程序,允许模型彼此通信。(他们都可以参考变更通知等内容)。

    如果不为每个控件使用ViewModel,则将ViewModel绑定到主窗口,其中所有嵌套控件都将报告给主窗口,主窗口将报告给主窗口的ViewModel。

        3
  •  0
  •   REA_ANDREW    14 年前

    所选内容的数据源是否绑定到您拥有的视图模型的类型,路径可能是每个视图模型的名称(对于格式化名称,它是字符串属性,或者显式是类型名称)。在选择时,您将拥有已选择的相关对象。

    可能每个视图模型都引用一个公共的提供者,正如您所说,这个提供者执行结果分析,并且两个不同的视图只以不同的方式显示相同的数据。