13

我正在构建一个 WinForms 应用程序,其 UI 仅包含 aNotifyIcon及其动态填充的ContextMenuStrip. 有一个MainForm将应用程序放在一起,但它永远不可见。

我着手尽可能可靠地构建它(使用 Autofac 处理对象图)并且对我的成功感到非常满意,即使在 O 部分也相处得很好。随着我目前正在实施的扩展,我似乎发现了我的设计中的一个缺陷,需要进行一些改造;我想知道我需要走的路,但对于如何准确定义依赖关系有点不清楚。

如上所述,菜单部分是在启动应用程序后动态填充的。为此,我定义了一个IToolStripPopulator接口:

public interface IToolStripPopulator
{
    System.Windows.Forms.ToolStrip PopulateToolStrip(System.Windows.Forms.ToolStrip toolstrip, EventHandler itemclick);
}

它的一个实现被注入到MainForm,Load()方法调用PopulateToolStrip()ContextMenuStrip表单中定义的处理程序。填充器的依赖项仅与获取用于菜单项的数据有关。

这种抽象已经通过几个演进步骤很好地工作,但当我需要多个事件处理程序时不再足够了,例如因为我正在创建几个不同的菜单项组 - 仍然隐藏在单个IToolStripPopulator界面后面,因为表单不应该是完全关心这个。

正如我所说,我想我知道一般结构应该是什么样的 - 我将IToolStripPopulator接口重命名为更具体的*并创建了一个新接口,其PopulateToolStrip()方法不带EventHandler参数,而是注入到对象中(也允许关于实现等所需的处理程序数量的更大灵活性)。这样,我的“最重要”IToolStripPopulator可以很容易地成为任意数量的特定适配器的适配器。

现在我不清楚的是我应该如何解决 EventHandler 依赖项。我认为处理程序都应该在 中定义MainForm,因为它具有正确响应菜单事件所需的所有其他依赖项,并且它还“拥有”菜单。这意味着我对IToolStripPopulator最终注入 MainForm 的对象的依赖项需要MainForm使用Lazy<T>.

我的第一个想法是定义一个IClickHandlerSource接口:

public interface IClickHandlerSource
{
    EventHandler GetClickHandler();
}

这是由我实现的MainForm,我的具体IToolStripPopulator实现依赖于Lazy<IClickHandlerSource>. 虽然这可行,但它是不灵活的。我要么必须为可能越来越多的处理程序(严重违反MainForm该类的 OCP)定义单独的接口,要么必须不断扩展IClickHandlerSource(主要违反 ISP)。直接依赖事件处理程序在消费者方面看起来是个好主意,但是通过惰性实例(或类似)的属性单独连接构造函数似乎非常混乱 - 如果可能的话。

我目前最好的选择似乎是:

public interface IEventHandlerSource
{
    EventHandler Get(EventHandlerType type);
}

该接口仍将由MainForm惰性单例实现并注入,并且EventHandlerType将是具有我需要的不同类型的自定义枚举。这仍然不是非常符合 OCP,但相当灵活。除了新的事件处理程序本身和(可能)新编写EventHandlerType的.MainFormIToolStripPopulator

或者....它的单独实现IEventHandlerSource(作为唯一对象)依赖于Lazy<MainForm>并将EventHandlerType选项解析为定义的特定处理程序MainForm

我正在尝试以MainForm一种可行的方式真正摆脱事件处理程序,但现在似乎还不太可能。

我最好的选择是什么,提供不同事件处理程序的最松散耦合和最优雅的解决方案?

[*是的,我可能应该单独留下这个名字来真正遵守 OCP,但这样看起来更好。]

4

4 回答 4

3

我最好的选择是什么,提供不同事件处理程序的最松散耦合和最优雅的解决方案?

不存在通用解决方案,它取决于全球应用程序架构。

如果你想要一个最松散的耦合,EventAggregator模式可以帮助你在这种情况下(你的IEventHandlerSource类似):

但是,应该非常谨慎地使用全局事件——它们会弄脏架构,因为在任何地方都可以订阅事件。

DI 和 IoC 中的重要一点:低依赖不应该知道高依赖。我认为,正如 Leon 之前所说,最好ITool<T>MainForm. 在某些操作之后MainForm将调用此工具中的某些方法。

于 2014-05-09T12:45:37.363 回答
1

您是否尝试过使用事件聚合器?请参阅:Caliburn 框架,事件聚合器 事件聚合器会将工具条与主窗体分离。

public interface IToolStripPopulator
{
    ToolStrip PopulateToolStrip(ToolStrip toolstrip);
}

为了方便起见,像这样包装聚合器:

public static class Event
{
    private static readonly IEventAggregator aggregator = new EventAggregator();
    public static IEventAggregator Aggregator
    {
        get
        {
            return aggregator;   
        }
    }
}

您定义一个或多个事件类,例如:

public class EventToolStripClick {
    public object Sender {get;set;}
    public EventArgs Args {get;set;}   
}

在创建工具条的控制器中,在 Click 处理程序中发布自定义事件,写入:

public void ControllerToolStripClick(object sender, EventArgs args )
{
    Event.Aggregator.Publish(new EventToolStripClick(){Sender=sender,Args=args)});
}

在 mainForm 中实现接口 IHandle

public class MainForm : Form, IHandle<EventToolStripClick>
{
    ...
    public void Handle(EventToolStripClick evt)
    {
        //your implementation here
    }
}
于 2015-02-09T21:31:03.157 回答
1

首先,我认为您不应该声称您的主窗体必须包含所有事件处理程序。您显然遇到了这样一个事实,即不同的处理程序具有不同的需求(可能还有不同的依赖项),那么为什么要将它们都安装到同一个类中呢?您可能会从其他语言处理事件的方式中获得一些灵感。WPF 使用命令,Java 有侦听器。两者都是对象,而不仅仅是一个委托,这使得它们在 IOC 场景中更容易处理。模拟这样的东西相当容易。您可以滥用工具栏项目上的标记,如下所示:Binding to commands in WinForms or use lambda expressions inside PopulateToolbar(请参阅Is there any wrong with using lambda for winforms event?) 将工具栏项与正确的命令相关联。这是假设因为 PopulateToolbar 知道需要创建哪些项目,它也知道每个项目属于哪个操作/命令。

表示动作的对象可以有自己的依赖注入,独立于主窗体或其他动作。然后可以稍后添加或删除具有自己操作的工具栏项目,而不会影响您的主窗体或任何其他操作,每个操作都可以独立测试和重构。

底线,停止考虑事件处理程序,开始考虑操作/命令本身作为一个实体,提出合适的模式将变得更容易。确保您了解Command Pattern,因为这正是您所需要的。

于 2014-09-06T22:00:06.460 回答
0

如果您在表单中托管子组件,它们可以填充主应用程序“shell”。

你可以有一个ShellComponent继承自 System.Windows.Forms.ContainerControl 的基类。这也将为您提供设计表面。而不是依赖 IToolStripPopulator,您可以拥有这样的 ITool。

public inteface ITool<T>
{
   int ToolIndex { get; }
   string Category { get; }
   Action OnClick(T eventArgs);
}

每次加载视图时,您都可以从 MainForm调用ShellComponentpublic List<ITool> OnAddTools(ToolStrip toolStrip)这样,组件将负责填充工具条。

然后,“ShellComponent”会向 IoC 容器询问实现ITool<T>. 这样,您ITool<T>就提供了一种分离事件(在MainForm或 中ContainerControl)并将其推送到任何类的方法。它还允许您准确定义要为参数传递的内容(与 MouseClickEventArgs 等相反)。

于 2012-06-28T16:09:56.523 回答