如何处理*许多*上下文菜单

时间:2011-01-27 15:41:47

标签: c# winforms vb6-migration

我正在用C#(使用Winforms)重写一个旧的VB6应用程序,该应用程序使用单个上下文菜单,其中多个项目基于名为“InitControls”的单片函数更改其Caption,Visible和Enabled特征

该函数长度为500行,主要由switch语句组成,该语句根据所选项的标签决定要启用哪些控件(有一个树视图和列表视图;它从活动的一个中选择所选项并获取其标签)。然后,它启用,禁用和修改可见项的文本,并清除任何无用的分隔符。原始版本使用ActiveBar(自定义控件),允许它在一个位置更改文本,并一次性显示菜单,上下文菜单和工具栏中的项目。

我目前正在重新实现C#中的行逻辑行,但我讨厌它因为我没有真正修复任何东西,只是将问题转换为一种新语言(并且可能在此过程中将其搞砸) 。我创建了一个类,允许我在一个地方更改任何“订阅”菜单项的文本,启用和可见属性,甚至为所有订阅菜单项添加/删除事件处理程序。它工作,甚至看起来显然是正确的,但我很确定必须有一个更好的方法。我的主要形式非常好。

处理复杂的上下文菜单和工具栏逻辑的标准.NET方法是什么?

1 个答案:

答案 0 :(得分:3)

据我所知,你基本上想要重构一个大的switch-case方法。谷歌搜索“切换案例重构”应该给你几个例子,你可以检查,找到最适合你的东西。

通常,当您重构switch的情况时,这意味着您希望将每个case块中的逻辑提取到一个新类中,这可能是所有情况共有的接口的实现。您的类的正确实现将取决于单个case语句的条件:这称为Strategy pattern,因为每个条件都需要不同的策略。

在您的情况下,您需要稍微扩展模式:上下文菜单中有多个候选,每个都能够处理某种节点类型。在这种情况下,您的右键单击处理程序需要让他们决定他们是否可以为某个节点提供功能。

<强> [编辑]

为了澄清一点,我将提供一个简单的例子。

我提到应该将各个实现提取到实现相同接口的类中,该接口应该根据当前条件负责更改菜单项的外观和状态。

interface IMenuStateManager
{
    // this method updates state of one or 
    // more menu elements, according to the 
    // specified selected node info
    void UpdateState(ISelectedNodeInfo info);   
}

我们对IMenuStateManager接口的第一个基本实现只会简单地调用其他管理器的实现。这称为Composite object pattern,因为它允许我们将一组对象视为单个对象:

// composite class for a list of menu managers
class CompositeMenuStateManager : IMenuStateManager
{
    private readonly IMenuStateManager[] _childManagers;

    // params keyword will allow as to pass a comma separated list
    // of managers, which is neat
    public CompositeMenuStateManager(params IMenuStateManager[] managers)
    {
        _childManagers = managers;
    }

    // this is where the job gets done, but composite
    // class doesn't do much work by itself
    public void UpdateState(ISelectedNodeInfo info)    
    {
        // allow each state manager to change its state
        foreach (IMenuStateManager mgr in _childManagers)
        {
            mgr.UpdateState(info);
        }
    }
}

现在,您仍有大量可能的菜单候选列表,但现在它们的逻辑分离到不同的类中,然后包装在单个复合对象中。

IMenuStateManager _menuManager = new CompositeMenuStateManager
(
    // note: each menu "manager" can manage one or more
    // items, if you find it useful. 
    // For example, ClipboardMenuStateManager can be
    // a composite manager itself (cut/copy/paste).

    new ClipboardMenuStateManager(some params),
    new SomeOtherMenuItemManager(various params),
    new YetAnotherMenuItemManager(various params),
    ...
);

我猜选择节点时菜单状态会更新,但这是您应该轻松适应您的应用程序的。该特定事件处理程序将整个职责委托给我们的复合菜单管理器:

void Node_Selected(sender object, EventArgs args)
{
    // find out which node was clicked
    Node node = object as Node;

    // get the data (model) node for this tree node
    INodeData data = node.Tag as INodeData;

    // create some info which will be passed to the manager.
    // you can pass information that might be useful,
    // or just simply pass the node data itself
    ISelectedNodeInfo info = new SelectedNodeInfo(data, some other stuff);

    // let the manager do the rest of the job
    _menuManager.UpdateState(info);
}

由于您可能有三个菜单项同时执行相同的工作(主菜单,上下文菜单,工具栏),您可能希望每个IMenuStateManager实现同时更新所有三个菜单项时间。最简单的方法是传递一个ToolStripItem对象数组,这是几个不同菜单元素的基本抽象类:

class PrintMenuManager : IMenuStateManager
{
    private readonly ToolStripItem[] _items;

    // this constructor can accept several menu elements
    public PrintMenuManager(params ToolStripItem[] items)
    {
        _items = items;
    }

    public void UpdateState(ISelectedNodeInfo node)
    {
        foreach (ToolStripItem item in _items)
        {
            // if node is printable, enable
            // all "print" menu items and buttons
            item.Enabled = (node.IsPrintable);
        }
    }
}

创建PrintMenuManager实例时,您可以传递相关的所有按钮和菜单项:

// (this should be one of the child managers in
// the composite menu manager, but you get it)
IMenuStateManager printMnuManaegr = new PrintMenuManager
(
    this.printMenuItem,
    this.printContextMenuItem,
    this.printToolbarButton,
);

哇,最后这个结果很长。 :)

好的,这是关于它的开始。