很久之前,写过一个简易的多级菜单,如今回头看,我都不敢承认那玩意是自己写的,于是打算重新写过一个,并且做成一个简易的GUI。原来用的OLED,现在改成了TFT(ST7789驱动)。但是答题的思路还是不变的。

先说说思路,由于选择的是一个没有触摸的屏幕,于是就要加上物理按键。这样才能操作屏幕嘛。
按照面向对象的思想,我将整个GUI结构分为三类:按键,显示,动作。

  1. 按键用来控制屏幕,进行上滑下滑切入切出菜单;
  2. 显示则作为每一级菜单的背景,并且将菜单设置为静态显示(即每次切换只显示一次);
  3. 动作则用来链接各种显示功能。例如一级菜单切入二级菜单这么个动作;
  4. 除此之外就是各种动态效果,这部分暂时还未开工。

接下来看看menu.c文件的内容

#include <stdio.h>
#include "string.h"
#include "lcd_init.h"
#include "lcd.h"
#include "main.h"
#include "menu.h"//多级菜单的级数
#define     Menu_Select     3//选项显示缓存区
char buf[100];//按键标志初始值
uint8_t List_Number = 1;//主次菜单切换
static uint8_t Menu_Change = 0;
//菜单刷新标志位
static uint8_t Menu_Refresh_Flag = 0;
//up/down控制按键标志位
static uint8_t UD_Action_Flag = 0;extern unsigned char BMP_1[];
extern unsigned char BMP_2[];
extern unsigned char BMP_3[];
extern unsigned char BMP_4[];
extern unsigned char BMP_5[];
extern unsigned char BMP_6[];
extern unsigned char BMP_7[];
extern unsigned char BMP_8[];
extern unsigned char BMP_9[];/******************************************************************************* @brief  绘制一条垂线或者横线* @param  x1,y1 起始坐标x2,y2 终点坐标color 颜色* @retval 无******************************************************************************/
void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{u16 i = 0;if (x1 == x2){for (i = y1; i <= y2; i++){LCD_DrawPoint(x1, i, color);}}if (y1 == y2){for (i = x1; i <= x2; i++){LCD_DrawPoint(i, y1, color);}}
}/******************************************************************************* @brief  绘制一个方框* @param  x1,y1 起始坐标x2,y2 终点坐标color 颜色* @retval 无******************************************************************************/
void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color)
{Draw_Line(x1, y1, x2, y1, color);Draw_Line(x1, y1, x1, y2, color);Draw_Line(x1, y2, x2, y2, color);Draw_Line(x2, y1, x2, y2, color);
}/******************************************************************************* @brief  绘制选项框* @param  x1,y1 起始坐标x2,y2 终点坐标color 颜色index 选项框的厚度* @retval 无******************************************************************************/
void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index)
{u16 i = 0;for (i = 0; i < index; i++){Draw_Rectangle(x1 + i, y1 + i, x2 - i, y2 - i, color);}
}/******************************************************************************* @brief  绘制窗体* @param  x1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List    选项框个数* @retval 无******************************************************************************/
void Draw_Windows(void)
{Draw_Box(0, 0, 240, 240, LBBLUE, 5);Draw_Box(0, 36, 240, 41, LBBLUE, 5);sprintf(buf, "   WINDOWS1      ");LCD_ShowString(5, 5, (uint8_t*)buf, WHITE, LBBLUE, 32, 0);
}/******************************************************************************* @brief  绘制圆角框* @param  x0,y0 圆心坐标color 颜色length 框体长度* @retval 无******************************************************************************/
void Draw_Circal_Box(u16 x0, u16 y0, u8 r, u16 color, u8 length)
{int a, b;a = 0;b = r;while (a <= b){LCD_DrawPoint(x0 - b, y0 - a, color);LCD_DrawPoint(x0 - b, y0 + a, color);LCD_DrawPoint(x0 - a, y0 + b, color);LCD_DrawPoint(x0 - a, y0 - b, color);LCD_DrawPoint(x0 + b + length, y0 - a, color);LCD_DrawPoint(x0 + b + length, y0 + a, color);LCD_DrawPoint(x0 + a + length, y0 - b, color);LCD_DrawPoint(x0 + a + length, y0 + b, color);a++;if ((a * a + b * b) > (r * r))//判断要画的点是否过远{b--;}}LCD_DrawLine(x0, y0 - r, x0 + length, y0 - r, color);LCD_DrawLine(x0, y0 + r, x0 + length, y0 + r, color);
}/******************************************************************************* @brief  绘制实心圆* @param  x0,y0 圆心坐标color 颜色* @retval 无******************************************************************************/
void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color)
{int a, b, c;a = 0;b = r;for (c = r; c > 0; c--){Draw_Circle(x0, y0, c - 1, color);}
}/******************************************************************************* @brief   显示初始菜单,自己任意配置* @param  x,y 显示坐标fc 字的颜色bc 字的背景色sizey 字号mode:  0非叠加模式  1叠加模式Box_Height 选项框的高度* @retval 无
******************************************************************************/
void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height)
{sprintf(buf,"DISPLAY");LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"VOICE");LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"NOTICE");LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"BATTERY");LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"MEMORY");LCD_ShowString(x, y + ( Box_Height * 4 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"ABOUT");LCD_ShowString(x, y + ( Box_Height * 5 ), (uint8_t *)buf, fc, bc, sizey, mode);
}/******************************************************************************* @brief   显示二级菜单,自己任意配置* @param  x,y 显示坐标fc 字的颜色bc 字的背景色sizey 字号mode:  0非叠加模式  1叠加模式Box_Height 选项框的高度* @retval 无
******************************************************************************/
void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height)
{sprintf(buf,"DISPLAY");LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"VOICE");LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"NOTICE");LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"BATTERY");LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);
}/******************************************************************************* @brief   显示三级菜单,自己任意配置* @param  x,y 显示坐标fc 字的颜色bc 字的背景色sizey 字号mode:  0非叠加模式  1叠加模式Box_Height 选项框的高度* @retval 无
******************************************************************************/
void Display_Third_Menu(void)
{LCD_ShowPicture( 25, 15, 50, 50, BMP_1);   //1LCD_ShowPicture( 95, 15, 50, 50, BMP_2); //2LCD_ShowPicture(175, 15, 50, 50, BMP_3); //3LCD_ShowPicture( 15, 95, 50, 50, BMP_4); //4LCD_ShowPicture( 95, 95, 50, 50, BMP_5); //5LCD_ShowPicture(175, 95, 50, 50, BMP_6); //6LCD_ShowPicture( 15, 175, 50, 50, BMP_7);    //7LCD_ShowPicture( 95, 175, 50, 50, BMP_8);    //8LCD_ShowPicture(175, 175, 50, 50, BMP_9);    //9
}/******************************************************************************* @brief  注册up控制按键* @param  Menu_List:控制的列表项数目* @retval 无* eg:一个页面有6个选项,就让 Menu_List = 6******************************************************************************/
void Button_Up_Click(uint8_t Menu_List)
{if (Button_Click_Event_1){if (List_Number > 1)List_Number = List_Number - 1;else if (List_Number == 1)List_Number = Menu_List;elseList_Number = List_Number;UD_Action_Flag = 2;//显示列表项标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}
}/******************************************************************************* @brief  注册down控制按键* @param  控制的列表项数目* @retval 无******************************************************************************/
void Button_Down_Click(uint8_t Menu_List)
{if (Button_Click_Event_2){if (List_Number < Menu_List)List_Number = List_Number + 1;else if (List_Number == Menu_List)List_Number = 1;elseList_Number = List_Number;UD_Action_Flag = 1;//显示列表项标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}
}/******************************************************************************* @brief  注册菜单切入控制按键* @param  Desk_Num 菜单级数* @retval 无****************************************************************************/
void Button_Menu_Enter(u8 Desk_Num)
{LCD_ShowIntNum(200,220,Menu_Change,1,WHITE,BLACK,16);//切入菜单,即进入下一级菜单if(Button_Click_Event_3){Action_Menu_Change(0);     //载入下一级菜单动作Menu_Refresh_Flag = 1;if (Menu_Change < Desk_Num - 1)Menu_Change = Menu_Change + 1;else if (Menu_Change == Desk_Num - 1)Menu_Change = Desk_Num - 1;//显示菜单级标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}
}/******************************************************************************* @brief  注册菜单切出控制按键* @param  Desk_Num 菜单级数* @retval 无****************************************************************************/
void Button_Menu_Exit(u8 Desk_Num)
{LCD_ShowIntNum(200, 220, Menu_Change, 1, WHITE, BLACK, 16);//切出菜单,即返回上一级菜单if (Button_Click_Event_4){Action_Menu_Change(1);      //载入上一级菜单的动作Menu_Refresh_Flag = 1;if (Menu_Change > 0)Menu_Change = Menu_Change - 1;if (Menu_Change < Desk_Num - 2)Menu_Change = 0;//显示菜单级标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}
}/******************************************************************************* @brief  注册菜单切换控制按键搭配的动作* @param  select 事件标志* @retval 无****************************************************************************/
u8 List = 0,List2 = 0;
void Action_Menu_Change(u8 select)
{switch (select){case 0:switch (Menu_Change){case 0:List = List_Number;List_Number = 1;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;case 1:List2 = List_Number;List_Number = 1;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;//case ...3级、4级菜单}break;case 1:switch (Menu_Change){case 1:List_Number = List;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;case 2:List_Number = List2; LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;//case ...3级、4级菜单}default:break;}}/******************************************************************************* @brief  注册选项框上滑动作* @param  x1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List    选项框个数* @retval 无******************************************************************************/
void Action_Box_Up(u16 x1,u16 y1,u16 x2,u16 y2,uint8_t Menu_List, uint8_t Box_Height)
{if(UD_Action_Flag == 2){UD_Action_Flag = 0;if(List_Number >= 1 && List_Number < Menu_List){LCD_DrawLine(x1, y2+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);LCD_DrawLine(x1, y1+Box_Height*(List_Number), x1, y2+Box_Height*(List_Number), BLACK);LCD_DrawLine(x2, y1+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);printf("%d",List_Number);}if(List_Number == Menu_List){LCD_DrawRectangle(x1, y1 + Box_Height*(0), x2, y2+Box_Height*(0), BLACK);}}}/******************************************************************************* @brief  注册选项框下滑动作* @param  x1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List 选项框个数* @retval 无******************************************************************************/
void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height)
{if (UD_Action_Flag == 1){UD_Action_Flag = 0;if (List_Number > 1 && List_Number <= Menu_List){LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x2, y1 + Box_Height * (List_Number - 2), BLACK);LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x1, y2 + Box_Height * (List_Number - 2), BLACK);LCD_DrawLine(x2, y1 + Box_Height * (List_Number - 2), x2, y2 + Box_Height * (List_Number - 2), BLACK);printf("%d", List_Number);}if (List_Number == 1){LCD_DrawRectangle(x1, y1 + Box_Height * (Menu_List - 1), x2, y2 + Box_Height * (Menu_List - 1), BLACK);}}
}/******************************************************************************* @brief  绘制选项框* @param  x1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List   选项框个数* @retval 无******************************************************************************/
void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height)
{LCD_DrawRectangle(x1, y1 + Box_Height * (List_Number - 1), x2, y2 + Box_Height * (List_Number - 1), BLUE);
}/******************************************************************************* @brief  注册菜单* @param  无* @retval 无******************************************************************************/
void Menu_Display_Control(void)
{Button_Menu_Enter(Menu_Select);                                                //菜单等级控制Button_Menu_Exit(Menu_Select);switch (Menu_Change){case 0:if (Menu_Refresh_Flag == 1){Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30);       //注册一个0级菜单Menu_Refresh_Flag = 0;}Draw_Option_Box(3, 0, 200, 30, 6, 30);                            //注册选项框Action_Box_Up(3, 0, 200, 30, 6, 30);                     //添加选项框的上滑动作Action_Box_Down(3, 0, 200, 30, 6, 30);                  //添加选项框的下滑动作Button_Down_Click(6);                                       //注册down控制按钮Button_Up_Click(6);                                         //注册up控制按钮break;case 1:if (Menu_Refresh_Flag == 1){Display_Second_Menu(10, 3, RED, BLACK, 32, 0, 59);         //注册一个1级菜单Menu_Refresh_Flag = 0;}Draw_Option_Box(3, 0, 150, 59, 4, 59);                            //注册选项框Action_Box_Up(3, 0, 150, 59, 4, 59);                     //添加选项框的上滑动作Action_Box_Down(3, 0, 150, 59, 4, 59);                  //添加选项框的下滑动作Button_Down_Click(4);                                       //注册down控制按钮Button_Up_Click(4);                                         //注册up控制按钮break;case 2:if (Menu_Refresh_Flag == 1){Display_Third_Menu();          //注册一个2级菜单Menu_Refresh_Flag = 0;}Draw_Option_Box(0, 0, 80, 79, 3, 79);                         //注册选项框Action_Box_Up(0, 0, 80,79, 3, 79);                       //添加选项框的上滑动作Action_Box_Down(0, 0, 80, 79, 3, 79);                   //添加选项框的下滑动作Button_Down_Click(3);                                       //注册down控制按钮Button_Up_Click(3);                                         //注册up控制按钮break;default:Menu_Refresh_Flag = 0;break;}
}

重点:如何创建菜单

如果使用其中自带的选项框,那么只需要修改两处即可
一是 Action_Menu_Change(u8 select) 函数,在其中添加 List_Number 变量的继承;
二是Menu_Display_Control(void) 函数中注册新的菜单。
重点在第二个函数

  1. 首先要确定菜单级数
  2. 然后申请一个或多个新的菜单,并且在Menu_Refresh_Flag == 1的情况下申请,不然会不断刷新屏幕
  3. 然后注册选项框,注册选项框的动作,动作可以自行修改
  4. 最后注册按键

接下来是menu.h文件内容

#ifndef __menu_H_
#define __menu_H_
#include <stdio.h>
#include "string.h"
#include "stdint.h"
#include "main.h"#define Button_Click_Event_1 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET  //up控制按键
#define Button_Click_Event_2    HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET  //down控制按键
#define Button_Click_Event_3    HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET  //菜单切入控制按键
#define Button_Click_Event_4    HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET  //菜单切出控制按键void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);
void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);
void Display_Third_Menu(void);void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index);
void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);
void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);void Draw_Windows(void);
void Draw_Circal_Box(u16 x0,u16 y0,u8 r,u16 color, u8 length);
void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color);void Button_Down_Click(uint8_t Menu_List);
void Button_Up_Click(uint8_t Menu_List);
void Button_Menu_Enter(u8 Desk_Num);
void Button_Menu_Exit(u8 Desk_Num);void Action_Box_Up(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);
void Action_Menu_Change(u8 select);void Menu_Display_Control(void); //void DA(void);
#endif

最后是main.c的内容

在while循环里添加 Menu_Display_Control(); 即可,菜单的其他配置都在menu.c中配置完成。

int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART2_UART_Init();LCD_Init();//LCD初始化LCD_Fill(0,0,LCD_W,LCD_H,BLACK);Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30);      //注册一个0级菜单,防止刚开机不显示东东西LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */Menu_Display_Control();}/* USER CODE END 3 */
}

显示效果

B站视频效果

STM32之简易GUI(多级菜单进阶版)相关推荐

  1. STM32+0.96OLED的多级菜单设计

    硬件 硬件选型 STM32F103C8T6最小核心板 0.96寸四脚OLED屏幕IIC接口 普通按键5个 硬件连线 SCL ---- PA1 SDA ---- PA2 KEY_UP ---- PA4 ...

  2. STM32简易多级菜单(数组查表法)

    单片机开发中,有时会用到屏幕来显示内容,当需要逐级显示内容时,就需要使用多级菜单的形式了. 1 多级菜单 多级菜单的实现,大体分为两种设计思路: 通过双向链表实现 通过数组查表实现 总体思路都是把菜单 ...

  3. 基于STM32的OLED多级菜单GUI实现(简化版智能手表)

    前言:本文的OLED多级菜单UI为一个综合性的STM32小项目,使用多传感器与OLED显示屏实现智能终端的效果.项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换,搭配DH ...

  4. 【STM32】基于STM32F103C8T6的水质检测系统设计(声光报警、多级菜单)

    需求 1.检测参数:水温.TDS.浊度.PH 2.超出阈值声光报警 3.LCD显示目标参数的测量结果 4.测量模式:单参数测量.所有参数表同时测量 切换方式:按键切换 原理 单总线技术 单总线技术采用 ...

  5. Android简易音乐重构MVVM Java版-新增推荐菜单及侧边栏展示(十二)

    Android简易音乐重构MVVM Java版-新增推荐菜单及侧边栏展示(十二) 关于 效果图 添加侧边栏 添加推荐歌单 新增RecommendAdapter适配器 修改DiscoverFragmen ...

  6. Java 实现 多级菜单

    一:前言 最近老师布置了给多级菜单的作业,感觉蛮有意思的,可以提升自己的逻辑!下面我写个简易版的多级菜单,本人还是菜鸟,欢迎各位给予宝贵的建议! 二:正文 由于是给各位演示,所有我把涉及的其他条件全省 ...

  7. python基础:多级菜单

    #类似于多级菜单中的经典列子---城市的多级菜单#个人更改了组成成分,效果大致不变,总体还是臃肿的,还在练习中,算是基本能实现效果了#---基本功能---#1.用户能根据选择进入菜单#2.用户能返回上 ...

  8. C语言 进阶版三子棋小游戏

    目录 前言 游戏运行效果: 游戏代码: 1.test.c文件 2.  game.h头文件 3.  game.c 一.框架部分 二.游戏函数实现 1.创建数组并初始化 2.打印数组 3.玩家下棋 4.电 ...

  9. 原版蓝光光盘加国语配音加中文字幕及解说字幕保留弹出菜单傻瓜版教程

    原版蓝光光盘加国语配音加中文字幕及解说字幕保留弹出菜单傻瓜版教程 闲来无事,发现了一本通俗易懂的教程,转发过来,让有点基础的同行们学习一下. 在这感谢原著者的辛勤劳动! 1. 得到片源 朋友到国外出差 ...

最新文章

  1. php 缓冲区,PHP的输出缓冲区
  2. Android(三)——将程序打包成APK文件,进行APK签名以及反编译APK
  3. chrome 代理插件_Chrome浏览器拓展插件同步助手
  4. Activity 横竖屏切换
  5. 移植marvell poncat3 demo板的总结
  6. IBM ThinkPad SL400 XP驱动
  7. 茫茫社招路,硕士毕业半年的抉择
  8. 魔兽世界用宏显示服务器时间,魔兽世界宏命令全表
  9. FOTA升级差分包编译服务器搭建
  10. p39 8.由以下三个集合,集合成员分别是会Python、C、Java的人员。
  11. 微信生态的渗透与价值
  12. 基于android的门禁系统代码,基于ESP8266 base on esp8266门禁系统 安卓源码
  13. markdown列表中的缩进
  14. 乔布斯的康熙盛世,库克的雍正王朝——纪念乔布斯逝世5周年
  15. 总投资1478亿!三星表示停止LCD 面板,转换为QD-OLED
  16. 一个做耽美漫画的内容网站源码解析过程,讲解他的框架和功能实现
  17. Java中加载配置文件方式的总结
  18. go语言实现爬虫(爬两层)
  19. 管理员管理留言php,自定义WordPress管理员留言头像
  20. Java实现文件的拆分合并

热门文章

  1. Java总结13 Lambda表达式 和 方法引用 的概念与应用
  2. Nexus安装和使用
  3. 新浪微博开发(五)AppList界面
  4. ThinkPHP5.0 中使用荣联云通讯
  5. java项目如何发送邮件
  6. Oracle数据库网络详解
  7. 产品生命周期管理——高效的全生命周期的产品管理方法
  8. CentOS 路由设置
  9. 【UML】— 用例图
  10. 什么是JDBC,JDBC是干嘛用的?