C#WinForms模型 - 视图 - 演示者(被动视图)

时间:2010-11-30 19:12:04

标签: c# design-patterns mvp passive-view

我正在用C#开发一个WinForms应用程序。我在GUI编程方面经验有限,而且我必须在飞行中学到很多东西。话虽这么说,这就是我正在建设的。

请参阅常规GUI查看以下链接:

GUI http://img227.imageshack.us/img227/1084/program0.jpg

现在,我已经完成了很多工作,但是在非常糟糕的自治设计模式中。我不知道该项目是否会达到一定的规模,因此,是时候进行一些重大的重构了。

我一直在研究很多关于GUI设计模式的问题,我希望实现的模式是被动视图(参见http://martinfowler.com/eaaDev/PassiveScreen.html)。我正在寻找一些如何将这些结合在一起的帮助。

背景:

1)根据用户在“TreeView”中单击的内容,左下角的“列表”将显示可填充“编辑器”区域的对象列表。这些对象可能是TextBox或DataGridView。用户切换列表以选择他/她想要在“编辑器”中看到的内容

2)该模型本质上是一个包含数据和配置文件的文件夹。有一个外部程序在给定目录上运行,创建输出文件/文件夹等。我正在开发的这个程序旨在以用户友好的方式有效地管理/配置这些对象

3)我一直在做的事情的问题是它几乎不可能进行测试,因此转向MVP式的被动视图设计模式

我正在尝试使程序独立于View运行。我无法找到任何更复杂的交互式视图与被动视图模式一起使用的示例。

问题:

1)我是否需要为程序的整个“外观”实现一个大的接口/视图,然后为每个TreeView,Editor,Logger等实现子接口/子视图?或者这样做有更好的“结构”吗?

2)当谈到从视图“移交”事件到演示者/控制器时(无论你希望使用哪种术语W.R.T.被动视图设计模式),我应该采用什么方式做到这一点?有时我会有简单的属性需要更新,有时我需要一系列的步骤才能展开。

我会喜欢这个主题的建议和建议。我已经浏览了互联网,但我没有找到足够的例子来帮助我继续这个项目。

提前致谢!

丹尼尔

2 个答案:

答案 0 :(得分:18)

这是一个简单的示例,演示了使用MVP设计模式的被动视图的概念。因为我们使用被动视图,所以视图不了解演示者。演示者只需订阅视图发布的事件并采取相应行动。

首先,我们需要为我们的视图定义合约。这通常是使用接口实现的,本质上,我们希望与我们的视图具有非常松散的耦合。我们希望能够切换到不同的视图或事件创建模拟视图以进行单元测试。

这是一份合同,描述了一个用于显示客户信息的简单视图

public interface ICustomerManagementView
{
    void InitializeCustomers(ICustomer[] customers);
    void DisplayCustomer(ICustomer customer);
    event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}

它公开了一个方法 InitializeCustomers ,它将用于使用我们模型中的对象初始化视图。

我们还有一个事件 SelectedCustomerChanged ,我们的演示者将使用该事件来接收视图中已发生操作的通知。

我们签订合同后,我们就可以开始在演示者中处理这些互动。

public class CustomerManagementPresenter
{
    private ICustomer _selectedCustomer;
    private readonly ICustomerManagementView _managementView;
    private readonly ICustomerRepository _customerRepository;

    public CustomerManagementPresenter(ICustomerManagementView managementView, ICustomerRepository customerRepository)
    {
        _managementView = managementView;
        _managementView.SelectedCustomerChanged += this.SelectedCustomerChanged;

        _customerRepository = customerRepository;

        _managementView.InitializeCustomers(_customerRepository.FetchCustomers());
    }

    private void SelectedCustomerChanged(object sender, EventArgs<ICustomer> args)
    {
        // Perform some logic here to update the view
        if(_selectedCustomer != args.Value)
        {
            _selectedCustomer = args.Value;
            _managementView.DisplayCustomer(_selectedCustomer);
        }
    }
}

在演示者中,我们可以使用另一种名为dependency injection的设计模式来访问我们的视图以及我们可能需要的任何模型类。在此示例中,我有一个CustomerRepository,负责获取客户详细信息。

在构造函数中,我们有两行重要的代码,首先我们在视图中订阅了SelectedCustomerChanged事件,我们可以在这里执行相关的操作。其次,我们使用来自存储库的数据调用InitilaizeCustomers。

此时我们还没有为视图定义具体的实现,我们需要做的就是创建一个实现 ICustomerManagementView 的对象。例如,在Windows窗体应用程序中,我们可以执行以下操作

public partial class CustomerManagementView : Form, ICustomerManagementView
{
    public CustomerManagementView()
    {
        this.InitializeComponents();
    }

    public void InitializeCustomers(ICustomer[] customers)
    {
        // Populate the tree view with customer details
    }

    public void DisplayCustomer(ICustomer customer)
    {
        // Display the customer...
    }

    // Event handler that responds to node selection
    private void CustomerTreeViewAfterSelect(object sender, TreeViewEventArgs e)
    {
        var customer = e.Node.Tag as ICustomer;
        if(customer != null)
        {
            this.OnSelectedCustomerChanged(new EventArgs<ICustomer>(customer));
        }
    }

    // Protected method so that we can raise our event
    protected virtual void OnSelectedCustomerChanged(EventArgs<ICustomer> args)
    {
        var eventHandler = this.SelectedCustomerChanged;
        if(eventHandler != null)
        {
            eventHandler.Invoke(this, args);
        }
    }

    // Our view will raise an event each time the selected customer changes
    public event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged;
}

如果我们想测试我们的表示逻辑,我们可以模拟我们的视图并执行一些断言。

编辑:包含自定义事件参数

public class EventArgs<T> : EventArgs
{
    private readonly T _value;

    public EventArgs(T value)
    {
        _value = value;
    }

    public T Value
    {
        get { return _value; }
    }
}

答案 1 :(得分:0)

我会用自己的礼物将它们分成不同的视图,并使用“控制”的演示者/视图来管理它们之间的消息委派。这不仅可以帮助测试,而且还可以使您的控件满足SRP。

因此,在您的情况下,您可能有一个主窗口将实现的IFormManager,然后是IFileManager,ILoggerWindow等等。

虽然使用它可能有点过分,但我建议你看一下智能客户端软件工厂(来自Microsoft模式和实践团队) - 它不再被积极开发,但它有一个很好的实现MVP并且这种视图组合很好,所以可能会给你一些好主意。