Single-chip multi-level menu


foreword

The previous project required the use of multi-level menus. Since I am using a 128*64 OLED, UI software such as LVGL cannot be used. Moreover, due to the particularity of the project, the UI on the market does not meet the requirements. I need to use some professional drawing software to build my own UI and style, so I need a multi-level menu function.

The multi-level menu scheme commonly used on the Internet is based on an index or tree structure, and most of them are indexes . Based on my own experience and analysis, the scheme of building an index online is not conducive to expansion , and the readability is also poor. It is not necessary, and it is easy to cause the disadvantage of memory out-of-bounds access . Based on these shortcomings, I developed a simple multi-level menu function by myself. This multi-level menu is actually a tree mode, but I used a clever method to directly convert the tree-structured data into a hash table, using a hash table to build a multi-level menu .


1. Understand the basics of multi-level menus

In fact, this multi-level menu is also based on the index, but it has good readability and expandability. The search performance is almost optimal, but it takes up a little memory space.

1.1 Hash table

This multi-level menu uses a point that few microcontroller developers will learn, that is, the data structure. One of the simpler data structures is used here to build a multi-level menu. This data structure is a hash table or a hash table.

Here we use a hash table to illustrate. A hash table can also be said to be a dictionary in Python, that is, there is a keyword in the hash table. The program can use the keyword to find out where the data is stored.

We can roughly understand the relationship between keywords (Key) and hash tables with a graph. As shown in the figure below, the program can generate a Key through the hash function , where the Key is equal to the stored address , and we get the Key, that is, the stored address, then we can directly access the address to obtain the data.

Note that the Key here is generated by itself through a hash function , and we can use this Key to find the stored data.

insert image description here

1.11 Hash functions

A hash function is a function that generates a key, which is unique , so that we can find what we want through the address. There are many hash functions, here is a simple example, look at the code:

/*最简单的哈希函数*/
#define MAXARRY 50
int HASH_TABLE[MAXARRY];
int create_hashkey(int num)
{
    
    
	return (num*2+3%MAXARRY);
}
/*找到相关数据*/
int Find_data(int num)
{
    
    
	int key=create_hashkey(num);
	return HASH_TABLE[key];
}

It can be seen from here that the hash table actually establishes a connection between an address and a keyword , and the relevant address can be found through the keyword , so as to find the relevant content.

1.12 Hash Collisions

Hash conflict means that the keywords generated by the hash function are the same, that is, the mapping address is the same, so we cannot find the required data through the address, and it is impossible for us to store different data in the same area.

This is called a hash conflict, and there are ways to solve hash conflicts, such as the simple open address method, that is, if there is a conflict, the hash value is recalculated to find the next empty address, and then stored.

1.13 suitable for the scene

Hash tables are best suited for solving problems that find records that are equal to a given value . The key point here is that the given value is very special and not repeated, that is, the keyword for calculating the hash value must be special . Each one is quite special and unique, which is great for searching problems. As an extrapolation, the time complexity of the search is O(1).

2. Hash table and multi-level menu

Having said so much, what is the connection between the hash table and the multi-level menu? We have carefully observed that multi-level menus require high-frequency queries, and the absolute position of each option in the multi-level menu is unique. Looking back at the application scenario of the hash table,
it is suitable for finding data that is equal to a given value. Each of our options is a unique absolute position, which is very suitable for the usage scenario of the hash table, perfect.

2.1 Option mapping of multi-level menu

If we can identify the specificity of each option, then can we map it and query the menu we set. Then its icon should look like this.

We can assume that sub-option 1.1 is the next level of option 1. Based on this assumption, when we access the hash table, can we directly input a string similar to "1.1" to know its actual content.

insert image description here

2.2 Reference code

After the simple analysis above, we can build a multi-level menu by using a hash table, but we don't care about the pointing objects of the upper and lower levels of the menu. We only need to enter the position of the relevant option and pass the hash all the time . The function gets what it points to. If we need to change the situation of the option, then we can know by changing the position of the input.

Here is a basic function for adding , deleting, modifying and checking hash tables , and an example of building and using a multi-level menu based on these four basic functions.

2.3 Definition of actual location

I will use a string below to obtain the unique key of the hash table. The string format of this is a personal customization. If you want to use this and understand this code, you can first understand the following figure.

insert image description here

It can be seen from the figure that this multi-level directory is actually a tree structure. If you want to traverse the tree, the general idea is to use pointers to point to its child nodes and parent nodes. You can know that the tree stores each of its nodes in the form of pointers. The relative position of a member. And I used a more ingenious method to express the absolute position of each position directly in the form of a string, which is actually a bit similar to the form of file storage, except that the actual position is stored in the hash table.

2.4 Hash table reference code

The implementation is based on object-oriented thinking, and the code is slightly complicated. If you understand function pointers, static and const, the structure will not be particularly difficult to understand!

In the code, GUI update and data update are written as two function pointers, which are convenient to use separately.

The code is written in pure C, if you want to use it on a single-chip microcomputer, just copy it directly!

2.4.1 Core logic code

/************************************************* 
Copyright:None 
Author: Silent Knight
Date:2022/7/18
Description:基于哈希表的多级菜单
**************************************************/ 
#include "stdio.h"
#include "string.h"

#define MAX_EXPLAIN    10        /*最大说明缓冲*/
#define HASH_SKEY_LEN  15        /*哈希关键字最大长度*/
#define HASH_SIZE 50 
#define HASH_NULLKEY '\0'
#define HASH_OK    1
#define HASH_ERROR (HASH_SIZE+2)


typedef unsigned long long uint64_t;
typedef unsigned int  uint16_t;
typedef unsigned char uint8_t;
typedef unsigned char Status;
/*初始化函数*/
static void * my_memset(void *s,int c,int n)/*初始化*/
{
    
    
    char *c_s=(char *)s;
    while(n--) *c_s++=c;
    return s;
}
/*复制函数*/
static void * my_memcpy(void *dest,void *src,unsigned int size)
{
    
    
    char *b_dest=(char *)dest,*b_src=(char *)src;
    unsigned int len;
    for(len=size;len>0;len--)
    {
    
    
        *b_dest++=*b_src++;
    }
    return dest;
}


/*函数行为表*/
struct hashmenu_vtbl;
/*
选项位置使用说明:pos赋值为相应的格式的内容即可访问相应节点的内容
1 表示一级选项的第一个
2.2,二级选的下一级子选项的第二个
1.1.1表示一级选的下一级子选项的第一个,
*/
typedef struct 
{
    
    
    char pos[HASH_SKEY_LEN];     /*选项位置也是关键字*/
    char explain[MAX_EXPLAIN];   /*选项说明*/
    void (*gui)(void *parameter);/*选项GUI更新函数*/
    void (*act)(void *parameter);/*选项动作函数*/
    void (*default_act)(void *parameter);/*选项默认动作,执行上下左右切换界面后默认执行*/
}hashmenu_member;                /*每个成员的属性*/
/*哈希表构成菜单*/
typedef struct 
{
    
    
    hashmenu_member hashtable[HASH_SIZE];  /*哈希表*/
    struct hashmenu_vtbl *c_vptr;/*哈希表行为函数指针*/
    int hash_table_size;  //哈希表关键字的数量 
}hashmenu;/*表头*/

/*函数行为表*/
struct hashmenu_vtbl
{
    
    
    Status  (*insert)(hashmenu * const Me,hashmenu_member *const member);     /*添加选项*/
    Status  (*remove)(hashmenu * const Me,hashmenu_member * const member);    /*删除选项*/
    Status  (*modify)(hashmenu * const Me,hashmenu_member * const member);    /*设置选项*/
    Status  (*search)(hashmenu * const Me,hashmenu_member * const member);    /*查找选项*/

    /*找到上下左右选项*/
    Status  (*searchleft)(hashmenu * const Me,hashmenu_member * const member);    /*查找当前节点左选项*/
    Status  (*searchright)(hashmenu * const Me,hashmenu_member * const member);    /*查找当前节点右选项*/
    Status  (*searchup)(hashmenu * const Me,hashmenu_member * const member);     /*查找当前节点上一个选项*/
    Status  (*searchdown)(hashmenu * const Me,hashmenu_member * const member);    /*查找当前节点下一个选项*/
};

/*仅留创建和删除接口留给其他文件进行访问*/
void HashMenuCreate(hashmenu * const Me);
void HashMenuDelete(hashmenu * const Me);

/*增删改查接口*/
static Status HashOpenInser(hashmenu *  const Me,hashmenu_member  *const member);
static Status HashOpenRemove(hashmenu * const Me,hashmenu_member * const member);
static Status HashOpenSearch(hashmenu * const Me,hashmenu_member * const member);
static Status HashOpenModify(hashmenu * const Me,hashmenu_member * const member);

/*菜单切换接口*/
static Status HashMenuPeerLeft(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点同级的选项*/
static Status HashMenuPeerRight(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点下一级的选项*/
static Status HashMenuDepthUp(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点下一级的选项*/
static Status HashMenuDepthDown(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点下一级的选项*/
/*去符号化哈希表*/    
static uint16_t HashOpenKey(const char* skey)  
{
    
      
    char *p = (char*)skey;  
    uint16_t hasy_key = 0;  
    if(*p)
    {
    
      
        for(;*p != '\0'; ++p)  
            hasy_key = (hasy_key << 5) - hasy_key + *p;
    }
    
    return hasy_key%(HASH_SIZE-1);  
}

/*找到唯一的哈希值*/
static uint16_t HashOpenFindUniqueKey(hashmenu * const Me,hashmenu_member  *const member)  
{
    
       
    uint16_t unique_addr;
    int cout=HASH_SIZE-1;
    uint16_t clen;

    if(Me->hash_table_size<=0) return HASH_ERROR;
 
    unique_addr=HashOpenKey(member->pos);
    
    /*根据内容查看哈希值是不是指向需要的地址*/
    clen=strlen(member->pos);
    clen = (clen >HASH_SKEY_LEN) ?HASH_SKEY_LEN:clen;    
    while((strncmp(Me->hashtable[unique_addr].pos,member->pos,clen)!=0)&&cout--) 
    {
    
    
        unique_addr=(unique_addr+1)%HASH_SIZE;    /*开放地址法则线性探测*/
    }
    
    if(cout<0) return HASH_ERROR;/*遍历完这个哈希表都没有找到*/
    return unique_addr;
}
/*创建哈希表*/
void HashMenuCreate(hashmenu * const Me)  
{
    
      
    int i;
    static struct hashmenu_vtbl vtable;
    Me->hash_table_size=0; 
    my_memset(Me, 0, sizeof(hashmenu));
   
    vtable.insert=&HashOpenInser;
    vtable.remove=&HashOpenRemove;
    vtable.modify=&HashOpenModify;
    vtable.search=&HashOpenSearch;

    vtable.searchleft=&HashMenuPeerLeft;
    vtable.searchright=&HashMenuPeerRight;
    vtable.searchup=&HashMenuDepthUp;
    vtable.searchdown=&HashMenuDepthDown;

    Me->c_vptr=&vtable;

}  
/*删除哈希表*/
void HashMenuDelete(hashmenu * const Me)
{
    
    
    int i;
    my_memset(Me, 0, sizeof(hashmenu));
}

/*增*/
Status HashOpenInser(hashmenu * const Me,hashmenu_member  *const member)
{
    
    
    uint16_t addr;
    if(Me->hash_table_size>=HASH_SIZE-1) return HASH_ERROR;
    
    addr=HashOpenKey(member->pos);
    /*开放地址法则线性探测解决冲突*/
    while(Me->hashtable[addr].explain[0]!=HASH_NULLKEY) 
        addr=(addr+1)%HASH_SIZE-1;                
    
    /*插入数据*/
    my_memcpy(&(Me->hashtable[addr]),member,sizeof(hashmenu_member));
    Me->hash_table_size++;

    return HASH_OK;
}

/*删*/
Status HashOpenRemove(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint16_t addr;

    addr=HashOpenFindUniqueKey(Me,member);
    if(addr) 
    {
    
    
        my_memset(&(Me->hashtable[addr]),0,sizeof(hashmenu_member));
        Me->hash_table_size--;
        return HASH_OK;
    }
    else return HASH_ERROR;
    return HASH_OK;
}

/*查*/
Status HashOpenSearch(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint16_t addr;
    addr=HashOpenFindUniqueKey(Me,member);
    if(addr!=HASH_ERROR) 
    {
    
    
        my_memcpy(member,&(Me->hashtable[addr]),sizeof(hashmenu_member));
        return HASH_OK;
    }
    else return HASH_ERROR;
    return HASH_OK;
}

/*改*/
Status HashOpenModify(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint16_t addr;
    addr=HashOpenFindUniqueKey(Me,member);
    if(addr) 
    {
    
    
        my_memcpy(&(Me->hashtable[addr]),member,sizeof(hashmenu_member));
        return HASH_OK;
    }
    else return HASH_ERROR;         

    return HASH_OK;
}

/*找到当前节点同级的左选项*/
/*例如:当前pos="1.1.2",左选项节点就是pos="1.1.1"*/
static Status HashMenuPeerLeft(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    uint8_t t_chang;
    Status flag;
    if(Me->hash_table_size<=0) return HASH_ERROR;
    
    while(*++c_pos!='\0');/*找到位置最后的位置*/
    t_chang=*(--c_pos)-1;/*将最后位置的上一个字符值-1*/
    *c_pos=t_chang;
    
    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        t_chang=(*c_pos)+1;
        *c_pos=t_chang;        
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}

/*找到当前节点同级的右选项*/
/*例如:当前pos="1.1.2",右选项节点就是pos="1.1.3"*/
static Status HashMenuPeerRight(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    uint8_t t_chang;
    Status flag;
    if(Me->hash_table_size<=0) return HASH_ERROR;
    
    while(*++c_pos!='\0');/*找到位置最后的位置*/
    t_chang=*(--c_pos)+1;/*将最后位置的上一个字符值-1*/
    *c_pos=t_chang;
    
    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        t_chang=(*c_pos)-1;
        *c_pos=t_chang;        
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}

/*找到当前节点下一级的选项*/
/*例如:当前pos="1.1",下一级的选项节点就是pos="1.1.1"*/
static Status HashMenuDepthDown(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    Status flag;
    uint16_t clen;
    if(Me->hash_table_size<=0) return HASH_ERROR;
    
    clen=strlen(member->pos);
    if(clen>=HASH_SKEY_LEN-3) return HASH_ERROR; /*避免内存越界*/

    while(*++c_pos!='\0');/*找到位置最后的位置*/
    *c_pos='.';
    *(c_pos+1)='1';
    *(c_pos+2)='\0';

    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        *c_pos='\0';
        *(c_pos+1)='\0';
        *(c_pos+2)='\0';      
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}
/*找到当前节点上一级的选项*/
/*例如:当前pos="1.1",上一级的选项节点就是pos="1"*/
static Status HashMenuDepthUp(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    uint8_t last_pos;/*保存上次数字*/
    Status flag;
    uint16_t clen;
    
    if(Me->hash_table_size<=0) return HASH_ERROR;
    clen=strlen(member->pos);
    if(clen<=1) /*避免内存越界*/
    {
    
    
        Me->c_vptr->search(Me,member);
        return HASH_OK; 
    }
    while(*++c_pos!='\0');/*找到位置最后的位置*/
    last_pos=*(c_pos-1);/*保存数字*/

    *(c_pos-1)='\0';/*.1,删除数字*/
    *(c_pos-2)='\0';/*.,删除点*/

    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        *(c_pos-1)=last_pos;
        *(c_pos-2)='.';        
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}
/*打印成员信息*/ 
void PrintMember(hashmenu_member  member)  
{
    
     
    printf("pos=%s\r\n",member.pos);
}  
/*打印哈希表所有内容*/
void HashOpenPrint(hashmenu const * const Me)  
{
    
     
    for(int i=0;i<HASH_SIZE;i++)
    {
    
    
        if(Me->hashtable[i].explain[0]!=HASH_NULLKEY)
        {
    
    
            printf("key is %s explain = %s\n",Me->hashtable[i].pos,Me->hashtable[i].explain);
        }
    }
}  

2.4.2 Test reference code

Using the reference code, you can mainly look at the format of the data I created. The logic is the same, but it is slightly longer.

/*测试GUI*/
void GUI_1(void *parameter)
{
    
    
    printf("--------------- -GUI 123--------------\r\n");
    printf("---------------->OPTION 1<-------------\r\n");
    printf("-----------------OPTION 2-------------\r\n");
    printf("-----------------OPTION 3-------------\r\n");
}

void GUI_2(void *parameter)
{
    
    
    printf("--------------- -GUI 123--------------\r\n");
    printf("-----------------OPTION 1-------------\r\n");
    printf("---------------->OPTION 2<------------\r\n");
    printf("-----------------OPTION 3-------------\r\n");
}

void GUI_3(void *parameter)
{
    
    
    printf("--------------- -GUI 123--------------\r\n");
    printf("-----------------OPTION 1-------------\r\n");
    printf("-----------------OPTION 2-------------\r\n");
    printf("---------------->OPTION 3<------------\r\n");
}


void GUI_1_1(void *parameter)
{
    
    
    printf("------------------GUI 1_1-------------\r\n");
    printf("---------------->OPTION 1.1<------------\r\n");
    printf("-----------------OPTION 1.2-------------\r\n");
    printf("-----------------OPTION 1.3------------\r\n");
}

void GUI_1_2(void *parameter)
{
    
    
    printf("------------------GUI 1_1-------------\r\n");
    printf("-----------------OPTION 1.1-------------\r\n");
    printf("---------------->OPTION 1.2<------------\r\n");
    printf("-----------------OPTION 1.3------------\r\n");
}

void GUI_1_3(void *parameter)
{
    
    
    printf("------------------GUI 1_1-------------\r\n");
    printf("-----------------OPTION 1.1-------------\r\n");
    printf("-----------------OPTION 1.2-------------\r\n");
    printf("---------------->OPTION 1.3<------------\r\n");
}
/*测试功能函数*/
void func1(void *num)
{
    
    
    hashmenu_member *mem=(hashmenu_member *)num;
    printf("mem->explain =%s\r\n",mem->explain);
}


#define UP      'w'
#define DOWM    's'
#define LEFT    'a'
#define RIGHT   'd'
#define ENTER   'e'
#define QUIT    'q'
hashmenu tmenu;
void test()
{
    
    
    hashmenu_member t_member;
    hashmenu_member d_member;
    int x=1;
    char choice;
    /*创建哈希表*/
    HashMenuCreate(&tmenu);
    /*输入内容,插入哈希表*/
    t_member.act=func1;
    t_member.gui=GUI_1;
    strcpy(t_member.pos,"1");
    strcpy(t_member.explain,"1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    t_member.gui=GUI_2;
    strcpy(t_member.pos,"2");
    strcpy(t_member.explain,"2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    strcpy(t_member.pos,"3");
    t_member.gui=GUI_3;
    t_member.act=func1;
    strcpy(t_member.explain,"3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    t_member.gui=GUI_1_1;
    strcpy(t_member.pos,"1.1");
    strcpy(t_member.explain,"1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.gui=GUI_1_2;
    t_member.act=func1;
    strcpy(t_member.pos,"1.2");
    strcpy(t_member.explain,"1.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.gui=GUI_1_3;
    t_member.act=func1;
    strcpy(t_member.pos,"1.3");
    strcpy(t_member.explain,"1.3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    strcpy(t_member.pos,"2.1");
    strcpy(t_member.explain,"2.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"2.2");
    strcpy(t_member.explain,"2.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);


    strcpy(t_member.pos,"3.1");
    strcpy(t_member.explain,"2.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"3.2");
    strcpy(t_member.explain,"2.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"1.1.1");
    strcpy(t_member.explain,"1.1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"1.1.2");
    strcpy(t_member.explain,"1.1.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"1.1.3");
    strcpy(t_member.explain,"1.1.3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    strcpy(t_member.pos,"2.1.1");
    strcpy(t_member.explain,"2.1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"2.2.1");
    strcpy(t_member.explain,"2.2.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"3.1.1");
    strcpy(t_member.explain,"3.1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"3.1.2");
    strcpy(t_member.explain,"3.1.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"3.1.3");
    strcpy(t_member.explain,"3.1.3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);
    HashOpenPrint(&tmenu);
    printf("--------------------------------------\r\n");
    strcpy(d_member.pos,"1");

    tmenu.c_vptr->search(&tmenu,&d_member);
    PrintMember(d_member);
    while(choice!=QUIT)
    {
    
    
        /*模拟按键或者串口输入*/
        scanf("%c",&choice);
        /*根据输入执行动作*/
        switch (choice)
        {
    
    
        case UP:
            if(tmenu.c_vptr->searchup(&tmenu,&d_member)!=HASH_ERROR)      d_member.act(&d_member);;
            
            break;
        case DOWM:
            if(tmenu.c_vptr->searchdown(&tmenu,&d_member)!=HASH_ERROR)    d_member.act(&d_member);
            break;   
        case LEFT:
            if(tmenu.c_vptr->searchleft(&tmenu,&d_member)!=HASH_ERROR)    d_member.act(&d_member);
            break;   
        case RIGHT:
            if(tmenu.c_vptr->searchright(&tmenu,&d_member)!=HASH_ERROR)    d_member.act(&d_member);
            break;   
        case ENTER:
            d_member.act(&d_member);
            break;              
        default:
            break;
        }
        /*根据查询内容更新GUI*/
        if(choice!='\n') d_member.gui(NULL);
    }

}

int main()
{
    
    
    test();
    return 0;
}

2.4.3 Execution Results

pos=1
context=1
pos=2
context=2
pos=3
context=3
pos=1.1
context=11
pos=1.2
context=12
pos=1.3
context=13
-------------all content-------------
key is 1 explain = 1 EXPLAIN

key is 2 explain = 2 EXPLAIN

key is 3 explain = 3 EXPLAIN

key is 1.1 explain = 1.1 EXPLAIN

key is 1.2 explain = 1.2 EXPLAIN

key is 1.3 explain = 1.3 EXPLAIN

--------------------------------------
pos=1
context=1
d
mem->explain =2 EXPLAIN
,content=2
--------------- -GUI 123--------------
-----------------OPTION 1-------------
---------------->OPTION 2<------------
-----------------OPTION 3-------------
d
mem->explain =3 EXPLAIN
,content=3
--------------- -GUI 123--------------
-----------------OPTION 1-------------
-----------------OPTION 2-------------
---------------->OPTION 3<------------
a
mem->explain =2 EXPLAIN
,content=2
--------------- -GUI 123--------------
-----------------OPTION 1-------------
---------------->OPTION 2<------------
-----------------OPTION 3-------------
a
mem->explain =1 EXPLAIN
,content=1
--------------- -GUI 123--------------
---------------->OPTION 1<-------------
-----------------OPTION 2-------------
-----------------OPTION 3-------------
s
mem->explain =1.1 EXPLAIN
,content=11
------------------GUI 1_1-------------
---------------->OPTION 1.1<------------
-----------------OPTION 1.2-------------
-----------------OPTION 1.3------------
d
mem->explain =1.2 EXPLAIN
,content=12
------------------GUI 1_1-------------
-----------------OPTION 1.1-------------
---------------->OPTION 1.2<------------
-----------------OPTION 1.3------------
d
mem->explain =1.3 EXPLAIN
,content=13
------------------GUI 1_1-------------
-----------------OPTION 1.1-------------
-----------------OPTION 1.2-------------
---------------->OPTION 1.3<------------
a
mem->explain =1.2 EXPLAIN
,content=12
------------------GUI 1_1-------------
-----------------OPTION 1.1-------------
---------------->OPTION 1.2<------------
-----------------OPTION 1.3------------
a
mem->explain =1.1 EXPLAIN
,content=11
------------------GUI 1_1-------------
---------------->OPTION 1.1<------------
-----------------OPTION 1.2-------------
-----------------OPTION 1.3------------


3. Broken thoughts

In fact, this menu is very suitable for debugging or when the hardware is very simple . In a relatively simple environment, for example, there are only buttons and a very simple display (such as a 128*64 OLED), or there is no display at all. In fact, it is recommended that you learn some commonly used embedded GUI graphics libraries, such as LVGL, QT, etc. If you are not like me and need to draw a suitable GUI independently, it is not recommended to use it when not debugging or in an environment where the hardware is really simple.

This is the end of the sharing. I have been thinking about this idea for a long time. From having a suitable idea to writing code while writing an article, I overturned the previous idea twice, and it has been a whole day. I hope it will be helpful to everyone! ! !

4. Complete reference code

The complete reference code, including tests and core code, is posted here.

/************************************************* 
Copyright:None 
Author: Silent Knight
Date:2022/7/18
Description:基于哈希表的多级菜单
**************************************************/ 
#include "stdio.h"
#include "string.h"

#define MAX_EXPLAIN    10        /*最大说明缓冲*/
#define HASH_SKEY_LEN  15        /*哈希关键字最大长度*/
#define HASH_SIZE 50 
#define HASH_NULLKEY '\0'
#define HASH_OK    1
#define HASH_ERROR (HASH_SIZE+2)


typedef unsigned long long uint64_t;
typedef unsigned int  uint16_t;
typedef unsigned char uint8_t;
typedef unsigned char Status;
/*初始化函数*/
static void * my_memset(void *s,int c,int n)/*初始化*/
{
    
    
    char *c_s=(char *)s;
    while(n--) *c_s++=c;
    return s;
}
/*复制函数*/
static void * my_memcpy(void *dest,void *src,unsigned int size)
{
    
    
    char *b_dest=(char *)dest,*b_src=(char *)src;
    unsigned int len;
    for(len=size;len>0;len--)
    {
    
    
        *b_dest++=*b_src++;
    }
    return dest;
}


/*函数行为表*/
struct hashmenu_vtbl;
/*
选项位置使用说明:pos赋值为相应的格式的内容即可访问相应节点的内容
1 表示一级选项的第一个
2.2,二级选的下一级子选项的第二个
1.1.1表示一级选的下一级子选项的第一个,
*/
typedef struct 
{
    
    
    char pos[HASH_SKEY_LEN];     /*选项位置也是关键字*/
    char explain[MAX_EXPLAIN];   /*选项说明*/
    void (*gui)(void *parameter);/*选项GUI更新函数*/
    void (*act)(void *parameter);/*选项动作函数*/
    void (*default_act)(void *parameter);/*选项默认动作,执行上下左右切换界面后默认执行*/
}hashmenu_member;                /*每个成员的属性*/
/*哈希表构成菜单*/
typedef struct 
{
    
    
    hashmenu_member hashtable[HASH_SIZE];  /*哈希表*/
    struct hashmenu_vtbl *c_vptr;/*哈希表行为函数指针*/
    int hash_table_size;  //哈希表关键字的数量 
}hashmenu;/*表头*/

/*函数行为表*/
struct hashmenu_vtbl
{
    
    
    Status  (*insert)(hashmenu * const Me,hashmenu_member *const member);     /*添加选项*/
    Status  (*remove)(hashmenu * const Me,hashmenu_member * const member);    /*删除选项*/
    Status  (*modify)(hashmenu * const Me,hashmenu_member * const member);    /*设置选项*/
    Status  (*search)(hashmenu * const Me,hashmenu_member * const member);    /*查找选项*/

    /*找到上下左右选项*/
    Status  (*searchleft)(hashmenu * const Me,hashmenu_member * const member);    /*查找当前节点左选项*/
    Status  (*searchright)(hashmenu * const Me,hashmenu_member * const member);    /*查找当前节点右选项*/
    Status  (*searchup)(hashmenu * const Me,hashmenu_member * const member);     /*查找当前节点上一个选项*/
    Status  (*searchdown)(hashmenu * const Me,hashmenu_member * const member);    /*查找当前节点下一个选项*/
};

/*仅留创建和删除接口留给其他文件进行访问*/
void HashMenuCreate(hashmenu * const Me);
void HashMenuDelete(hashmenu * const Me);

/*增删改查接口*/
static Status HashOpenInser(hashmenu *  const Me,hashmenu_member  *const member);
static Status HashOpenRemove(hashmenu * const Me,hashmenu_member * const member);
static Status HashOpenSearch(hashmenu * const Me,hashmenu_member * const member);
static Status HashOpenModify(hashmenu * const Me,hashmenu_member * const member);

/*菜单切换接口*/
static Status HashMenuPeerLeft(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点同级的选项*/
static Status HashMenuPeerRight(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点下一级的选项*/
static Status HashMenuDepthUp(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点下一级的选项*/
static Status HashMenuDepthDown(hashmenu * const Me,hashmenu_member * const member);/*找到当前节点下一级的选项*/
/*去符号化哈希表*/    
static uint16_t HashOpenKey(const char* skey)  
{
    
      
    char *p = (char*)skey;  
    uint16_t hasy_key = 0;  
    if(*p)
    {
    
      
        for(;*p != '\0'; ++p)  
            hasy_key = (hasy_key << 5) - hasy_key + *p;
    }
    
    return hasy_key%(HASH_SIZE);  
}

/*找到唯一的哈希值*/
static uint16_t HashOpenFindUniqueKey(hashmenu * const Me,hashmenu_member  *const member)  
{
    
       
    uint16_t unique_addr;
    int cout=HASH_SIZE-1;
    uint16_t clen;

    if(Me->hash_table_size<=0) return HASH_ERROR;
 
    unique_addr=HashOpenKey(member->pos);
    
    /*根据内容查看哈希值是不是指向需要的地址*/
    clen=strlen(member->pos);
    clen = (clen >HASH_SKEY_LEN) ?HASH_SKEY_LEN:clen;    
    while((strncmp(Me->hashtable[unique_addr].pos,member->pos,clen)!=0)&&cout--) 
    {
    
    
        unique_addr=(unique_addr+1)%HASH_SIZE;    /*开放地址法则线性探测*/
    }
    
    if(cout<0) return HASH_ERROR;/*遍历完这个哈希表都没有找到*/
    return unique_addr;
}
/*创建哈希表*/
void HashMenuCreate(hashmenu * const Me)  
{
    
      
    int i;
    static struct hashmenu_vtbl vtable;
    Me->hash_table_size=0; 
    my_memset(Me, 0, sizeof(hashmenu));
   
    vtable.insert=&HashOpenInser;
    vtable.remove=&HashOpenRemove;
    vtable.modify=&HashOpenModify;
    vtable.search=&HashOpenSearch;

    vtable.searchleft=&HashMenuPeerLeft;
    vtable.searchright=&HashMenuPeerRight;
    vtable.searchup=&HashMenuDepthUp;
    vtable.searchdown=&HashMenuDepthDown;

    Me->c_vptr=&vtable;

}  
/*删除哈希表*/
void HashMenuDelete(hashmenu * const Me)
{
    
    
    int i;
    my_memset(Me, 0, sizeof(hashmenu));
}

/*增*/
Status HashOpenInser(hashmenu * const Me,hashmenu_member  *const member)
{
    
    
    uint16_t addr;
    if(Me->hash_table_size>=HASH_SIZE-1) return HASH_ERROR;
    
    addr=HashOpenKey(member->pos);
    /*开放地址法则线性探测解决冲突*/
    while(Me->hashtable[addr].explain[0]!=HASH_NULLKEY) 
        addr=(addr+1)%HASH_SIZE;                
    
    /*插入数据*/
    my_memcpy(&(Me->hashtable[addr]),member,sizeof(hashmenu_member));
    Me->hash_table_size++;

    return HASH_OK;
}

/*删*/
Status HashOpenRemove(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint16_t addr;

    addr=HashOpenFindUniqueKey(Me,member);
    if(addr) 
    {
    
    
        my_memset(&(Me->hashtable[addr]),0,sizeof(hashmenu_member));
        Me->hash_table_size--;
        return HASH_OK;
    }
    else return HASH_ERROR;
    return HASH_OK;
}

/*查*/
Status HashOpenSearch(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint16_t addr;
    addr=HashOpenFindUniqueKey(Me,member);
    if(addr!=HASH_ERROR) 
    {
    
    
        my_memcpy(member,&(Me->hashtable[addr]),sizeof(hashmenu_member));
        return HASH_OK;
    }
    else return HASH_ERROR;
    return HASH_OK;
}

/*改*/
Status HashOpenModify(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint16_t addr;
    addr=HashOpenFindUniqueKey(Me,member);
    if(addr) 
    {
    
    
        my_memcpy(&(Me->hashtable[addr]),member,sizeof(hashmenu_member));
        return HASH_OK;
    }
    else return HASH_ERROR;         

    return HASH_OK;
}

/*找到当前节点同级的左选项*/
/*例如:当前pos="1.1.2",左选项节点就是pos="1.1.1"*/
static Status HashMenuPeerLeft(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    uint8_t t_chang;
    Status flag;
    if(Me->hash_table_size<=0) return HASH_ERROR;
    
    while(*++c_pos!='\0');/*找到位置最后的位置*/
    t_chang=*(--c_pos)-1;/*将最后位置的上一个字符值-1*/
    *c_pos=t_chang;
    
    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        t_chang=(*c_pos)+1;
        *c_pos=t_chang;        
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}

/*找到当前节点同级的右选项*/
/*例如:当前pos="1.1.2",右选项节点就是pos="1.1.3"*/
static Status HashMenuPeerRight(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    uint8_t t_chang;
    Status flag;
    if(Me->hash_table_size<=0) return HASH_ERROR;
    
    while(*++c_pos!='\0');/*找到位置最后的位置*/
    t_chang=*(--c_pos)+1;/*将最后位置的上一个字符值-1*/
    *c_pos=t_chang;
    
    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        t_chang=(*c_pos)-1;
        *c_pos=t_chang;        
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}

/*找到当前节点下一级的选项*/
/*例如:当前pos="1.1",下一级的选项节点就是pos="1.1.1"*/
static Status HashMenuDepthDown(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    Status flag;
    uint16_t clen;
    if(Me->hash_table_size<=0) return HASH_ERROR;
    
    clen=strlen(member->pos);
    if(clen>=HASH_SKEY_LEN-3) return HASH_ERROR; /*避免内存越界*/

    while(*++c_pos!='\0');/*找到位置最后的位置*/
    *c_pos='.';
    *(c_pos+1)='1';
    *(c_pos+2)='\0';

    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        *c_pos='\0';
        *(c_pos+1)='\0';
        *(c_pos+2)='\0';      
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}
/*找到当前节点上一级的选项*/
/*例如:当前pos="1.1",上一级的选项节点就是pos="1"*/
static Status HashMenuDepthUp(hashmenu * const Me,hashmenu_member * const member)
{
    
    
    uint8_t *c_pos=(uint8_t *)member->pos;
    uint8_t last_pos;/*保存上次数字*/
    Status flag;
    uint16_t clen;
    
    if(Me->hash_table_size<=0) return HASH_ERROR;
    clen=strlen(member->pos);
    if(clen<=1) /*避免内存越界*/
    {
    
    
        Me->c_vptr->search(Me,member);
        return HASH_OK; 
    }
    while(*++c_pos!='\0');/*找到位置最后的位置*/
    last_pos=*(c_pos-1);/*保存数字*/

    *(c_pos-1)='\0';/*.1,删除数字*/
    *(c_pos-2)='\0';/*.,删除点*/

    flag=Me->c_vptr->search(Me,member);
    if(flag!=HASH_ERROR) return HASH_OK;
    else
    {
    
    
        /*重新指向先前内容*/
        *(c_pos-1)=last_pos;
        *(c_pos-2)='.';        
        Me->c_vptr->search(Me,member);
    }
    /*执行默认函数*/
    member->default_act(NULL);
    return HASH_OK;
}
/*打印成员信息*/ 
void PrintMember(hashmenu_member  member)  
{
    
     
    printf("pos=%s\r\n",member.pos);
}  
/*打印哈希表所有内容*/
void HashOpenPrint(hashmenu const * const Me)  
{
    
     
    for(int i=0;i<HASH_SIZE;i++)
    {
    
    
        if(Me->hashtable[i].explain[0]!=HASH_NULLKEY)
        {
    
    
            printf("key is %s explain = %s\n",Me->hashtable[i].pos,Me->hashtable[i].explain);
        }
    }
}  

/*测试GUI*/
void GUI_1(void *parameter)
{
    
    
    printf("--------------- -GUI 123--------------\r\n");
    printf("---------------->OPTION 1<-------------\r\n");
    printf("-----------------OPTION 2-------------\r\n");
    printf("-----------------OPTION 3-------------\r\n");
}

void GUI_2(void *parameter)
{
    
    
    printf("--------------- -GUI 123--------------\r\n");
    printf("-----------------OPTION 1-------------\r\n");
    printf("---------------->OPTION 2<------------\r\n");
    printf("-----------------OPTION 3-------------\r\n");
}

void GUI_3(void *parameter)
{
    
    
    printf("--------------- -GUI 123--------------\r\n");
    printf("-----------------OPTION 1-------------\r\n");
    printf("-----------------OPTION 2-------------\r\n");
    printf("---------------->OPTION 3<------------\r\n");
}


void GUI_1_1(void *parameter)
{
    
    
    printf("------------------GUI 1_1-------------\r\n");
    printf("---------------->OPTION 1.1<------------\r\n");
    printf("-----------------OPTION 1.2-------------\r\n");
    printf("-----------------OPTION 1.3------------\r\n");
}

void GUI_1_2(void *parameter)
{
    
    
    printf("------------------GUI 1_1-------------\r\n");
    printf("-----------------OPTION 1.1-------------\r\n");
    printf("---------------->OPTION 1.2<------------\r\n");
    printf("-----------------OPTION 1.3------------\r\n");
}

void GUI_1_3(void *parameter)
{
    
    
    printf("------------------GUI 1_1-------------\r\n");
    printf("-----------------OPTION 1.1-------------\r\n");
    printf("-----------------OPTION 1.2-------------\r\n");
    printf("---------------->OPTION 1.3<------------\r\n");
}
/*测试功能函数*/
void func1(void *num)
{
    
    
    hashmenu_member *mem=(hashmenu_member *)num;
    printf("mem->explain =%s\r\n",mem->explain);
}


#define UP      'w'
#define DOWM    's'
#define LEFT    'a'
#define RIGHT   'd'
#define ENTER   'e'
#define QUIT    'q'
hashmenu tmenu;
void test()
{
    
    
    hashmenu_member t_member;
    hashmenu_member d_member;
    int x=1;
    char choice;
    /*创建哈希表*/
    HashMenuCreate(&tmenu);
    /*输入内容,插入哈希表*/
    t_member.act=func1;
    t_member.gui=GUI_1;
    strcpy(t_member.pos,"1");
    strcpy(t_member.explain,"1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    t_member.gui=GUI_2;
    strcpy(t_member.pos,"2");
    strcpy(t_member.explain,"2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    strcpy(t_member.pos,"3");
    t_member.gui=GUI_3;
    t_member.act=func1;
    strcpy(t_member.explain,"3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    t_member.gui=GUI_1_1;
    strcpy(t_member.pos,"1.1");
    strcpy(t_member.explain,"1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.gui=GUI_1_2;
    t_member.act=func1;
    strcpy(t_member.pos,"1.2");
    strcpy(t_member.explain,"1.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.gui=GUI_1_3;
    t_member.act=func1;
    strcpy(t_member.pos,"1.3");
    strcpy(t_member.explain,"1.3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    strcpy(t_member.pos,"2.1");
    strcpy(t_member.explain,"2.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"2.2");
    strcpy(t_member.explain,"2.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);


    strcpy(t_member.pos,"3.1");
    strcpy(t_member.explain,"2.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"3.2");
    strcpy(t_member.explain,"2.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"1.1.1");
    strcpy(t_member.explain,"1.1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"1.1.2");
    strcpy(t_member.explain,"1.1.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"1.1.3");
    strcpy(t_member.explain,"1.1.3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    strcpy(t_member.pos,"2.1.1");
    strcpy(t_member.explain,"2.1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"2.2.1");
    strcpy(t_member.explain,"2.2.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    t_member.act=func1;
    PrintMember(t_member);

    strcpy(t_member.pos,"3.1.1");
    strcpy(t_member.explain,"3.1.1 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"3.1.2");
    strcpy(t_member.explain,"3.1.2 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);

    t_member.act=func1;
    strcpy(t_member.pos,"3.1.3");
    strcpy(t_member.explain,"3.1.3 EXPLAIN\r\n");
    tmenu.c_vptr->insert(&tmenu,&t_member);
    PrintMember(t_member);
    HashOpenPrint(&tmenu);
    printf("--------------------------------------\r\n");
    strcpy(d_member.pos,"1");

    tmenu.c_vptr->search(&tmenu,&d_member);
    PrintMember(d_member);
    while(choice!=QUIT)
    {
    
    
        /*模拟按键或者串口输入*/
        scanf("%c",&choice);
        /*根据输入执行动作*/
        switch (choice)
        {
    
    
        case UP:
            if(tmenu.c_vptr->searchup(&tmenu,&d_member)!=HASH_ERROR)      d_member.act(&d_member);;
            
            break;
        case DOWM:
            if(tmenu.c_vptr->searchdown(&tmenu,&d_member)!=HASH_ERROR)    d_member.act(&d_member);
            break;   
        case LEFT:
            if(tmenu.c_vptr->searchleft(&tmenu,&d_member)!=HASH_ERROR)    d_member.act(&d_member);
            break;   
        case RIGHT:
            if(tmenu.c_vptr->searchright(&tmenu,&d_member)!=HASH_ERROR)    d_member.act(&d_member);
            break;   
        case ENTER:
            d_member.act(&d_member);
            break;              
        default:
            break;
        }
        /*根据查询内容更新GUI*/
        if(choice!='\n') d_member.gui(NULL);
    }

}

int main()
{
    
    
    test();
    return 0;
}

5.PS

In fact, the structure of the article is almost the same as the location of the code definition I wrote, you can turn it up and have a look.

Guess you like

Origin blog.csdn.net/weixin_46185705/article/details/125838527