使用多维数组创建菜单

时间:2022-02-13 21:35:04

I am very new to c and c++. I am a c# programmer by day and rarely use arrays anymore. Nevertheless, I am working on a c++ project. I want to create a menu for a GUI that will resemble:

我对c和c ++很新。我白天是一名c#程序员,很少再使用数组了。不过,我正在研究一个c ++项目。我想为GUI创建一个菜单,类似于:

Menu Item 1
    Item 1.1
        Item 1.1.1
        Item 1.1.2
    Item 1.2
        Item 1.2.1
        Item 1.2.2
Menu Item 2
    Item 2.1
        Item 2.1.1
        Item 2.1.2
    Item 2.2
        Item 2.2.1
        Item 2.2.2

I am able to create a 2 dimensional version, but want to build a 3 dimensional version. Any help is greatly appreciated.

我能够创建一个二维版本,但想要构建一个三维版本。任何帮助是极大的赞赏。

Here is my 2 dimensional version:

这是我的二维版本:

    const char* menu[2][4] = 
    {
        {"Menu Item 1", "1.1 Item", "1.2 Item", "1.3 Item"},
        {"Menu Item 2", "2.1 Item", "2.2 Item", "2.3 Item"}

    };


    for(int i = 0; i < NUMOFCHO; i++){

        printf("%s\n\r", menu[i][0]);

        for(int k = 1; k < NUMOFITEMS; k++){
            printf("--%s\n\r", menu[i][k]);         

        }
    }   

3 个解决方案

#1


2  

Static arrays probably aren't the best data structure to use if you want any flexibility.

如果您想要任何灵活性,静态数组可能不是最好的数据结构。

I suggest using a dynamically sized container like std::vector.

我建议使用像std :: vector这样的动态大小的容器。

You'll often see menu structures like this using a "tree" type hierarchy, where each level of the menu has a name, and its children (sub menu items). Here's how you might go about using these concepts.

您经常会看到使用“树”类型层次结构的菜单结构,其中菜单的每个级别都有一个名称及其子项(子菜单项)。以下是您如何使用这些概念。

struct MenuItem
{
    // name of the menu item
    std::string name;

    // sub menu items
    std::vector<MenuItem> children;
};

Traversing the menu is easiest using recursion, here's a simple example.

使用递归遍历菜单是最简单的,这是一个简单的例子。

void printMenu(const std::vector<MenuItem> &menu, int level) 
{
    // string containing tabs depending on the level of the menu
    std::string prefix( level, '\t' );

    // we're visiting the next menu level
    ++level;

    // iterate over this level of the menu
    for (const auto &item : menu) 
    {
        // display the item name
        std::cout << prefix << item.name << std::endl;

        // visit this level's children
        printMenu( item.children, level );
    }
}

Here's how you would initialize the sample menu structure provided.

以下是初始化提供的示例菜单结构的方法。

std::vector<MenuItem> menu 
{
    {
        { 
            { "Menu Item 1" },
            { 
                {
                    { "Item 1.1" },
                    { 
                        {
                            { "Item 1.1.1" },
                            { {} }
                        },
                        {
                            { "Item 1.1.2" },
                            { {} }
                        }
                    }
                },
                {
                    { "Item 1.2" },
                    {
                        {
                            { "Item 1.2.1" },
                            { {} }
                        },
                        {
                            { "Item 1.2.2" },
                            { {} }
                        }
                    }
                },
            }
        },
        {
            { "Menu Item 2" },
            {
                {
                    { "Item 2.1" },
                    {
                        {
                            { "Item 2.1.1" },
                            { {} }
                        },
                        {
                            { "Item 2.1.2" },
                            { {} }
                        }
                    }
                },
                {
                    { "Item 2.2" },
                    {
                        {
                            { "Item 2.2.1" },
                            { {} }
                        },
                        {
                            { "Item 2.2.2" },
                            { {} }
                        }
                    }
                },
            }
        }
    }
};

To access a specific menu item you can do the following.

要访问特定菜单项,您可以执行以下操作。

// Item 2.2.1
std::string item = menu[ 1 ].children[ 1 ].children[ 0 ].name;

Or even adding an item after the structure has been initialized.

甚至在结构初始化后添加项目。

menu[ 1 ].children[ 1 ].children.push_back({
    { "Testing" }, // name
    { {} }         // children
});

/*
    Menu Item 1
        Item 1.1
                Item 1.1.1
                Item 1.1.2
        Item 1.2
                Item 1.2.1
                Item 1.2.2
    Menu Item 2
        Item 2.1
                Item 2.1.1
                Item 2.1.2
        Item 2.2
                Item 2.2.1
                Item 2.2.2
                Testing
*/

You can find a complete example here.

你可以在这里找到一个完整的例子。

#2


1  

Arrays are usually a poor choice for nested menus.

对于嵌套菜单,数组通常是不好的选择。

In my experience menus are usually linked together rather than in an array:

根据我的经验,菜单通常是链接在一起而不是在数组中:

Menu1  
  Item1 --> Menu1.1
    |  
    v  
  Item2 --> Menu1.2
    |
    v
  Item3

I usually I have a menu contain items. Each item can point to another submenu. This allows for the case where an item does not have a submenu.

我通常有一个菜单包含项目。每个项目都可以指向另一个子菜单。这允许项目没有子菜单的情况。

#3


0  

I have a solution that I implemented a little while back in C. It's set up to go as deep as 8 levels but there's technically no limit to depth. It's for embedded so it uses static memory for everything but it could easily be modified to use dynamic allocation. You can find the sources on my github page but I'll go through and explain them a little bit here.

我有一个解决方案,我在C中实现了一段时间。它设置为深达8级,但技术上没有深度限制。它是嵌入式的,所以它使用静态内存,但可以很容易地修改它以使用动态分配。你可以在我的github页面上找到这些资源,但我会在这里详细解释一下。

The Menu is built around a k-ary tree (k-tree) which is implemented as a binary tree (b-tree). Unlike a regular k-tree where a node can have multiple children which would make sense for a menu system, since the system is memory and resource constrained, a binary tree makes more since. It allows for statically allocated nodes without using fixed sized arrays to store child nodes. This implementation is a little confusing at first but is actually used in several resource sensitive systems such as the Linux kernel.

菜单围绕k-ary树(k-tree)构建,其实现为二叉树(b-tree)。与常规k树不同,其中节点可以具有多个子菜单,这对于菜单系统是有意义的,因为系统是内存和资源约束的,所以二进制树生成更多。它允许静态分配的节点,而不使用固定大小的数组来存储子节点。这个实现起初有点令人困惑,但实际上在几个资源敏感的系统中使用,例如Linux内核。

A regular k-tree is represented as:

常规的k树表示为:

struct t {
   int n;
   int numKids;
   struct t **kids;
}

and the resulting tree struture looks like:

结果树的结构看起来像:

   TOP
   |
   [ list of kids ]
   |     |      |
   k1    k2     k3
   |
   |
   [list of kids]
   |     |     |
   k1.1  k1.2  k1.3

This representation works if you have things like malloc but on an embedded system where using malloc is anathema, if you want to add another child, use a static array of limited length in addition to having to record the number of kids in the array in the structure.

如果你有类似malloc的东西,但是在使用malloc是诅咒的嵌入式系统上,如果你想添加另一个子节点,使用有限长度的静态数组,除了必须记录数组中的数量结构体。

Instead, the following structure is used:

而是使用以下结构:

struct t {
   int n;
   struct t *firstChildNode;
   struct t *firstSiblingNode;
   struct t *trueParentNode;
   struct t *fakeParentNode;
};

and the tree representation looks like:

并且树表示如下:


  [TOP NODE ] <-
  -----------   \
  ^|  ^          \
  ||  |           \ fP
  ||  |tP, fP      \
  ||  |_______  fS  \__________     fS
  ||  [NODE 2]------>[NODE 2.1]---------->NULL
  ||  ------- <------ ---------
  ||       |      tP      |
  ||       |fC            |fC
  ||fC     V              V
  ||      NULL          NULL
  ||
  |tP, fP
  ||
  |V_____
  [NODE1]
  -------
     ^
     |tP, fp
     |_________   fS
     [NODE 1.1] -----> NULL
         |
         |fC
         |
         V
       NULL

Where:

  • fP = fakeParentNode - this pointer is used to figure out where the actual parent as it would appear in a classical k-ary tree.
  • fP = fakeParentNode - 此指针用于确定实际父级在经典k-ary树中的显示位置。

  • tP = trueParentNode - this pointer is used to show where this node is actually attached since it could be either a child node of another node or a sibling.
  • tP = trueParentNode - 此指针用于显示此节点实际连接的位置,因为它可能是另一个节点的子节点或兄弟节点。

  • fC = firstChildNode - pointer to the first child node.
  • fC = firstChildNode - 指向第一个子节点的指针。

  • fS = firstSiblingNode - pointer to the first sibling node.
  • fS = firstSiblingNode - 指向第一个兄弟节点的指针。

From any node, you can only access the first child or the first sibling, making this a classical b-tree. From that first child, you can access it's children and siblings. Likewise for the first sibling pointer. This way all the nodes are stored in a list which can be comprised of statically allocated structures. The true and fake Parent pointers are for easily tracing the ancestry of each child node. The fake parent pointer is used to map a classical k-tree structure over this b-tree.

从任何节点,您只能访问第一个孩子或第一个兄弟,使其成为经典的b树。从第一个孩子,你可以访问它的孩子和兄弟姐妹。同样对于第一个兄弟指针。这样,所有节点都存储在一个列表中,该列表可以由静态分配的结构组成。真假父指针用于轻松跟踪每个子节点的祖先。伪父指针用于映射该b树上的经典k树结构。

There are several advantages to this seemingly confusing approach:

这种看似令人困惑的方法有几个优点:

  • Each node is a fixed size and can be statically allocated.
  • 每个节点都是固定大小,可以静态分配。

  • No arrays are needed for representing child nodes.
  • 表示子节点不需要数组。

  • Many algorithms can be easily expressed since it's just a b-tree.
  • 许多算法都可以很容易地表达,因为它只是一个b树。

This implementation of the menu is limited in that the functionality to remove nodes is not present since this doesn't seem to be a necessary feature for a menu. To add a menu or menu item is very simple. See example in menu_top.c

菜单的这种实现受到限制,因为删除节点的功能不存在,因为这似乎不是菜单的必要特征。添加菜单或菜单项非常简单。请参阅menu_top.c中的示例

This design was inspired by https://blog.mozilla.org/nnethercote/2012/03/07/n-ary-trees-in-c/

此设计灵感来自https://blog.mozilla.org/nnethercote/2012/03/07/n-ary-trees-in-c/

Code for ktree.c:

ktree.c的代码:

/* Includes ------------------------------------------------------------------*/
#include "ktree.h"
#include "qp_port.h"                                        /* for QP support */
#include "project_includes.h"

/* Compile-time called macros ------------------------------------------------*/
Q_DEFINE_THIS_FILE                  /* For QSPY to know the name of this file */
DBG_DEFINE_THIS_MODULE( DBG_MODL_DBG );/* For debug system to ID this module */

/* Private typedefs ----------------------------------------------------------*/
/* Private defines -----------------------------------------------------------*/
/* Private macros ------------------------------------------------------------*/
/* Private variables and Local objects ---------------------------------------*/
static treeNode_t Menu1;
char *const Menu1Txt = "Menu 1";

   static treeNode_t MenuItem1;
   char *const MenuItem1_1Txt = "Menu Item 1.1";

   static treeNode_t MenuItem2;
   char *const MenuItem1_2Txt = "Menu Item 1.2";

   static treeNode_t MenuItem3;
   char *const MenuItem1_3Txt = "Menu Item 1.3";

   static treeNode_t MenuItem4;
   char *const MenuItem1_4Txt = "Menu Item 1.4";

static treeNode_t Menu2;
char *const Menu2Txt = "Menu 2";

   static treeNode_t MenuItem2_1;
   char *const MenuItem2_1Txt = "Menu Item 2.1";

   static treeNode_t MenuItem2_2;
   char *const MenuItem2_2Txt = "Menu Item 2.2";

   static treeNode_t MenuItem2_3;
   char *const MenuItem2_3Txt = "Menu Item 2.3";

   static treeNode_t MenuItem2_4;
   char *const MenuItem2_4Txt = "Menu Item 2.4";

static treeNode_t Menu3;
char *const Menu3Txt = "Menu 3";

   static treeNode_t Menu3_1;
   char *const Menu3_1Txt = "Menu 3_1";

      static treeNode_t MenuItem3_1_1;
      char *const MenuItem3_1_1Txt = "Menu Item 3.1.1";

      static treeNode_t MenuItem3_1_2;
      char *const MenuItem3_1_2Txt = "Menu Item 3.1.2";

      static treeNode_t MenuItem3_1_3;
      char *const MenuItem3_1_3Txt = "Menu Item 3.1.3";

      static treeNode_t MenuItem3_1_4;
      char *const MenuItem3_1_4Txt = "Menu Item 3.1.4";

   static treeNode_t Menu3_2;
   char *const Menu3_2Txt = "Menu 3_2";

      static treeNode_t MenuItem3_2_1;
      char *const MenuItem3_2_1Txt = "Menu Item 3.2.1";

      static treeNode_t MenuItem3_2_2;
      char *const MenuItem3_2_2Txt = "Menu Item 3.2.2";

      static treeNode_t MenuItem3_2_3;
      char *const MenuItem3_2_3Txt = "Menu Item 3.2.3";

      static treeNode_t MenuItem3_2_4;
      char *const MenuItem3_2_4Txt = "Menu Item 3.2.4";

   static treeNode_t Menu3_3;
   char *const Menu3_3Txt = "Menu 3_3";

      static treeNode_t Menu3_3_1;
      char *const Menu3_3_1Txt = "Menu 3_3_1";

         static treeNode_t MenuItem3_3_1_1;
         char *const MenuItem3_3_1_1Txt = "Menu Item 3.3.1.1";

         static treeNode_t MenuItem3_3_1_2;
         char *const MenuItem3_3_1_2Txt = "Menu Item 3.3.1.2";

         static treeNode_t MenuItem3_3_1_3;
         char *const MenuItem3_3_1_3Txt = "Menu Item 3.3.1.3";

      static treeNode_t Menu3_3_2;
      char *const Menu3_3_2Txt = "Menu 3_3_2";

         static treeNode_t MenuItem3_3_2_1;
         char *const MenuItem3_3_2_1Txt = "Menu Item 3.3.2.1";

         static treeNode_t MenuItem3_3_2_2;
         char *const MenuItem3_3_2_2Txt = "Menu Item 3.3.2.2";

         static treeNode_t MenuItem3_3_2_3;
         char *const MenuItem3_3_2_3Txt = "Menu Item 3.3.2.3";

      static treeNode_t Menu3_3_3;
      char *const Menu3_3_3Txt = "Menu 3_3_3";

         static treeNode_t MenuItem3_3_3_1;
         char *const MenuItem3_3_3_1Txt = "Menu Item 3.3.3.1";

         static treeNode_t MenuItem3_3_3_2;
         char *const MenuItem3_3_3_2Txt = "Menu Item 3.3.3.2";

         static treeNode_t MenuItem3_3_3_3;
         char *const MenuItem3_3_3_3Txt = "Menu Item 3.3.3.3";

      static treeNode_t Menu3_3_4;
      char *const Menu3_3_4Txt = "Menu 3_3_4";

         static treeNode_t MenuItem3_3_4_1;
         char *const MenuItem3_3_4_1Txt = "Menu Item 3.3.4.1";

         static treeNode_t MenuItem3_3_4_2;
         char *const MenuItem3_3_4_2Txt = "Menu Item 3.3.4.2";

         static treeNode_t MenuItem3_3_4_3;
         char *const MenuItem3_3_4_3Txt = "Menu Item 3.3.4.3";

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************/
void KTREE_nodeCtor( treeNode_t *node )
{
   /* Initialize the root node */
   node->firstChildNode = NULL;
   node->firstSiblingNode = NULL;
   node->fakeParentNode = NULL;
   node->trueParentNode = NULL;
}

/******************************************************************************/
uint8_t KTREE_findDepth( treeNode_t *node, uint8_t currDepth )
{
   /* Make sure the node is not null.  If this happens, it's a bug. */
   if ( NULL == node ) {
      ERR_printf("!!!Node is null\n");
      return( 0 );
   }

   if ( NULL == node->fakeParentNode ) {
      /* If no fake parents exist, this is the top.  Return the calculated
       * depth */
      return( currDepth );
   } else {
      /* Parent exists; recurse after incrementing the depth */
      return (KTREE_findDepth( node->fakeParentNode, ++currDepth ));
   }
}

/******************************************************************************/
void KTREE_addChild(
      treeNode_t *childToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
)
{
   /* Find the parent node */
   if ( NULL == trueParentNode ) {
      childToAdd->trueParentNode = NULL;
      childToAdd->fakeParentNode = NULL;
      return;
   } else if ( NULL == trueParentNode->firstChildNode ) {

      /* If the node where we want to add a child currently has no children,
       * add the child node here to the childNode field. */
      trueParentNode->firstChildNode = childToAdd;
//      dbg_slow_printf("whereToAddChild=0x%08x\n", (uint32_t )whereToAddChild);
   } else {
      /* Otherwise, add a sibling to the child of this node */
      KTREE_addNextSibling( childToAdd, trueParentNode->firstChildNode, fakeParentNode);
//      dbg_slow_printf("Adding a sibling to the child of node '%s'\n", trueParentNode->text);
   }
}

/******************************************************************************/
void KTREE_addNextSibling(
      treeNode_t *siblingToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
)
{
   if( NULL == trueParentNode->firstSiblingNode ) {
      /* In the node being added, set its parents. */
      siblingToAdd->fakeParentNode = fakeParentNode;
      siblingToAdd->trueParentNode = trueParentNode;

      /* Set the empty firstSiblingNode pointer to new node being added. */
      trueParentNode->firstSiblingNode = siblingToAdd;
//      dbg_slow_printf("Added node '%s' as a sibling to node '%s'\n", siblingToAdd->text, trueParentNode->text);
   } else {
//      dbg_slow_printf("Sibling '%s' already exists here.  Trying next one\n", trueParentNode->text);
      KTREE_addNextSibling(siblingToAdd, trueParentNode->firstSiblingNode, fakeParentNode);
   }
}

/******************************************************************************/
void KTREE_printTree( treeNode_t *node, uint8_t level )
{
   if ( NULL == node ) {
//      dbg_slow_printf("Node at level %d is null, not printing\n", level);
      return;
   } else {
      KTREE_printNode( node, level );
   }

   if ( NULL != node->firstChildNode ) {
//      dbg_slow_printf("Child exists, descending one level\n");
      KTREE_printTree( node->firstChildNode, level+1 );
   }

   if ( NULL != node->firstSiblingNode ) {
//      dbg_slow_printf("Sibling exits, moving right\n");
      KTREE_printTree( node->firstSiblingNode, level );
   }

}

/******************************************************************************/
void KTREE_printNode( treeNode_t *node, uint8_t level )
{
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("NodeText: %s\n", node->text);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("True Parent   pointer: 0x%08x\n", (uint32_t)node->trueParentNode);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("Fake Parent   pointer: 0x%08x\n", (uint32_t)node->fakeParentNode);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("First Sibling pointer: 0x%08x\n", (uint32_t)node->firstSiblingNode);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("First Child   pointer: 0x%08x\n", (uint32_t)node->firstChildNode);
}

/******************************************************************************/
void KTREE_fakeMenuTest( void )
{
   /* First Node */
   KTREE_nodeCtor( &Menu1 );
   Menu1.text = Menu1Txt;

      /* Add a child node */
      KTREE_nodeCtor( &MenuItem1 );
      MenuItem1.text = MenuItem1_1Txt;
      KTREE_addChild( &MenuItem1, &Menu1, &Menu1 );

      /* Add siblings to that child node */
      KTREE_nodeCtor( &MenuItem2 );
      MenuItem2.text = MenuItem1_2Txt;
      KTREE_addChild( &MenuItem2, &Menu1, &Menu1 );

      KTREE_nodeCtor( &MenuItem3 );
      MenuItem3.text = MenuItem1_3Txt;
      KTREE_addChild( &MenuItem3, &Menu1, &Menu1 );

      KTREE_nodeCtor( &MenuItem4 );
      MenuItem4.text = MenuItem1_4Txt;
      KTREE_addChild( &MenuItem4, &Menu1, &Menu1 );

   /* Add another node at top level - it gets added as a sibling to the first node */
   KTREE_nodeCtor( &Menu2 );
   Menu2.text = Menu2Txt;
   KTREE_addNextSibling( &Menu2, &Menu1, NULL );

      /* Add menu items to it as children  */
      KTREE_nodeCtor( &MenuItem2_1 );
      MenuItem2_1.text = MenuItem1_1Txt;
      KTREE_addChild( &MenuItem2_1, &Menu2, &Menu2 );

      /* Add siblings to that child node */
      KTREE_nodeCtor( &MenuItem2_2 );
      MenuItem2_2.text = MenuItem2_2Txt;
      KTREE_addChild( &MenuItem2_2, &Menu2, &Menu2 );

      KTREE_nodeCtor( &MenuItem2_3 );
      MenuItem2_3.text = MenuItem2_3Txt;
      KTREE_addChild( &MenuItem2_3, &Menu2, &Menu2 );

      KTREE_nodeCtor( &MenuItem2_4 );
      MenuItem2_4.text = MenuItem2_4Txt;
      KTREE_addChild( &MenuItem2_4, &Menu2, &Menu2 );

   /* Add another node at top level - it gets added as a sibling to the first node */
   KTREE_nodeCtor( &Menu3 );
   Menu3.text = Menu3Txt;
   KTREE_addNextSibling( &Menu3, &Menu1, NULL );

      /* Add submenu nodes - it gets added as a sibling to the first node */
      KTREE_nodeCtor( &Menu3_1 );
      Menu3_1.text = Menu3_1Txt;
      KTREE_addChild( &Menu3_1, &Menu3, &Menu3 );

         /* Add menu items to it as children  */
         KTREE_nodeCtor( &MenuItem3_1_1 );
         MenuItem3_1_1.text = MenuItem3_1_1Txt;
         KTREE_addChild( &MenuItem3_1_1, &Menu3_1, &Menu3_1 );

         KTREE_nodeCtor( &MenuItem3_1_2 );
         MenuItem3_1_2.text = MenuItem3_1_2Txt;
         KTREE_addChild( &MenuItem3_1_2, &Menu3_1, &Menu3_1 );

         KTREE_nodeCtor( &MenuItem3_1_3 );
         MenuItem3_1_3.text = MenuItem3_1_3Txt;
         KTREE_addChild( &MenuItem3_1_3, &Menu3_1, &Menu3_1 );

         KTREE_nodeCtor( &MenuItem3_1_4 );
         MenuItem3_1_4.text = MenuItem3_1_4Txt;
         KTREE_addChild( &MenuItem3_1_4, &Menu3_1, &Menu3_1 );

      KTREE_nodeCtor( &Menu3_2 );
      Menu3_2.text = Menu3_2Txt;
      KTREE_addChild( &Menu3_2, &Menu3, &Menu3 );

         /* Add menu items to it as children  */
         KTREE_nodeCtor( &MenuItem3_2_1 );
         MenuItem3_2_1.text = MenuItem3_2_1Txt;
         KTREE_addChild( &MenuItem3_2_1, &Menu3_2, &Menu3_2 );

         KTREE_nodeCtor( &MenuItem3_2_2 );
         MenuItem3_2_2.text = MenuItem3_2_2Txt;
         KTREE_addChild( &MenuItem3_2_2, &Menu3_2, &Menu3_2 );

         KTREE_nodeCtor( &MenuItem3_2_3 );
         MenuItem3_2_3.text = MenuItem3_2_3Txt;
         KTREE_addChild( &MenuItem3_2_3, &Menu3_2, &Menu3_2 );

         KTREE_nodeCtor( &MenuItem3_2_4 );
         MenuItem3_2_4.text = MenuItem3_2_4Txt;
         KTREE_addChild( &MenuItem3_2_4, &Menu3_2, &Menu3_2 );

      KTREE_nodeCtor( &Menu3_3 );
      Menu3_3.text = Menu3_3Txt;
      KTREE_addChild( &Menu3_3, &Menu3, &Menu3 );

         KTREE_nodeCtor( &Menu3_3_1 );
         Menu3_3_1.text = Menu3_3_1Txt;
         KTREE_addChild( &Menu3_3_1, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_1_1 );
            MenuItem3_3_1_1.text = MenuItem3_3_1_1Txt;
            KTREE_addChild( &MenuItem3_3_1_1, &Menu3_3_1, &Menu3_3_1 );

            KTREE_nodeCtor( &MenuItem3_3_1_2 );
            MenuItem3_3_1_2.text = MenuItem3_3_1_2Txt;
            KTREE_addChild( &MenuItem3_3_1_2, &Menu3_3_1, &Menu3_3_1 );

            KTREE_nodeCtor( &MenuItem3_3_1_3 );
            MenuItem3_3_1_3.text = MenuItem3_3_1_3Txt;
            KTREE_addChild( &MenuItem3_3_1_3, &Menu3_3_1, &Menu3_3_1 );

         KTREE_nodeCtor( &Menu3_3_2 );
         Menu3_3_2.text = Menu3_3_2Txt;
         KTREE_addChild( &Menu3_3_2, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_2_1 );
            MenuItem3_3_2_1.text = MenuItem3_3_2_1Txt;
            KTREE_addChild( &MenuItem3_3_2_1, &Menu3_3_2, &Menu3_3_2 );

            KTREE_nodeCtor( &MenuItem3_3_2_2 );
            MenuItem3_3_2_2.text = MenuItem3_3_2_2Txt;
            KTREE_addChild( &MenuItem3_3_2_2, &Menu3_3_2, &Menu3_3_2 );

            KTREE_nodeCtor( &MenuItem3_3_2_3 );
            MenuItem3_3_2_3.text = MenuItem3_3_2_3Txt;
            KTREE_addChild( &MenuItem3_3_2_3, &Menu3_3_2, &Menu3_3_2 );

         KTREE_nodeCtor( &Menu3_3_3 );
         Menu3_3_3.text = Menu3_3_3Txt;
         KTREE_addChild( &Menu3_3_3, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_3_1 );
            MenuItem3_3_3_1.text = MenuItem3_3_3_1Txt;
            KTREE_addChild( &MenuItem3_3_3_1, &Menu3_3_3, &Menu3_3_3 );

            KTREE_nodeCtor( &MenuItem3_3_3_2 );
            MenuItem3_3_3_2.text = MenuItem3_3_3_2Txt;
            KTREE_addChild( &MenuItem3_3_3_2, &Menu3_3_3, &Menu3_3_3 );

            KTREE_nodeCtor( &MenuItem3_3_3_3 );
            MenuItem3_3_3_3.text = MenuItem3_3_3_3Txt;
            KTREE_addChild( &MenuItem3_3_3_3, &Menu3_3_3, &Menu3_3_3 );

         KTREE_nodeCtor( &Menu3_3_4 );
         Menu3_3_4.text = Menu3_3_4Txt;
         KTREE_addChild( &Menu3_3_4, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_4_1 );
            MenuItem3_3_4_1.text = MenuItem3_3_4_1Txt;
            KTREE_addChild( &MenuItem3_3_4_1, &Menu3_3_4, &Menu3_3_4 );

            KTREE_nodeCtor( &MenuItem3_3_4_2 );
            MenuItem3_3_4_2.text = MenuItem3_3_4_2Txt;
            KTREE_addChild( &MenuItem3_3_4_2, &Menu3_3_4, &Menu3_3_4 );

            KTREE_nodeCtor( &MenuItem3_3_4_3 );
            MenuItem3_3_4_3.text = MenuItem3_3_4_3Txt;
            KTREE_addChild( &MenuItem3_3_4_3, &Menu3_3_4, &Menu3_3_4 );

   KTREE_printTree( &Menu1, 0 );
}

Code for ktree.h:

ktree.h的代码:

#ifndef KTREE_H_
#define KTREE_H_

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"                                 /* For STM32F4 support */
#include "Shared.h"

/* Exported defines ----------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/

typedef void (*pMenuFunction) (
      const char* dataBuf,
      uint16_t dataLen,
      CBMsgRoute dst
);

typedef struct treeNode {
   struct treeNode *fakeParentNode;
   struct treeNode *trueParentNode;
   struct treeNode *firstChildNode;
   struct treeNode *firstSiblingNode;
   char *text;
   char *selector;
   pMenuFunction actionToTake;
} treeNode_t;


/* Exported macros -----------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/


/**
 * @brief   Initialize the menu memory space with the contents of the menu
 *
 * This function initializes the menu application.
 *
 * @param [in]  iBus: I2C_Bus_t identifier for I2C bus to initialize
 *    @arg
 * @return: None
 */
void KTREE_nodeCtor( treeNode_t *node );

/**
 * @brief   Counts the depth (via fakeParentNode pointer) of the passed in
 * pointer to a node.
 *
 * Recursive function that counts it's own depth by traversing the tree up via
 * the fakeParentNode pointer and counting.
 *
 * @param [in]  *node: treeNode_t pointer to the node
 * @parem [in] currDepth: uint8_t value of the current depth.  Unless you want
 * to for some reason offset where you start counting from, pass in 0 here.
 * @return: uint8_t depth of the current node.
 */
uint8_t KTREE_findDepth( treeNode_t *node, uint8_t currDepth );

/**
 * @brief   Adds a child node to a passed in parent node.
 *
 * This function adds a child node to a passed in parent node after doing some
 * initial checking of both nodes.
 *
 * @param [in] childNode: treeNode_t* pointer to the node to be added as a child
 * @parem [in] trueParentNone: treeNode_t* pointer to the node that the child
 * node will be directly attached to because it may appear as a sibling node or
 * an actual child node.
 * @parem [in] fakeParentNone: treeNode_t* pointer to the node that indicates
 * the "regular" tree depth of this child node.
 * @return: None
 */
void KTREE_addChild(
      treeNode_t *childToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
);

/**
 * @brief   Adds a sibling node to a passed in parent node.
 *
 * This function adds a sibling node to a passed in parent node by checking if
 * the child node of the trueParentNode is used up and if it is, it recursively
 * calls itself with the used node as a trueParentNode parameter until it finds
 * the last sibling.  It then adds the sibling node there and sets the
 * fakeParentNode.
 *
 * @param [in] childNode: treeNode_t* pointer to the node to be added as a child
 * @parem [in] trueParentNone: treeNode_t* pointer to the node that the child
 * node will be directly attached to because it may appear as a sibling node or
 * an actual child node.
 * @parem [in] fakeParentNone: treeNode_t* pointer to the node that indicates
 * the "regular" tree depth of this child node.
 * @return: None
 */
void KTREE_addNextSibling(
      treeNode_t *siblingToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
);

void KTREE_printTree( treeNode_t *node, uint8_t level );

void KTREE_printNode( treeNode_t *node, uint8_t level );

void KTREE_fakeMenuTest( void );

I don't want to post the rest of the code here (it's already a lot) but take a look at the rest of that directory in my link to see how it was used. menu.c/h uses the ktree to set up an actual menu and uses it.

我不想在这里发布其余的代码(它已经很多了)但是看看我链接中该目录的其余部分,看看它是如何使用的。 menu.c / h使用ktree设置实际菜单并使用它。

#1


2  

Static arrays probably aren't the best data structure to use if you want any flexibility.

如果您想要任何灵活性,静态数组可能不是最好的数据结构。

I suggest using a dynamically sized container like std::vector.

我建议使用像std :: vector这样的动态大小的容器。

You'll often see menu structures like this using a "tree" type hierarchy, where each level of the menu has a name, and its children (sub menu items). Here's how you might go about using these concepts.

您经常会看到使用“树”类型层次结构的菜单结构,其中菜单的每个级别都有一个名称及其子项(子菜单项)。以下是您如何使用这些概念。

struct MenuItem
{
    // name of the menu item
    std::string name;

    // sub menu items
    std::vector<MenuItem> children;
};

Traversing the menu is easiest using recursion, here's a simple example.

使用递归遍历菜单是最简单的,这是一个简单的例子。

void printMenu(const std::vector<MenuItem> &menu, int level) 
{
    // string containing tabs depending on the level of the menu
    std::string prefix( level, '\t' );

    // we're visiting the next menu level
    ++level;

    // iterate over this level of the menu
    for (const auto &item : menu) 
    {
        // display the item name
        std::cout << prefix << item.name << std::endl;

        // visit this level's children
        printMenu( item.children, level );
    }
}

Here's how you would initialize the sample menu structure provided.

以下是初始化提供的示例菜单结构的方法。

std::vector<MenuItem> menu 
{
    {
        { 
            { "Menu Item 1" },
            { 
                {
                    { "Item 1.1" },
                    { 
                        {
                            { "Item 1.1.1" },
                            { {} }
                        },
                        {
                            { "Item 1.1.2" },
                            { {} }
                        }
                    }
                },
                {
                    { "Item 1.2" },
                    {
                        {
                            { "Item 1.2.1" },
                            { {} }
                        },
                        {
                            { "Item 1.2.2" },
                            { {} }
                        }
                    }
                },
            }
        },
        {
            { "Menu Item 2" },
            {
                {
                    { "Item 2.1" },
                    {
                        {
                            { "Item 2.1.1" },
                            { {} }
                        },
                        {
                            { "Item 2.1.2" },
                            { {} }
                        }
                    }
                },
                {
                    { "Item 2.2" },
                    {
                        {
                            { "Item 2.2.1" },
                            { {} }
                        },
                        {
                            { "Item 2.2.2" },
                            { {} }
                        }
                    }
                },
            }
        }
    }
};

To access a specific menu item you can do the following.

要访问特定菜单项,您可以执行以下操作。

// Item 2.2.1
std::string item = menu[ 1 ].children[ 1 ].children[ 0 ].name;

Or even adding an item after the structure has been initialized.

甚至在结构初始化后添加项目。

menu[ 1 ].children[ 1 ].children.push_back({
    { "Testing" }, // name
    { {} }         // children
});

/*
    Menu Item 1
        Item 1.1
                Item 1.1.1
                Item 1.1.2
        Item 1.2
                Item 1.2.1
                Item 1.2.2
    Menu Item 2
        Item 2.1
                Item 2.1.1
                Item 2.1.2
        Item 2.2
                Item 2.2.1
                Item 2.2.2
                Testing
*/

You can find a complete example here.

你可以在这里找到一个完整的例子。

#2


1  

Arrays are usually a poor choice for nested menus.

对于嵌套菜单,数组通常是不好的选择。

In my experience menus are usually linked together rather than in an array:

根据我的经验,菜单通常是链接在一起而不是在数组中:

Menu1  
  Item1 --> Menu1.1
    |  
    v  
  Item2 --> Menu1.2
    |
    v
  Item3

I usually I have a menu contain items. Each item can point to another submenu. This allows for the case where an item does not have a submenu.

我通常有一个菜单包含项目。每个项目都可以指向另一个子菜单。这允许项目没有子菜单的情况。

#3


0  

I have a solution that I implemented a little while back in C. It's set up to go as deep as 8 levels but there's technically no limit to depth. It's for embedded so it uses static memory for everything but it could easily be modified to use dynamic allocation. You can find the sources on my github page but I'll go through and explain them a little bit here.

我有一个解决方案,我在C中实现了一段时间。它设置为深达8级,但技术上没有深度限制。它是嵌入式的,所以它使用静态内存,但可以很容易地修改它以使用动态分配。你可以在我的github页面上找到这些资源,但我会在这里详细解释一下。

The Menu is built around a k-ary tree (k-tree) which is implemented as a binary tree (b-tree). Unlike a regular k-tree where a node can have multiple children which would make sense for a menu system, since the system is memory and resource constrained, a binary tree makes more since. It allows for statically allocated nodes without using fixed sized arrays to store child nodes. This implementation is a little confusing at first but is actually used in several resource sensitive systems such as the Linux kernel.

菜单围绕k-ary树(k-tree)构建,其实现为二叉树(b-tree)。与常规k树不同,其中节点可以具有多个子菜单,这对于菜单系统是有意义的,因为系统是内存和资源约束的,所以二进制树生成更多。它允许静态分配的节点,而不使用固定大小的数组来存储子节点。这个实现起初有点令人困惑,但实际上在几个资源敏感的系统中使用,例如Linux内核。

A regular k-tree is represented as:

常规的k树表示为:

struct t {
   int n;
   int numKids;
   struct t **kids;
}

and the resulting tree struture looks like:

结果树的结构看起来像:

   TOP
   |
   [ list of kids ]
   |     |      |
   k1    k2     k3
   |
   |
   [list of kids]
   |     |     |
   k1.1  k1.2  k1.3

This representation works if you have things like malloc but on an embedded system where using malloc is anathema, if you want to add another child, use a static array of limited length in addition to having to record the number of kids in the array in the structure.

如果你有类似malloc的东西,但是在使用malloc是诅咒的嵌入式系统上,如果你想添加另一个子节点,使用有限长度的静态数组,除了必须记录数组中的数量结构体。

Instead, the following structure is used:

而是使用以下结构:

struct t {
   int n;
   struct t *firstChildNode;
   struct t *firstSiblingNode;
   struct t *trueParentNode;
   struct t *fakeParentNode;
};

and the tree representation looks like:

并且树表示如下:


  [TOP NODE ] <-
  -----------   \
  ^|  ^          \
  ||  |           \ fP
  ||  |tP, fP      \
  ||  |_______  fS  \__________     fS
  ||  [NODE 2]------>[NODE 2.1]---------->NULL
  ||  ------- <------ ---------
  ||       |      tP      |
  ||       |fC            |fC
  ||fC     V              V
  ||      NULL          NULL
  ||
  |tP, fP
  ||
  |V_____
  [NODE1]
  -------
     ^
     |tP, fp
     |_________   fS
     [NODE 1.1] -----> NULL
         |
         |fC
         |
         V
       NULL

Where:

  • fP = fakeParentNode - this pointer is used to figure out where the actual parent as it would appear in a classical k-ary tree.
  • fP = fakeParentNode - 此指针用于确定实际父级在经典k-ary树中的显示位置。

  • tP = trueParentNode - this pointer is used to show where this node is actually attached since it could be either a child node of another node or a sibling.
  • tP = trueParentNode - 此指针用于显示此节点实际连接的位置,因为它可能是另一个节点的子节点或兄弟节点。

  • fC = firstChildNode - pointer to the first child node.
  • fC = firstChildNode - 指向第一个子节点的指针。

  • fS = firstSiblingNode - pointer to the first sibling node.
  • fS = firstSiblingNode - 指向第一个兄弟节点的指针。

From any node, you can only access the first child or the first sibling, making this a classical b-tree. From that first child, you can access it's children and siblings. Likewise for the first sibling pointer. This way all the nodes are stored in a list which can be comprised of statically allocated structures. The true and fake Parent pointers are for easily tracing the ancestry of each child node. The fake parent pointer is used to map a classical k-tree structure over this b-tree.

从任何节点,您只能访问第一个孩子或第一个兄弟,使其成为经典的b树。从第一个孩子,你可以访问它的孩子和兄弟姐妹。同样对于第一个兄弟指针。这样,所有节点都存储在一个列表中,该列表可以由静态分配的结构组成。真假父指针用于轻松跟踪每个子节点的祖先。伪父指针用于映射该b树上的经典k树结构。

There are several advantages to this seemingly confusing approach:

这种看似令人困惑的方法有几个优点:

  • Each node is a fixed size and can be statically allocated.
  • 每个节点都是固定大小,可以静态分配。

  • No arrays are needed for representing child nodes.
  • 表示子节点不需要数组。

  • Many algorithms can be easily expressed since it's just a b-tree.
  • 许多算法都可以很容易地表达,因为它只是一个b树。

This implementation of the menu is limited in that the functionality to remove nodes is not present since this doesn't seem to be a necessary feature for a menu. To add a menu or menu item is very simple. See example in menu_top.c

菜单的这种实现受到限制,因为删除节点的功能不存在,因为这似乎不是菜单的必要特征。添加菜单或菜单项非常简单。请参阅menu_top.c中的示例

This design was inspired by https://blog.mozilla.org/nnethercote/2012/03/07/n-ary-trees-in-c/

此设计灵感来自https://blog.mozilla.org/nnethercote/2012/03/07/n-ary-trees-in-c/

Code for ktree.c:

ktree.c的代码:

/* Includes ------------------------------------------------------------------*/
#include "ktree.h"
#include "qp_port.h"                                        /* for QP support */
#include "project_includes.h"

/* Compile-time called macros ------------------------------------------------*/
Q_DEFINE_THIS_FILE                  /* For QSPY to know the name of this file */
DBG_DEFINE_THIS_MODULE( DBG_MODL_DBG );/* For debug system to ID this module */

/* Private typedefs ----------------------------------------------------------*/
/* Private defines -----------------------------------------------------------*/
/* Private macros ------------------------------------------------------------*/
/* Private variables and Local objects ---------------------------------------*/
static treeNode_t Menu1;
char *const Menu1Txt = "Menu 1";

   static treeNode_t MenuItem1;
   char *const MenuItem1_1Txt = "Menu Item 1.1";

   static treeNode_t MenuItem2;
   char *const MenuItem1_2Txt = "Menu Item 1.2";

   static treeNode_t MenuItem3;
   char *const MenuItem1_3Txt = "Menu Item 1.3";

   static treeNode_t MenuItem4;
   char *const MenuItem1_4Txt = "Menu Item 1.4";

static treeNode_t Menu2;
char *const Menu2Txt = "Menu 2";

   static treeNode_t MenuItem2_1;
   char *const MenuItem2_1Txt = "Menu Item 2.1";

   static treeNode_t MenuItem2_2;
   char *const MenuItem2_2Txt = "Menu Item 2.2";

   static treeNode_t MenuItem2_3;
   char *const MenuItem2_3Txt = "Menu Item 2.3";

   static treeNode_t MenuItem2_4;
   char *const MenuItem2_4Txt = "Menu Item 2.4";

static treeNode_t Menu3;
char *const Menu3Txt = "Menu 3";

   static treeNode_t Menu3_1;
   char *const Menu3_1Txt = "Menu 3_1";

      static treeNode_t MenuItem3_1_1;
      char *const MenuItem3_1_1Txt = "Menu Item 3.1.1";

      static treeNode_t MenuItem3_1_2;
      char *const MenuItem3_1_2Txt = "Menu Item 3.1.2";

      static treeNode_t MenuItem3_1_3;
      char *const MenuItem3_1_3Txt = "Menu Item 3.1.3";

      static treeNode_t MenuItem3_1_4;
      char *const MenuItem3_1_4Txt = "Menu Item 3.1.4";

   static treeNode_t Menu3_2;
   char *const Menu3_2Txt = "Menu 3_2";

      static treeNode_t MenuItem3_2_1;
      char *const MenuItem3_2_1Txt = "Menu Item 3.2.1";

      static treeNode_t MenuItem3_2_2;
      char *const MenuItem3_2_2Txt = "Menu Item 3.2.2";

      static treeNode_t MenuItem3_2_3;
      char *const MenuItem3_2_3Txt = "Menu Item 3.2.3";

      static treeNode_t MenuItem3_2_4;
      char *const MenuItem3_2_4Txt = "Menu Item 3.2.4";

   static treeNode_t Menu3_3;
   char *const Menu3_3Txt = "Menu 3_3";

      static treeNode_t Menu3_3_1;
      char *const Menu3_3_1Txt = "Menu 3_3_1";

         static treeNode_t MenuItem3_3_1_1;
         char *const MenuItem3_3_1_1Txt = "Menu Item 3.3.1.1";

         static treeNode_t MenuItem3_3_1_2;
         char *const MenuItem3_3_1_2Txt = "Menu Item 3.3.1.2";

         static treeNode_t MenuItem3_3_1_3;
         char *const MenuItem3_3_1_3Txt = "Menu Item 3.3.1.3";

      static treeNode_t Menu3_3_2;
      char *const Menu3_3_2Txt = "Menu 3_3_2";

         static treeNode_t MenuItem3_3_2_1;
         char *const MenuItem3_3_2_1Txt = "Menu Item 3.3.2.1";

         static treeNode_t MenuItem3_3_2_2;
         char *const MenuItem3_3_2_2Txt = "Menu Item 3.3.2.2";

         static treeNode_t MenuItem3_3_2_3;
         char *const MenuItem3_3_2_3Txt = "Menu Item 3.3.2.3";

      static treeNode_t Menu3_3_3;
      char *const Menu3_3_3Txt = "Menu 3_3_3";

         static treeNode_t MenuItem3_3_3_1;
         char *const MenuItem3_3_3_1Txt = "Menu Item 3.3.3.1";

         static treeNode_t MenuItem3_3_3_2;
         char *const MenuItem3_3_3_2Txt = "Menu Item 3.3.3.2";

         static treeNode_t MenuItem3_3_3_3;
         char *const MenuItem3_3_3_3Txt = "Menu Item 3.3.3.3";

      static treeNode_t Menu3_3_4;
      char *const Menu3_3_4Txt = "Menu 3_3_4";

         static treeNode_t MenuItem3_3_4_1;
         char *const MenuItem3_3_4_1Txt = "Menu Item 3.3.4.1";

         static treeNode_t MenuItem3_3_4_2;
         char *const MenuItem3_3_4_2Txt = "Menu Item 3.3.4.2";

         static treeNode_t MenuItem3_3_4_3;
         char *const MenuItem3_3_4_3Txt = "Menu Item 3.3.4.3";

/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/******************************************************************************/
void KTREE_nodeCtor( treeNode_t *node )
{
   /* Initialize the root node */
   node->firstChildNode = NULL;
   node->firstSiblingNode = NULL;
   node->fakeParentNode = NULL;
   node->trueParentNode = NULL;
}

/******************************************************************************/
uint8_t KTREE_findDepth( treeNode_t *node, uint8_t currDepth )
{
   /* Make sure the node is not null.  If this happens, it's a bug. */
   if ( NULL == node ) {
      ERR_printf("!!!Node is null\n");
      return( 0 );
   }

   if ( NULL == node->fakeParentNode ) {
      /* If no fake parents exist, this is the top.  Return the calculated
       * depth */
      return( currDepth );
   } else {
      /* Parent exists; recurse after incrementing the depth */
      return (KTREE_findDepth( node->fakeParentNode, ++currDepth ));
   }
}

/******************************************************************************/
void KTREE_addChild(
      treeNode_t *childToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
)
{
   /* Find the parent node */
   if ( NULL == trueParentNode ) {
      childToAdd->trueParentNode = NULL;
      childToAdd->fakeParentNode = NULL;
      return;
   } else if ( NULL == trueParentNode->firstChildNode ) {

      /* If the node where we want to add a child currently has no children,
       * add the child node here to the childNode field. */
      trueParentNode->firstChildNode = childToAdd;
//      dbg_slow_printf("whereToAddChild=0x%08x\n", (uint32_t )whereToAddChild);
   } else {
      /* Otherwise, add a sibling to the child of this node */
      KTREE_addNextSibling( childToAdd, trueParentNode->firstChildNode, fakeParentNode);
//      dbg_slow_printf("Adding a sibling to the child of node '%s'\n", trueParentNode->text);
   }
}

/******************************************************************************/
void KTREE_addNextSibling(
      treeNode_t *siblingToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
)
{
   if( NULL == trueParentNode->firstSiblingNode ) {
      /* In the node being added, set its parents. */
      siblingToAdd->fakeParentNode = fakeParentNode;
      siblingToAdd->trueParentNode = trueParentNode;

      /* Set the empty firstSiblingNode pointer to new node being added. */
      trueParentNode->firstSiblingNode = siblingToAdd;
//      dbg_slow_printf("Added node '%s' as a sibling to node '%s'\n", siblingToAdd->text, trueParentNode->text);
   } else {
//      dbg_slow_printf("Sibling '%s' already exists here.  Trying next one\n", trueParentNode->text);
      KTREE_addNextSibling(siblingToAdd, trueParentNode->firstSiblingNode, fakeParentNode);
   }
}

/******************************************************************************/
void KTREE_printTree( treeNode_t *node, uint8_t level )
{
   if ( NULL == node ) {
//      dbg_slow_printf("Node at level %d is null, not printing\n", level);
      return;
   } else {
      KTREE_printNode( node, level );
   }

   if ( NULL != node->firstChildNode ) {
//      dbg_slow_printf("Child exists, descending one level\n");
      KTREE_printTree( node->firstChildNode, level+1 );
   }

   if ( NULL != node->firstSiblingNode ) {
//      dbg_slow_printf("Sibling exits, moving right\n");
      KTREE_printTree( node->firstSiblingNode, level );
   }

}

/******************************************************************************/
void KTREE_printNode( treeNode_t *node, uint8_t level )
{
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("NodeText: %s\n", node->text);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("True Parent   pointer: 0x%08x\n", (uint32_t)node->trueParentNode);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("Fake Parent   pointer: 0x%08x\n", (uint32_t)node->fakeParentNode);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("First Sibling pointer: 0x%08x\n", (uint32_t)node->firstSiblingNode);
   for ( uint8_t i = 0; i < level; i++ ) {
      printf("*--");
   }
   printf("First Child   pointer: 0x%08x\n", (uint32_t)node->firstChildNode);
}

/******************************************************************************/
void KTREE_fakeMenuTest( void )
{
   /* First Node */
   KTREE_nodeCtor( &Menu1 );
   Menu1.text = Menu1Txt;

      /* Add a child node */
      KTREE_nodeCtor( &MenuItem1 );
      MenuItem1.text = MenuItem1_1Txt;
      KTREE_addChild( &MenuItem1, &Menu1, &Menu1 );

      /* Add siblings to that child node */
      KTREE_nodeCtor( &MenuItem2 );
      MenuItem2.text = MenuItem1_2Txt;
      KTREE_addChild( &MenuItem2, &Menu1, &Menu1 );

      KTREE_nodeCtor( &MenuItem3 );
      MenuItem3.text = MenuItem1_3Txt;
      KTREE_addChild( &MenuItem3, &Menu1, &Menu1 );

      KTREE_nodeCtor( &MenuItem4 );
      MenuItem4.text = MenuItem1_4Txt;
      KTREE_addChild( &MenuItem4, &Menu1, &Menu1 );

   /* Add another node at top level - it gets added as a sibling to the first node */
   KTREE_nodeCtor( &Menu2 );
   Menu2.text = Menu2Txt;
   KTREE_addNextSibling( &Menu2, &Menu1, NULL );

      /* Add menu items to it as children  */
      KTREE_nodeCtor( &MenuItem2_1 );
      MenuItem2_1.text = MenuItem1_1Txt;
      KTREE_addChild( &MenuItem2_1, &Menu2, &Menu2 );

      /* Add siblings to that child node */
      KTREE_nodeCtor( &MenuItem2_2 );
      MenuItem2_2.text = MenuItem2_2Txt;
      KTREE_addChild( &MenuItem2_2, &Menu2, &Menu2 );

      KTREE_nodeCtor( &MenuItem2_3 );
      MenuItem2_3.text = MenuItem2_3Txt;
      KTREE_addChild( &MenuItem2_3, &Menu2, &Menu2 );

      KTREE_nodeCtor( &MenuItem2_4 );
      MenuItem2_4.text = MenuItem2_4Txt;
      KTREE_addChild( &MenuItem2_4, &Menu2, &Menu2 );

   /* Add another node at top level - it gets added as a sibling to the first node */
   KTREE_nodeCtor( &Menu3 );
   Menu3.text = Menu3Txt;
   KTREE_addNextSibling( &Menu3, &Menu1, NULL );

      /* Add submenu nodes - it gets added as a sibling to the first node */
      KTREE_nodeCtor( &Menu3_1 );
      Menu3_1.text = Menu3_1Txt;
      KTREE_addChild( &Menu3_1, &Menu3, &Menu3 );

         /* Add menu items to it as children  */
         KTREE_nodeCtor( &MenuItem3_1_1 );
         MenuItem3_1_1.text = MenuItem3_1_1Txt;
         KTREE_addChild( &MenuItem3_1_1, &Menu3_1, &Menu3_1 );

         KTREE_nodeCtor( &MenuItem3_1_2 );
         MenuItem3_1_2.text = MenuItem3_1_2Txt;
         KTREE_addChild( &MenuItem3_1_2, &Menu3_1, &Menu3_1 );

         KTREE_nodeCtor( &MenuItem3_1_3 );
         MenuItem3_1_3.text = MenuItem3_1_3Txt;
         KTREE_addChild( &MenuItem3_1_3, &Menu3_1, &Menu3_1 );

         KTREE_nodeCtor( &MenuItem3_1_4 );
         MenuItem3_1_4.text = MenuItem3_1_4Txt;
         KTREE_addChild( &MenuItem3_1_4, &Menu3_1, &Menu3_1 );

      KTREE_nodeCtor( &Menu3_2 );
      Menu3_2.text = Menu3_2Txt;
      KTREE_addChild( &Menu3_2, &Menu3, &Menu3 );

         /* Add menu items to it as children  */
         KTREE_nodeCtor( &MenuItem3_2_1 );
         MenuItem3_2_1.text = MenuItem3_2_1Txt;
         KTREE_addChild( &MenuItem3_2_1, &Menu3_2, &Menu3_2 );

         KTREE_nodeCtor( &MenuItem3_2_2 );
         MenuItem3_2_2.text = MenuItem3_2_2Txt;
         KTREE_addChild( &MenuItem3_2_2, &Menu3_2, &Menu3_2 );

         KTREE_nodeCtor( &MenuItem3_2_3 );
         MenuItem3_2_3.text = MenuItem3_2_3Txt;
         KTREE_addChild( &MenuItem3_2_3, &Menu3_2, &Menu3_2 );

         KTREE_nodeCtor( &MenuItem3_2_4 );
         MenuItem3_2_4.text = MenuItem3_2_4Txt;
         KTREE_addChild( &MenuItem3_2_4, &Menu3_2, &Menu3_2 );

      KTREE_nodeCtor( &Menu3_3 );
      Menu3_3.text = Menu3_3Txt;
      KTREE_addChild( &Menu3_3, &Menu3, &Menu3 );

         KTREE_nodeCtor( &Menu3_3_1 );
         Menu3_3_1.text = Menu3_3_1Txt;
         KTREE_addChild( &Menu3_3_1, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_1_1 );
            MenuItem3_3_1_1.text = MenuItem3_3_1_1Txt;
            KTREE_addChild( &MenuItem3_3_1_1, &Menu3_3_1, &Menu3_3_1 );

            KTREE_nodeCtor( &MenuItem3_3_1_2 );
            MenuItem3_3_1_2.text = MenuItem3_3_1_2Txt;
            KTREE_addChild( &MenuItem3_3_1_2, &Menu3_3_1, &Menu3_3_1 );

            KTREE_nodeCtor( &MenuItem3_3_1_3 );
            MenuItem3_3_1_3.text = MenuItem3_3_1_3Txt;
            KTREE_addChild( &MenuItem3_3_1_3, &Menu3_3_1, &Menu3_3_1 );

         KTREE_nodeCtor( &Menu3_3_2 );
         Menu3_3_2.text = Menu3_3_2Txt;
         KTREE_addChild( &Menu3_3_2, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_2_1 );
            MenuItem3_3_2_1.text = MenuItem3_3_2_1Txt;
            KTREE_addChild( &MenuItem3_3_2_1, &Menu3_3_2, &Menu3_3_2 );

            KTREE_nodeCtor( &MenuItem3_3_2_2 );
            MenuItem3_3_2_2.text = MenuItem3_3_2_2Txt;
            KTREE_addChild( &MenuItem3_3_2_2, &Menu3_3_2, &Menu3_3_2 );

            KTREE_nodeCtor( &MenuItem3_3_2_3 );
            MenuItem3_3_2_3.text = MenuItem3_3_2_3Txt;
            KTREE_addChild( &MenuItem3_3_2_3, &Menu3_3_2, &Menu3_3_2 );

         KTREE_nodeCtor( &Menu3_3_3 );
         Menu3_3_3.text = Menu3_3_3Txt;
         KTREE_addChild( &Menu3_3_3, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_3_1 );
            MenuItem3_3_3_1.text = MenuItem3_3_3_1Txt;
            KTREE_addChild( &MenuItem3_3_3_1, &Menu3_3_3, &Menu3_3_3 );

            KTREE_nodeCtor( &MenuItem3_3_3_2 );
            MenuItem3_3_3_2.text = MenuItem3_3_3_2Txt;
            KTREE_addChild( &MenuItem3_3_3_2, &Menu3_3_3, &Menu3_3_3 );

            KTREE_nodeCtor( &MenuItem3_3_3_3 );
            MenuItem3_3_3_3.text = MenuItem3_3_3_3Txt;
            KTREE_addChild( &MenuItem3_3_3_3, &Menu3_3_3, &Menu3_3_3 );

         KTREE_nodeCtor( &Menu3_3_4 );
         Menu3_3_4.text = Menu3_3_4Txt;
         KTREE_addChild( &Menu3_3_4, &Menu3_3, &Menu3_3 );

            /* Add menu items to it as children  */
            KTREE_nodeCtor( &MenuItem3_3_4_1 );
            MenuItem3_3_4_1.text = MenuItem3_3_4_1Txt;
            KTREE_addChild( &MenuItem3_3_4_1, &Menu3_3_4, &Menu3_3_4 );

            KTREE_nodeCtor( &MenuItem3_3_4_2 );
            MenuItem3_3_4_2.text = MenuItem3_3_4_2Txt;
            KTREE_addChild( &MenuItem3_3_4_2, &Menu3_3_4, &Menu3_3_4 );

            KTREE_nodeCtor( &MenuItem3_3_4_3 );
            MenuItem3_3_4_3.text = MenuItem3_3_4_3Txt;
            KTREE_addChild( &MenuItem3_3_4_3, &Menu3_3_4, &Menu3_3_4 );

   KTREE_printTree( &Menu1, 0 );
}

Code for ktree.h:

ktree.h的代码:

#ifndef KTREE_H_
#define KTREE_H_

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"                                 /* For STM32F4 support */
#include "Shared.h"

/* Exported defines ----------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/

typedef void (*pMenuFunction) (
      const char* dataBuf,
      uint16_t dataLen,
      CBMsgRoute dst
);

typedef struct treeNode {
   struct treeNode *fakeParentNode;
   struct treeNode *trueParentNode;
   struct treeNode *firstChildNode;
   struct treeNode *firstSiblingNode;
   char *text;
   char *selector;
   pMenuFunction actionToTake;
} treeNode_t;


/* Exported macros -----------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/


/**
 * @brief   Initialize the menu memory space with the contents of the menu
 *
 * This function initializes the menu application.
 *
 * @param [in]  iBus: I2C_Bus_t identifier for I2C bus to initialize
 *    @arg
 * @return: None
 */
void KTREE_nodeCtor( treeNode_t *node );

/**
 * @brief   Counts the depth (via fakeParentNode pointer) of the passed in
 * pointer to a node.
 *
 * Recursive function that counts it's own depth by traversing the tree up via
 * the fakeParentNode pointer and counting.
 *
 * @param [in]  *node: treeNode_t pointer to the node
 * @parem [in] currDepth: uint8_t value of the current depth.  Unless you want
 * to for some reason offset where you start counting from, pass in 0 here.
 * @return: uint8_t depth of the current node.
 */
uint8_t KTREE_findDepth( treeNode_t *node, uint8_t currDepth );

/**
 * @brief   Adds a child node to a passed in parent node.
 *
 * This function adds a child node to a passed in parent node after doing some
 * initial checking of both nodes.
 *
 * @param [in] childNode: treeNode_t* pointer to the node to be added as a child
 * @parem [in] trueParentNone: treeNode_t* pointer to the node that the child
 * node will be directly attached to because it may appear as a sibling node or
 * an actual child node.
 * @parem [in] fakeParentNone: treeNode_t* pointer to the node that indicates
 * the "regular" tree depth of this child node.
 * @return: None
 */
void KTREE_addChild(
      treeNode_t *childToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
);

/**
 * @brief   Adds a sibling node to a passed in parent node.
 *
 * This function adds a sibling node to a passed in parent node by checking if
 * the child node of the trueParentNode is used up and if it is, it recursively
 * calls itself with the used node as a trueParentNode parameter until it finds
 * the last sibling.  It then adds the sibling node there and sets the
 * fakeParentNode.
 *
 * @param [in] childNode: treeNode_t* pointer to the node to be added as a child
 * @parem [in] trueParentNone: treeNode_t* pointer to the node that the child
 * node will be directly attached to because it may appear as a sibling node or
 * an actual child node.
 * @parem [in] fakeParentNone: treeNode_t* pointer to the node that indicates
 * the "regular" tree depth of this child node.
 * @return: None
 */
void KTREE_addNextSibling(
      treeNode_t *siblingToAdd,
      treeNode_t *trueParentNode,
      treeNode_t *fakeParentNode
);

void KTREE_printTree( treeNode_t *node, uint8_t level );

void KTREE_printNode( treeNode_t *node, uint8_t level );

void KTREE_fakeMenuTest( void );

I don't want to post the rest of the code here (it's already a lot) but take a look at the rest of that directory in my link to see how it was used. menu.c/h uses the ktree to set up an actual menu and uses it.

我不想在这里发布其余的代码(它已经很多了)但是看看我链接中该目录的其余部分,看看它是如何使用的。 menu.c / h使用ktree设置实际菜单并使用它。