基于 GD32F303ZET6 苹果派开发板
简介
行输入是一种输入控件,用于获取用户输入的一行字符串,应用广泛,浏览器的搜索栏、一些小工具的参数设置,等等。行输入控件通常有两种状态,分别是有焦点和无焦点。写入时只能写入有焦点的行输入控件,不能写入没有焦点的行输入控件;读取时不论控件是否有焦点,都能读取成功。
行输入控件通常与按键输入控件搭配使用,实现基本的用户交互。
控制结构体设计
行输入控件具有“有焦点”和“无焦点”两种状态,通过点击改变状态。点击一个行输入控件后,如果该控件没有焦点,那么会自动获取焦点。此时,如果界面内有其它行输入控件具有焦点,那么就要取消该控件的焦点,保证界面内只能有一个行输入控件有焦点。行输入控件的状态是通过点击改变的,与按键类似,因此行输入控件的控制结构体可以参考按键控件,如下所示。
//行输入控件控制结构体
typedef struct
{
u16 x, y, width, height; //原点、宽度、高度
char* text; //输入结果,创建时自动动态分配
u8 textSize; //字体大小,可以是 12、16、24
u16 textColor; //字体颜色
u8 focus; //0-无焦点,1-有焦点
u16 hasFocusColor; //有焦点时显示的颜色,没有背景图片时使用
u16 noFocusColor; //无焦点时显示的颜色,没有背景图片时使用
void* hasFocusImage; //有焦点时显示的图片,使用 JPEG 图片,不用时请填入 NULL
void* noFocusImage; //无焦点时显示的图片,使用 JPEG 图片,不用时请填入 NULL
}StructLineEdit;
API 函数设计
行输入控件所需要的 API 接口函数比较多,具体如下所示。通过 LineEditScan 函数扫描行输入控件时,如果检测到控件有被按下,那么控件将自动获得焦点并更新状态。对于多个行输入控件,通常只有一个拥有焦点,所以需要用 LineEditRemoveFocus 函数清除行输入控件的焦点状态。
void CreateLineEdit(StructLineEdit* widget); //创建行编辑控件
u32 LineEditScan(StructLineEdit* widget); //行编辑控件扫描
void LineEditRemoveFocus(StructLineEdit* widget); //取消行编辑控件的焦点
void LineEditAdd(StructLineEdit* widget, char code); //行编辑控件添加一个字符
void LineEditDelete(StructLineEdit* widget); //删除一个字符
驱动源文件包含的头文件
驱动文件需要包含的头文件如下所示,为了美观,我们可以用 JPEG 图片来装饰行输入控件。
#include "LineEdit.h"
#include "LCD.h"
#include "Touch.h"
#include "JPEG.h"
#include "stdio.h"
创建控件函数
在创建控件函数中,我们要设置行输入控件的初始状态,同时还要为行输入控件的结果输入准备一个缓冲区,用于储存用户输入,具体如下所示。一般行输入控件都会有黑色或白色边框,为了显示的字符不会覆盖到边框,本驱动中行输入的首位都预留了一个空格的位置。
void CreateLineEdit(StructLineEdit* widget)
{
//行输入控件能容纳的最大字符数量
unsigned int maxStringLen;
//计算行编辑控件最大字符串长度
maxStringLen = widget->width / (widget->textSize / 2);
//为字符串缓冲区申请动态内存
widget->text = (char*)malloc(maxStringLen + 1);
if(NULL == widget->text)
{
printf("Fail to malloc\r\n");
while(1){}
}
//设定第一个字符为空格
widget->text[0] = ' ';
widget->text[1] = 0;
//默认无焦点
widget->focus = 0;
//更新显示
UpdateShow(widget);
}
刷新显示函数
上述代码中的 UpdateShow 函数用于刷新行输入控件的显示,可以做为一个内部函数,如下所示。UpdateShow 函数首先刷新背景,可以是刷图或矩形填充。因为行输入控件通常是矩形的,为了节省内存空间,在这里将行输入控件的图片类型定为 JPEG 图片。刷新完背景后,可以按照纵坐标居中的方式打印行输入控件输入结果。
static void UpdateShow(StructLineEdit* widget)
{
unsigned int x0, y0, x1, y1, i, x, y;
//计算计算控件坐标范围
x0 = widget->x;
y0 = widget->y;
x1 = widget->x + widget->width - 1;
y1 = widget->y + widget->height - 1;
//有焦点时刷新背景
if(1 == widget->focus)
{
if(NULL != widget->hasFocusImage)
{
DrawJPEG(widget->hasFocusImage, widget->x, widget->y);
}
else
{
LCDFill(x0, y0, x1, y1, widget->hasFocusColor);
}
}
//无焦点时刷新背景
else
{
if(NULL != widget->noFocusImage)
{
DrawJPEG(widget->noFocusImage, widget->x, widget->y);
}
else
{
LCDFill(x0, y0, x1, y1, widget->noFocusColor);
}
}
//计算字符串起点的横坐标和纵坐标
x0 = widget->x;
y0 = widget->y + ((widget->height - widget->textSize) / 2);
//显示字符串
i = 0;
x = x0;
y = y0;
while(0 != widget->text[i])
{
LCDShowChar(x, y, widget->text[i], widget->textSize, 1);
x = x + (widget->textSize / 2);
i++;
}
}
扫描函数
行输入控件的扫描函数如下所示,一旦确定行输入按键有被按下,就可以直接将焦点标志位设为 1 ,最后调用 UpdateShow 更新显示即可。这里的返回值只是为了通知用户行输入控件是否有状态改变,用户可以据此解除其它行输入控件的焦点状态。
u32 LineEditScan(StructLineEdit* widget)
{
unsigned int x0, y0, x1, y1;
unsigned short tx, ty;
//已经获得焦点,直接返回
if(0 != widget->focus)
{
return 0;
}
//计算控件坐标范围
x0 = widget->x;
y0 = widget->y;
x1 = widget->x + widget->width - 1;
y1 = widget->y + widget->height - 1;
//获取触屏扫描结果
if(0 != ScanTouch(&tx, &ty))
{
//检测到控件被按下
if((tx >= x0) && (tx <= x1) && (ty >= y0) && (ty <= y1))
{
//标记获得了焦点
widget->focus = 1;
//刷新显示
UpdateShow(widget);
//返回状态改变了
return 1;
}
}
//返回状态未改变
return 0;
}
清除焦点函数
清除行输入控件的焦点状态更为简单,只需要清空标志位,然后刷新显示即可。
void LineEditRemoveFocus(StructLineEdit* widget)
{
//当前并未获得焦点,直接返回
if(0 == widget->focus)
{
return;
}
//标记未获得焦点
widget->focus = 0;
//刷新显示
UpdateShow(widget);
}
输入函数
LineEditAdd 函数用于向行输入控件添加一个输入,具体定义如下所示。需要注意:行输入控件的首位两端需要预留至少一个空格的空白区域,以免字符显示覆盖到了边框。
void LineEditAdd(StructLineEdit* widget, char code)
{
unsigned int maxStringLen, stringLen;
//当前并未获得焦点,直接返回
if(0 == widget->focus)
{
return;
}
//非法字符,直接返回
if(code < ' ')
{
return;
}
//计算行编辑控件最大字符串长度
maxStringLen = widget->width / (widget->textSize / 2);
maxStringLen = maxStringLen - 1;
//统计当前字符串长度
stringLen = 0;
while(0 != widget->text[stringLen])
{
stringLen++;
}
//字符串长度大于等于最大字符串长度,直接返回
if(stringLen >= maxStringLen)
{
return;
}
//将字符添加到字符串末尾
widget->text[stringLen] = code;
widget->text[stringLen + 1] = 0;
//刷新显示
UpdateShow(widget);
}
删除函数
LineEditDelete 函数用于从末尾删除行输入控件的输入结果,具体定义如下所示。在这里要注意不要误删了最前边预留的空格。
void LineEditDelete(StructLineEdit* widget)
{
unsigned int stringLen;
//当前并未获得焦点,直接返回
if(0 == widget->focus)
{
return;
}
//统计字符串长度
stringLen = 0;
while(0 != widget->text[stringLen])
{
stringLen++;
}
//只有一个字符,直接返回,用于保护最前边的空格
if(1 == stringLen)
{
return;
}
//删除末尾的字符
widget->text[stringLen - 1] = 0;
//刷新显示
UpdateShow(widget);
}
测试代码
行输入控件的测试代码如下,因为这里所有的控件都需要做触屏扫描,以前的触屏扫描函数每调用一次都会触发 IIC 传输,效率极低,所以代码工程中做了优化,新添了触屏扫描任务,用于每隔一段时间做一次触屏扫描。
#include "Button.h"
#include "LineEdit.h"
int main(void)
{
//行输入控件
static StructLineEdit s_structLineEdit1;
static StructLineEdit s_structLineEdit2;
//按键控件
static StructButton s_structButton1Widget;
static StructButton s_structButton2Widget;
static StructButton s_structButton3Widget;
static StructButton s_structButton4Widget;
//初始化
InitHardware(); //初始化硬件相关
InitSoftware(); //初始化软件相关
//设置 LCD 初始状态
LCDDisplayDir(1);
LCDClear(WHITE);
//创建行输入 1
s_structLineEdit1.x = 150;
s_structLineEdit1.y = 150;
s_structLineEdit1.width = 475;
s_structLineEdit1.height = 50;
s_structLineEdit1.textColor = WHITE;
s_structLineEdit1.textSize = 24;
s_structLineEdit1.hasFocusColor = GREEN;
s_structLineEdit1.noFocusColor = BLUE;
s_structLineEdit1.hasFocusImage = NULL;
s_structLineEdit1.noFocusImage = NULL;
CreateLineEdit(&s_structLineEdit1);
//创建行输入 2
s_structLineEdit2.x = s_structLineEdit1.x;
s_structLineEdit2.y = s_structLineEdit1.y + s_structLineEdit1.height + 25;
s_structLineEdit2.width = s_structLineEdit1.width;
s_structLineEdit2.height = s_structLineEdit1.height;
s_structLineEdit2.textColor = s_structLineEdit1.textColor;
s_structLineEdit2.textSize = s_structLineEdit1.textSize;
s_structLineEdit2.hasFocusColor = s_structLineEdit1.hasFocusColor;
s_structLineEdit2.noFocusColor = s_structLineEdit1.noFocusColor;
s_structLineEdit2.hasFocusImage = s_structLineEdit1.hasFocusImage;
s_structLineEdit2.noFocusImage = s_structLineEdit1.noFocusImage;
CreateLineEdit(&s_structLineEdit2);
//按键 1 初始化
s_structButton1Widget.x = 150;
s_structButton1Widget.y = 340;
s_structButton1Widget.width = 100;
s_structButton1Widget.height = 50;
s_structButton1Widget.text = "1";
s_structButton1Widget.textColor = WHITE;
s_structButton1Widget.textSize = 24;
s_structButton1Widget.pressColor = GREEN;
s_structButton1Widget.releaseColor = BLUE;
s_structButton1Widget.pressImage = NULL;
s_structButton1Widget.releaseImage = NULL;
ButtonCreate(&s_structButton1Widget);
//按键 2 初始化
s_structButton2Widget.x = s_structButton1Widget.x + s_structButton1Widget.width + 25;
s_structButton2Widget.y = s_structButton1Widget.y;
s_structButton2Widget.width = s_structButton1Widget.width;
s_structButton2Widget.height = s_structButton1Widget.height;
s_structButton2Widget.text = "2";
s_structButton2Widget.textColor = s_structButton1Widget.textColor;
s_structButton2Widget.textSize = s_structButton1Widget.textSize;
s_structButton2Widget.pressColor = s_structButton1Widget.pressColor;
s_structButton2Widget.releaseColor = s_structButton1Widget.releaseColor;
s_structButton2Widget.pressImage = s_structButton1Widget.pressImage;
s_structButton2Widget.releaseImage = s_structButton1Widget.releaseImage;
ButtonCreate(&s_structButton2Widget);
//按键 3 初始化
s_structButton3Widget.x = s_structButton2Widget.x + s_structButton2Widget.width + 25;
s_structButton3Widget.y = s_structButton1Widget.y;
s_structButton3Widget.width = s_structButton1Widget.width;
s_structButton3Widget.height = s_structButton1Widget.height;
s_structButton3Widget.text = "3";
s_structButton3Widget.textColor = s_structButton1Widget.textColor;
s_structButton3Widget.textSize = s_structButton1Widget.textSize;
s_structButton3Widget.pressColor = s_structButton1Widget.pressColor;
s_structButton3Widget.releaseColor = s_structButton1Widget.releaseColor;
s_structButton3Widget.pressImage = s_structButton1Widget.pressImage;
s_structButton3Widget.releaseImage = s_structButton1Widget.releaseImage;
ButtonCreate(&s_structButton3Widget);
//按键 BACK 初始化
s_structButton4Widget.x = s_structButton3Widget.x + s_structButton3Widget.width + 25;
s_structButton4Widget.y = s_structButton1Widget.y;
s_structButton4Widget.width = s_structButton1Widget.width;
s_structButton4Widget.height = s_structButton1Widget.height;
s_structButton4Widget.text = "<-";
s_structButton4Widget.textColor = s_structButton1Widget.textColor;
s_structButton4Widget.textSize = s_structButton1Widget.textSize;
s_structButton4Widget.pressColor = s_structButton1Widget.pressColor;
s_structButton4Widget.releaseColor = s_structButton1Widget.releaseColor;
s_structButton4Widget.pressImage = s_structButton1Widget.pressImage;
s_structButton4Widget.releaseImage = s_structButton1Widget.releaseImage;
ButtonCreate(&s_structButton4Widget);
while(1)
{
//LED 闪烁
LEDFlicker(10);
//触屏扫描
TouchScanTask();
//行输入 1 扫描
if(LineEditScan(&s_structLineEdit1))
{
LineEditRemoveFocus(&s_structLineEdit2);
}
//行输入 2 扫描
if(LineEditScan(&s_structLineEdit2))
{
LineEditRemoveFocus(&s_structLineEdit1);
}
//按键 1 扫描
if(ButtonScan(&s_structButton1Widget))
{
LineEditAdd(&s_structLineEdit1, '1');
LineEditAdd(&s_structLineEdit2, '1');
}
//按键 2 扫描
if(ButtonScan(&s_structButton2Widget))
{
LineEditAdd(&s_structLineEdit1, '2');
LineEditAdd(&s_structLineEdit2, '2');
}
//按键 3 扫描
if(ButtonScan(&s_structButton3Widget))
{
LineEditAdd(&s_structLineEdit1, '3');
LineEditAdd(&s_structLineEdit2, '3');
}
//按键 BACK 扫描
if(ButtonScan(&s_structButton4Widget))
{
LineEditDelete(&s_structLineEdit1);
LineEditDelete(&s_structLineEdit2);
}
//延时 50 ms
DelayNms(50);
}
}
实验结果
实验结果如下所示。
-行输入控件-实验结果-20230304-1024x768.jpg)
总结
我们可以用链表的形式组织行输入控件,为行输入控件分组。这样一来,当一个行输入控件获得焦点后,另一个将自动失去焦点,就不用用户自己去管理每个行输入控件的状态了。同时,往行输入控件写入或删除时,可以直接针对控件分组进行。在写入/删除函数中,遍历链表,查找出具有焦点的控件,并更新状态,这样用户就无需多次调用写入/删除函数了。
源码
本章节中的源码请参考《单片机 GUI 设计(零)- 大纲》