前言
最近看了 电子量产工具 这个项目,本专栏是对该项目的一个总结。
一、业务系统分析
前面实现了各个子系统:显示、输入、文字、UI、页面,它们只是提供基础能力,跟业务逻辑没有关系。这样的架构很容易扩展,可以在这上面实现各种业务,对于不同的产品,我们只需要写出自己页面函数。
那么对于我们的带电子量产工具,是怎么设置 的主页面呢?
首先 观赏 最终实验效果:
可以看到在这个主界面上有很多按键,按键上有各自的名字。初始时,按键是红色,当点击按键时,变成绿色。只有一些按键可以通过点击改变颜色,有的可以通过网络输入改变颜色,甚至可以显示进度蓝色。
可以创建一个配置文件 来描述各个按键的特点:
我们编写 主页面代码时,需要先从配置文件中解析 出数据,再根据数据生成按钮。
当有事件发生时,就可以找到对应按钮,再 根据配置文案金的数据 执行相应的 点击函数。
二、处理配置文件
- 对于配置文件的每一行,都创建一个ItemCfg 结构体。(每一个按钮)
typedef struct ItemCfg { int index; char name[100]; int bCanBeTouched; // 是否可以被点击 char command[100]; // 状态发生变化时 调用的命令 }ItemCfg, *PItemCfg;
- 解析配置文件。
int ParseConfigFile(void) { FILE *fp; char buf[100]; char *p = buf; /* 1. open config file */ fp = fopen(CFG_FILE, "r"); if (!fp) { printf("can not open cfg file %s\n", CFG_FILE); return -1; } while (fgets(buf, 100, fp)) { /* 2.1 read each line */ buf[99] = '\0'; /* 2.2 吃掉开头的空格或TAB */ p = buf; while (*p == ' ' || *p =='\t') p++; /* 2.3 忽略注释 */ if (*p == '#') continue; /* 2.4 处理 */ g_tItemCfgs[g_iItemCfgCount].command[0] = '\0'; g_tItemCfgs[g_iItemCfgCount].index = g_iItemCfgCount; sscanf(p, "%s %d %s", g_tItemCfgs[g_iItemCfgCount].name, &g_tItemCfgs[g_iItemCfgCount].bCanBeTouched, \ g_tItemCfgs[g_iItemCfgCount].command); g_iItemCfgCount++; } return 0; }
- 获取配置文件按键的数目。
int GetItemCfgCount(void) { return g_iItemCfgCount; }
- 根据 系数 和 名字 获取配置文件。
PItemCfg GetItemCfgByIndex(int index) { if (index < g_iItemCfgCount) return &g_tItemCfgs[index]; else return NULL; } PItemCfg GetItemCfgByName(char *name) { int i; for (i = 0; i < g_iItemCfgCount; i++) { if (strcmp(name, g_tItemCfgs[i].name) == 0) return &g_tItemCfgs[i]; } return NULL; }
三、生成界面
- 之前 在 页面系统 中写的是 默认的按下函数。
对于按钮,我们要提供自己的OnPressed函数,不适用UI系统默认的函数,这里编写 main_page 按下函数。
static int MainPageOnPressed(struct Button *ptButton, PDispBuff ptDispBuff, PInputEvent ptInputEvent) { unsigned int dwColor = BUTTON_DEFAULT_COLOR; char name[100]; char status[100]; char *strButton; strButton = ptButton->name; /* 1. 对于触摸屏事件 */ if (ptInputEvent->iType == INPUT_TYPE_TOUCH) { /* 1.1 分辨能否被点击 */ if (GetItemCfgByName(ptButton->name)->bCanBeTouched == 0) return -1; /* 1.2 修改颜色 */ ptButton->status = !ptButton->status; if (ptButton->status) dwColor = BUTTON_PRESSED_COLOR; } else if (ptInputEvent->iType == INPUT_TYPE_NET) { /* 2. 对于网络事件 */ /* 根据传入的字符串修改颜色 : wifi ok, wifi err, burn 70 */ sscanf(ptInputEvent->str, "%s %s", name, status); if (strcmp(status, "ok") == 0) dwColor = BUTTON_PRESSED_COLOR; else if (strcmp(status, "err") == 0) dwColor = BUTTON_DEFAULT_COLOR; else if (status[0] >= '0' && status[0] <= '9') { dwColor = BUTTON_PERCENT_COLOR; strButton = status; } else return -1; } else { return -1; } /* 绘制底色 */ DrawRegion(&ptButton->tRegion, dwColor); /* 居中写文字 */ DrawTextInRegionCentral(strButton, &ptButton->tRegion, BUTTON_TEXT_COLOR); /* 刷新界面到 lcd/web */ FlushDisplayRegion(&ptButton->tRegion, ptDispBuff); return 0; }
- 根据配置文件生成按钮、界面:
static void GenerateButtons(void) { int width, height; int n_per_line; int row, rows; int col; int n; PDispBuff pDispBuff; int xres, yres; int start_x, start_y; int pre_start_x, pre_start_y; PButton pButton; int i = 0; /* 算出单个按钮的width/height */ g_tButtonCnt = n = GetItemCfgCount(); pDispBuff = GetDisplayBuffer(); xres = pDispBuff->iXres; yres = pDispBuff->iYres; width = sqrt(1.0/0.618 *xres * yres / n); n_per_line = xres / width + 1; width = xres / n_per_line; height = 0.618 * width; /* 居中显示: 计算每个按钮的region */ start_x = (xres - width * n_per_line) / 2; rows = n / n_per_line; if (rows * n_per_line < n) rows++; start_y = (yres - rows*height)/2; /* 计算每个按钮的region */ for (row = 0; (row < rows) && (i < n); row++) { pre_start_y = start_y + row * height; pre_start_x = start_x - width; for (col = 0; (col < n_per_line) && (i < n); col++) { pButton = &g_tButtons[i]; pButton->tRegion.iLeftUpX = pre_start_x + width; pButton->tRegion.iLeftUpY = pre_start_y; pButton->tRegion.iWidth = width - X_GAP; pButton->tRegion.iHeigh = height - Y_GAP; pre_start_x = pButton->tRegion.iLeftUpX; /* InitButton */ InitButton(pButton, GetItemCfgByIndex(i)->name, NULL, NULL, MainPageOnPressed); i++; } } /* OnDraw */ for (i = 0; i < n; i++) g_tButtons[i].OnDraw(&g_tButtons[i], pDispBuff); }
四、根据输入事件找到按钮
对于触摸屏事件,根据iX、iY找到按钮.
对于网络数据,我们限定为这样的格式:“name ok”、“name err”、“name 70%”根据name找到按钮.
/* 判断点击的位置 */ static int isTouchPointInRegion(int iX, int iY, PRegion ptRegion) { if (iX < ptRegion->iLeftUpX || iX >= ptRegion->iLeftUpX + ptRegion->iWidth) return 0; if (iY < ptRegion->iLeftUpY || iY >= ptRegion->iLeftUpY + ptRegion->iHeigh) return 0; return 1; } /* 根据输入事件找到按钮 */ static PButton GetButtonByInputEvent(PInputEvent ptInputEvent) { int i; char name[100]; if (ptInputEvent->iType == INPUT_TYPE_TOUCH) { for (i = 0; i < g_tButtonCnt; i++) { if (isTouchPointInRegion(ptInputEvent->iX, ptInputEvent->iY, &g_tButtons[i].tRegion)) return &g_tButtons[i]; } } else if (ptInputEvent->iType == INPUT_TYPE_NET) { sscanf(ptInputEvent->str, "%s", name); return GetButtonByName(name); } else { return NULL; } return NULL; }
- 重写 main_page.c 的 Run 函数。
static void MainPageRun(void *pParams) { int error; InputEvent tInputEvent; PButton ptButton; PDispBuff ptDispBuff = GetDisplayBuffer(); /* 读取配置文件 */ error = ParseConfigFile(); if (error) return ; /* 根据配置文件生成按钮、界面 */ GenerateButtons(); while (1) { /* 读取输入事件 */ error = GetInputEvent(&tInputEvent); if (error) continue; /* 根据输入事件找到按钮 */ ptButton = GetButtonByInputEvent(&tInputEvent); if (!ptButton) continue; /* 调用按钮的OnPressed函数 */ ptButton->OnPressed(ptButton, ptDispBuff, &tInputEvent); } }
五、业务系统总流程测试
测试效果:
总结
这个电子量产工具的项目就到这里结束了,后面继续推出 面试笔试的 新专栏,帮助大家避开 笔试的坑。