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

发布于 2024-10-14 18:11:19 字数 536 浏览 5 评论 0原文

我正在用 C#(使用 Winforms)重写一个旧的 VB6 应用程序,该应用程序使用单个上下文菜单,其中包含多个项目,这些项目基于名为“InitControls”的整体函数更改其标题、可见性和已启用特征

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

我目前只是在 C# 中重新实现了 line for line 的逻辑行,但我讨厌它,因为我并没有真正修复任何东西,只是将问题放入一种新语言中(并且可能会在这个过程中搞砸)。我创建了一个类,它允许我在一个地方更改任何“订阅”菜单项的文本、启用和可见属性,甚至添加/删除所有订阅菜单项的事件处理程序。它有效,甚至看起来似乎是正确的,但我很确定一定有更好的方法。我的 MainForm 非常大。

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

I'm re-writing in C# (with Winforms) an old VB6 app that uses a single context menu with multiple Items that change their Caption, Visible, and Enabled traits based on a monolithic function called "InitControls"

The function is 500 lines long and consists primarily of a switch statement that decides what controls to enable based on the selected item's tag (there's a tree view and list view; it selects the selected item from the active one and gets its tag). It then enables, disables, and modifies the text of the visible items, and clears any useless Separators. The original uses ActiveBar (a custom control) which allows it to change the text in one place and display the item in menus, context menus, and toolbars all at once.

I'm currently just re-implementing the logic line for line in C#, but I hate it because I'm not really fixing anything, just putting the problem into a new language (and possibly screwing it up in the process). I created a class that allowed me to change the text, enabled and visible properties of any "subscribed" Menu Items in one place and even add/remove event handlers for all subscriBed menu items. It works, and even seems apparently correct, but I'm pretty sure there's got to be a better way. My MainForm is ENORMOUS.

What is the standard .NET way of handling complex Context Menu and Toolbar logic?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

傾城如夢未必闌珊 2024-10-21 18:11:19

据我了解,您基本上想要重构一个大型 switch-case 方法。谷歌搜索“switch case refactoring”应该会给你几个例子,你可以查看以找到最适合你的东西。

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

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

[编辑]

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

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

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

哇,最后结果是一篇很长的文章。 :)

好的,这就是开始。

From what I understand, you basically want to refactor a large switch-case method. Googling for "switch case refactoring" should give you several examples you can check out to find something that suits you best.

Usually, when you are refactoring a switch case, this means that you want to extract logic from each case block into a new class, possibly an implementation of an interface common to all cases. The right implentation of your class will depend on the condition of an individual case statement: this is called a Strategy pattern, because each condition demands a different strategy.

In your case, you need to slightly extend the pattern: you have a number of candidates for the context menu, each of them being able to handle a certain node type. In that case, your right-click handler needs to let them decide if they can provide functionality for a certain node.

[Edit]

To clarify a bit, I will provide a simple example.

I mentioned that individual implementations should be extracted into classes which implement the same interface, which should be responsible for changing menu items' appearance and state, based on the current condition.

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

Our first, basic implementation of the IMenuStateManager interface will do nothing more that simply call other managers' implementations. This is called a Composite object pattern, because it allows us to treat a group of objects as a single object:

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

Now, you still have an enormous list of possible menu candidates, but now their logic is separated into different classes, and then wrapped in a single composite object.

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),
    ...
);

I guess that menu states get updated when a node is selected, but this is something you should easily adapt to your app. That particular event handler delegates the whole responsibility to our composite menu manager:

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

Since you will probably have three menu items doing the same job at the same time (main menu, context menu, toolbar), you will probably want to make each IMenuStateManager implementation update all three of them at the same time. The simplest way should be to to pass an array of ToolStripItem objects, which is the base abstract class for several different menu elements:

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

When creating the PrintMenuManager instance, you can pass all buttons and menu items which are related:

// (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,
);

Whew, this turned out to be a lengthy one at the end. :)

Ok, that's about it for a start.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文