【问题标题】:Data structure to represent menu which can have submenu表示可以有子菜单的菜单的数据结构
【发布时间】:2012-10-23 18:49:42
【问题描述】:

例如我可以有这样的东西,

A
B
 ba
 bb
C
 Ca
D

现在我有一个二维数组,但这不是很通用,因为如果我想将最大子级别从 2 扩展到 3,我需要另一个维度。有什么建议吗?

【问题讨论】:

  • 你需要一个递归关系。
  • 使用树?每个节点都有一个标签,可以有零个或多个子节点,可推广到任意数量的嵌套菜单。根节点的标签被忽略,只考虑其子节点。
  • @Seth 我明白你的评论,但我不知道如何把它放到代码中
  • @Wug,我需要保留插入顺序。不知道如何使用树轻松实现这一目标。
  • @user814628 看看 sampson-chen 的回答。您可以使用Component 类的适当属性和Composite 类的add() 操作轻松控制插入顺序。

标签: c++


【解决方案1】:

复合模式在这里是一个合适的应用:

(来自维基百科:)http://en.wikipedia.org/wiki/Composite_pattern

在你的情况下:

  • 创建一个名为“Menu”的基类(对应上图中的Component部分)
  • 创建一个名为“MenuItem”的派生类(这对应于上图中的Leaf部分)
  • 创建一个名为“SubMenu”的派生类(这对应于上图中的 Composite 部分)SubMenu 可以包含更多的菜单 - 可以是更多的 MenuItem 或 SubMenu。

您可以通过为每个 SubMenu 对象实现一个计数器,根据它们在复合“SubMenu”中的插入顺序来实现所需的菜单元素排序:每次调用aSubMenu.add(newMenuItemOrSubMenu)aSubMenu 应该增加自己的计数器并标记带有订购号的新产品。 (具体实现细节由您决定,您根本不必使用单独的计数器,只需使用列表或数组即可)

【讨论】:

    【解决方案2】:

    也许是这样的:

    class MenuNode
    {
    public:
        MenuNode(std::string new_label);
        void Add(MenuNode * new_node);
    private:
        std::string label;
        std::vector<MenuNode *> children; // changed to vector to preserve order
    };
    

    用法:

    MenuNode menu("root"),
             file("File"),
             edit("Edit"),
             open("Open..."),
             close("Close"),
             save("Save..."),
             prefs("Preferences"),
             yes_foo("Activate Foo"),
             no_foo("Deactivate Foo");
    
    menu.Add(&file);
    menu.Add(&edit);
    
    file.Add(&open);
    file.Add(&close);
    file.Add(&save);
    
    edit.Add(&prefs);
    
    prefs.Add(&yes_foo);
    prefs.Add(&no_foo);
    

    代表:

    Main Menu
      File
        Open...
        Close
        Save...
      Edit
        Preferences
          Activate Foo
          Deactivate Foo
    

    请注意此示例的明显缺陷,即对(可能)临时变量地址的依赖。您将无法在函数中创建它并返回它。

    实现的琐碎部分也丢失了,例如无法遍历示例代码中节点的私有状态。

    【讨论】:

    • 我想我会推荐动态内存和unique_ptr。这个答案缺乏封装。但这个答案是正确的想法。
    • 等等,不,向量应该直接包含MenuNodes。 ideone.com/kU2RPa
    • 这适用于所有向量实现还是存在不使用间接的平面实现?
    • 现在说什么?我发布的代码将适用于所有矢量实现。我知道“间接”是什么,但我不确定“不使用间接的平面向量”是什么意思。如果向量使用了 SBO,我发布的代码应该还是可以的。
    • 没关系,所有的向量都会使用间接,其他任何东西都没有任何意义。然后继续
    【解决方案3】:

    sampson-chen 提到的复合设计模式是我为小型开发监视器所做的实现的正确方法,让您从菜单结构中选择一些测试方法。

    我有一个基类“菜单项”,从中派生子菜单和叶子(菜单项)。叶子只是执行某些操作,而子菜单会打开另一个菜单级别。

    例如,基类可能与此类似(当您喜欢使用 shared_pointers 时):

     /*************************************************************//*!
     * @brief The base class for all menu entry types (sub menus and sub menu entries/items)
     ******************************************************************/
    class MenuEntry : public boost::enable_shared_from_this<MenuEntry>
    {
    
      public:
        virtual ~MenuEntry(){}
    
    /**************************************************************//*!
     * @brief Default implementation to add menu entries; has to be re implemented
     * @param[in] newSubMenuEntry - the new menu entry (another sub menu or leaf)
     **************************************************************/
    virtual void add(boost::shared_ptr<MenuEntry> newSubMenuEntry)=0;
    
    /*****************************************************************//*!
     * @brief Default implementation for call to menu entries; always returns false
     * @return false - the function has not been re implemented
     ****************************************************************/
    virtual bool call(void)
    {
      // the member function has not been re-implemented
      return false;
    }
    
    /*****************************************************************************//*!
     * @brief Default implementation, has to be reimplemented
     * @return emptyVector - an empty vector
     *********************************************************************************/
    virtual std::vector<boost::shared_ptr<MenuEntry> > getChildren(void)
    {
      std::vector<boost::shared_ptr<MenuEntry> > emptyVector;
      return emptyVector;
    }
    
    
    /*******************************************************************************//*!
     * @brief Gives a pointer to the parent of the actual menu entry
     * @return m_parent - pointer to the parent
     ******************************************************************************/
    boost::shared_ptr<MenuEntry> parent(void)
    {
      return m_parent;
    }
    
    /***************************************************************************//*!
     * @brief Default implementation for selecting a menu entry
     * @param[in] desiredMenuEntry - the desired menu entry which shall be selected
     * @return notExisting - a pointer to <b>this</b>
     **********************************************************************************/
    virtual boost::shared_ptr<MenuEntry> select(boost::shared_ptr<MenuEntry> desiredMenuEntry)=0;
    
    /**************************************************************************//*!
     * <B>Criticality: C0 \n\n</B>
     * @brief Sets a pointer to the parent of new menu entry
     * @param[in] pointerToParent - pointer to the parent
     ****************************************************************************/
    void setParent(boost::shared_ptr<MenuEntry> pointerToParent)
    {
      m_parent = pointerToParent;
    }
    
    /***************************************************************************//*!
     * @brief Default implementation for destroying children
     *****************************************************************************/
    virtual void destroy(void)=0;
    
      protected:
        /************************************************************************//*!
         * @brief Constructor for a menu entry (sub menu or leaf)
         * @param[in] menuEntryName - the name for the sub menu or leaf
         *************************************************************************/
        MenuEntry(std::string menuEntryName)
        {
          m_menuEntryName = menuEntryName;
        }
    
    };
    

    在 select 方法中,我通过返回值检查是否有执行某些操作的叶子或子菜单,我必须为此更改指针。

    为方便起见,您可以添加在子菜单中找到所有子菜单条目以进行显示的方法,或者使用实际菜单路径等为您构建标题行的方法...另一个想法是扫描方法您的菜单树,以避免重复菜单条目。

    【讨论】:

      【解决方案4】:

      使用一棵树。无论如何,这最好在树中定义。

      其中:rootNode 连接到ABCDB 连接到 babbC 连接到 Ca。等等。

      【讨论】:

        【解决方案5】:


        我在 Github 上使用 struct 作为详细信息

        ConsoleMenu.h

        typedef int (*MENU_FUNCTION_POINTER)();
        
        typedef struct _MENU MENU;
        typedef MENU* MENU_POINTER;
        
        struct _MENU
        {
            bool isValid; // C++ can not compare struct is NULL
            string text;
            MENU_FUNCTION_POINTER func;
        
            bool isOpen;
            MENU_POINTER childMenu;
            MENU_POINTER parent;
        };
        
        class ConsoleMenu
        {
        private:
            static MENU _chapters[];
            static MENU _chapter1[];
            static MENU _chapter2[];
        };
        

        ConsoleMenu.cpp

        MENU ConsoleMenu::_chapters[] = {
            {true, "Chapter 1", NULL, false, ConsoleMenu::_chapter1, NULL},
            {true, "Chapter 2", NULL, false, ConsoleMenu::_chapter2, NULL},
            {true, "Chapter 3", NULL, false, NULL, NULL},
            {true, "Chapter 4", NULL, false, NULL, NULL},
            {false, "unused", NULL, false, NULL, NULL}
        };
        
        MENU ConsoleMenu::_chapter1[] = {
            {true, "Example 1.1", Example1, false, NULL, ConsoleMenu::_chapters},
            {true, "Example 1.2", Example2, false, NULL, ConsoleMenu::_chapters},
            {false, "unused", NULL, false, NULL, NULL}
        };
        
        MENU ConsoleMenu::_chapter2[] = {
            {true, "Example 2.1", NULL, false, NULL, ConsoleMenu::_chapters},
            {true, "Example 2.2", NULL, false, NULL, ConsoleMenu::_chapters},
            {true, "Example 2.3", NULL, false, NULL, ConsoleMenu::_chapters},
            {false, "unused", NULL, false, NULL, NULL}
        };
        

        【讨论】:

          猜你喜欢
          • 2014-02-08
          • 1970-01-01
          • 2014-09-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-23
          • 1970-01-01
          • 2017-11-21
          相关资源
          最近更新 更多