代码改变世界

AWTK开发UI简单入门C语言篇

2021-10-13 11:25  dreamboy2000  阅读(1731)  评论(0编辑  收藏  举报

AWTK 开发 UI 简单入门 – C 语言篇

一,前序

  在上一篇教程中(AWTK 的 Window 开发环境安装教程),我们已经配置好 AWTK 的开发环境了,今天我们使用 C 语言写一个简单的小例子,让大家更加容易理解 AWTK 的工作原理。
  在 windows 平台上面开发,马上想到的开发工具就是 vs 了,作为宇宙最强的 IDE,在开发上的便捷性和易用性都是没得说的,虽然我们也可以使用 vscode 作为开发工具,但是为了让大家更加简单的理解 AWTK 这一个 GUI 的用法,所有我决定采用 vs 作为开发工具,并且不会采用 scons 来生成项目,尽量简单化一点,让大家看的明白。(毕竟在 windows 上开发,应该大部分人都会用 vs 这个 IDE 的吧)
  本章节中,采用的代码为 ZLG 提供的 HelloWorld-Demo 项目为原型来介绍如果做一个简单的 GUI,其界面为下图:
在这里插入图片描述

备注:

  1. 虽然本文采用 ZLG 提供的 HelloWorld-Demo 项目为原型来介绍,但UI 界面只是大致一样,同时为了更好的让读者了解,所以其代码会修改过,其目的是尽可能的使用最简单的代码和逻辑带读者入门。
  2. 附上 ZLG 提供 HelloWorld-Demo 项目的 github 地址:https://github.com/zlgopen/awtk-examples
  3. 附上本人修改后的 HelloWorld-Demo 项目的 github 地址:https://github.com/WNsACE/CSDN_AWTK_DEMO

二,建立项目

  本章节采用 vs2017 作为 IDE,所以下面的截图都是 vs2017 的界面,其他版本的 vs,其实都差不多。
  在例子中出现的 “D:\OpenLibraries\awtk\awtk” 为 AWTK 源码的路径,需要根据具体情况来对应修改 电脑上面的 AWTK 源码路径。

1.创建空项目

  使用 vs2017 创建一个新的 c++ 空项目,并修改名字为HelloWorld-Demo,如下图:
在这里插入图片描述

2.配置项目

  1. 把平台改为x64,如下图:
    在这里插入图片描述

备注:因为 AWTK 默认编译为 64 位的类库。

  1. 给项目新建两个 .c 文件,分别名为 app_main.c 和 window_main.c。

备注:这两个 .c 文件是空文件,没有任何东西的。

  1. 给项目加入 AWTK 相关的头文件,如下图:
    在这里插入图片描述

头文件路径为:

  1. D:\OpenLibraries\awtk\awtk\src
  2. D:\OpenLibraries\awtk\awtk\src\ext_widgets;
  1. 给项目加入对应的宏,如下图:
    在这里插入图片描述

这里主要是加入的宏分别是: WIN32 。

  1. 给项目加入 AWTK 相关类库,如下图:
    在这里插入图片描述在这里插入图片描述

类库路径:D:\OpenLibraries\awtk\awtk\lib;
类库名字:assets.lib;awtk.lib;base.lib;glad.lib;gpinyin.lib;linebreak.lib;nanovg.lib;SDL2.lib;tkc.lib;widgets.lib;winmm.lib;imm32.lib;version.lib;
备注:这里先不解释各个类库的作用,留到后面再讲,而这里的类库只是加入最基础的只是可以让本demo跑起来的最少类库。

  1. 其中 AWTK 的类库为:
    assets.lib,awtk.lib,base.lib,glad.lib,gpinyin.lib,linebreak.lib,nanovg.lib,SDL2.lib,tkc.lib,widgets.lib。
  2. 系统类库为:winmm.lib,imm32.lib,version.lib。

3.部署资源

  由于本项目中只用到很少的资源,只需要把 AWTK 的少量资源拷贝过来就可以了,本文暂时不介绍如何配置资源和生成资源。
  在这个 demo 中主要是资源分别是字体资源和风格资源,风格资源是必须要的(每一个 AWTK 的项目都必须要有一个 default 风格),而字体资源的话,如果项目中需要显示文字的话,则需要增加字体资源,否则可以不需要,接下来把 AWTK 源码中的资源直接拷贝过来。

  1. 把在程序目录下创建 res 的文件夹,如下图。
    在这里插入图片描述

备注:为了让代码结构好看一点,所以讲上面创建的 app_main.c 和 window_main.c 放到 src 文件夹中。

  1. 把 D:\OpenLibraries\awtk\awtk\demos 目录下的 assets 文件夹拷贝到刚刚创建的 res 的文件夹中,如下图:
    在这里插入图片描述
  2. 把 res 文件夹下多余用不到的文件删除。(这一步其实不做也是无所谓的,只不过为了让后面大家更好理解而已)

需要删除的文件分别是:( ./表示 HelloWorld-Demo 项目路径)

  1. ./res/assets/dark (文件夹)
  2. ./res/assets/README.md (文件)
  3. ./res/assets/default/inc (文件夹)
  4. ./res/assets/default/raw/data (文件夹)
  5. ./res/assets/default/raw/images (文件夹)
  6. ./res/assets/default/raw/scripts (文件夹)
  7. ./res/assets/default/raw/strings (文件夹)
  8. ./res/assets/default/raw/ui (文件夹)
  9. ./res/assets/default/raw/xml (文件夹)
  10. ./res/assets/default/raw/fonts/ap.ttf (文件)
  11. ./res/assets/default/raw/fonts/default_full.ttf (文件)
  12. ./res/assets/default/raw/fonts/README.md (文件)
  13. ./res/assets/default/raw/fonts/text.txt (文件)

注意:在 ./res/assets/default/raw/styles文件夹下,除了 default.bin 和 default.xml 两个文件以外全部删除。

三,编写项目

1. 打开 app_main.c 文件,并写入下面的代码:

 1 #include "awtk.h"
 2 
 3 extern ret_t application_init(void);
 4 
 5 int main(void)
 6 {
 7     int lcd_w = 800;
 8     int lcd_h = 480;
 9 
10     /* 
11     * 初始化 AWTK
12     * 参数 APP_DESKTOP 为设置 window 的桌面模式 
13     * 参数 "res" 为设置资源目录路径为程序工作目录下"res"
14     */
15     tk_init(lcd_w, lcd_h, APP_DESKTOP, NULL, "res");
16 
17     /* 预加载名为 default.tff 的字体资源 */
18     assets_manager_preload(assets_manager(), ASSET_TYPE_FONT, "default");
19     /* 预加载名为 default.bin 的风格资源 */
20     assets_manager_preload(assets_manager(), ASSET_TYPE_STYLE, "default");
21 
22     /* 初始化资源 */
23     tk_init_assets();
24 
25     /* 打开主屏幕 */
26     application_init();
27 
28     /* 进入awtk事件循环 */
29     tk_run();
30 
31     return 0;
32 }

 

2. 打开 window_main.c 文件,并写入下面的代码:

 1 #include "awtk.h"
 2 #include "awtk.h"
 3 extern ret_t application_init(void);
 4 
 5 widget_t* label_4_btn = NULL;    //递增数值label控件指针
 6 widget_t* label_4_edit = NULL;    //显示文本框label控件指针
 7 
 8 /**
 9  * Label文本的数值 + offset
10  */
11 static ret_t label_add(widget_t* label, int32_t offset)
12 {
13     if (label)
14     {
15         int32_t val = 0;
16         if (wstr_to_int(&(label->text), &val) == RET_OK)
17         {
18             char text[32];
19             val += offset;
20             val = tk_max(-200, tk_min(val, 200));
21             tk_snprintf(text, sizeof(text), "%d", val);
22             widget_set_text_utf8(label, text);
23 
24             return RET_OK;
25         }
26     }
27 
28     return RET_FAIL;
29 }
30 
31 /**
32  * 递增按钮事件
33  */
34 static ret_t on_inc_click(void* ctx, event_t* e)
35 {
36     label_add(label_4_btn, 1);
37 
38     return RET_OK;
39 }
40 
41 /**
42  * 递减按钮事件
43  */
44 static ret_t on_dec_click(void* ctx, event_t* e)
45 {
46     label_add(label_4_btn, -1);
47 
48     return RET_OK;
49 }
50 
51 /**
52  * 正在编辑事件
53  */
54 static ret_t on_changing(void* ctx, event_t* evt)
55 {
56     widget_t* target = WIDGET(evt->target);
57     widget_set_text(label_4_edit, target->text.str);
58 
59     return RET_OK;
60 }
61 
62 /**
63  * 初始化
64  */
65 ret_t application_init(void)
66 {
67     widget_t* win = window_create(NULL, 0, 0, 0, 0);
68 
69     /* 创建文本框*/
70     label_4_edit = label_create(win, 160, 96, 480, 40);
71     widget_set_text(label_4_edit, L"hello world");
72     widget_set_name(label_4_edit, "label_4_edit");
73 
74     /* 创建编辑框 */
75     widget_t* edit = edit_create(win, 160, 196, 480, 40);
76     edit_set_input_type(edit, INPUT_TEXT);
77     widget_set_text(edit, L"hello world");
78     widget_on(edit, EVT_VALUE_CHANGING, on_changing, NULL);
79 
80     /* 创建递减按钮 */
81     widget_t* dec_btn = button_create(win, 160, 288, 160, 40);
82     widget_set_text(dec_btn, L"dec");
83     widget_on(dec_btn, EVT_CLICK, on_dec_click, NULL);
84 
85     /* 创建label显示递增数值 */
86     label_4_btn = label_create(win, 320, 288, 160, 40);
87     widget_set_text(label_4_btn, L"88");
88     widget_set_name(label_4_btn, "label_4_btn");
89 
90     /* 创建递增按钮 */
91     widget_t* inc_btn = button_create(win, 480, 288, 160, 40);
92     widget_set_text(inc_btn, L"inc");
93     widget_on(inc_btn, EVT_CLICK, on_inc_click, NULL);
94 
95     return RET_OK;
96 }

四,分析代码

  其实把上面代码拷贝到文件中,点击编译和运行就可以看到本文一开始的 UI 效果图。
  但是大部分人都想知道为啥,其实每一行的代码都是代表着什么意思呢?所以这一环节就是配合着上面的代码注释来解释关键性代码的作用。

1. app_main.c 文件的 tk_init 函数

 1 /**
 2  * @method tk_init
 3  * 初始化TK。
 4  * @alias init
 5  * @annotation ["static", "scriptable"]
 6  * @param {wh_t} w LCD宽度。
 7  * @param {wh_t} h LCD高度。
 8  * @param {app_type_t} app_type 应用程序的类型。
 9  * @param {const char*} app_name 应用程序的名称(必须为常量字符串)。
10  * @param {const char*} app_root 应用程序的根目录,用于定位资源文件(必须为常量字符串)。
11  *
12  * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
13  */
14 ret_t tk_init(wh_t w, wh_t h, app_type_t app_type, const char* app_name, const char* app_root);
 看到上面的注释,我想大家都应该明白了,每个 AWTK 的程序都必须最先调用这个函数,包括嵌入式平台也是,这个函数会初始化平台信息,创建主循环,初始化各种控件创建信息等。

   如果在嵌入式平台中,LCD 的屏幕宽高就是这里的 LCD 宽高。

2. app_main.c 文件的 assets_manager_preload 函数

 1 /**
 2  * @method assets_manager_preload
 3  * 从文件系统中加载指定的资源,并缓存到内存中。在定义了宏WITH\_FS\_RES时才生效。
 4  * @param {assets_manager_t*} am asset manager对象。
 5  * @param {asset_type_t} type 资源的类型。
 6  * @param {char*} name 资源的名称。
 7  *
 8  * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
 9  */
10 ret_t assets_manager_preload(assets_manager_t* am, asset_type_t type, const char* name);
 这个函数是先把资源加载到资源列表中,主要是加载默认字体和默认风格,然后等待 tk_init_assets 函数的调用,把默认的字体和风格挂载到对应的地方。

3. app_main.c 文件的 tk_init_assets 函数

  

1 /**
2  * @method tk_init_assets
3  * 初始化资源。
4  * @annotation ["private"]
5  *
6  * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
7  */
8 ret_t tk_init_assets(void);

  

这个函数主要是把默认字体和默认风格挂载到 AWTK 的 主题上面和字体管理上面,如果没有这两步的话,程序可能会空白一片,没有任何东西显示出来。

4. app_main.c 文件的 tk_run 函数

1 /**
2  * @method tk_run
3  * 进入TK事件主循环。
4  * @alias run
5  * @annotation ["static", "scriptable"]
6  *
7  * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
8  */
9 ret_t tk_run(void);
 这个函数内部是一个 UI 的主循环,不断地绘制和触发以及接受各种事件,AWTK 所有的函数触发都是发生在 tk_run 函数中。

5. window_main.c 文件的 window_create 函数

/**
 * @method window_create
 * 创建window对象
 * @annotation ["constructor", "scriptable"]
 * @param {widget_t*} parent 父控件
 * @param {xy_t} x x坐标
 * @param {xy_t} y y坐标
 * @param {wh_t} w 宽度
 * @param {wh_t} h 高度
 *
 * @return {widget_t*} 对象。
 */
widget_t* window_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h);
 这个函数是用来创建一个可视化的窗口,这个可视化的窗口风格类型为 window_t,目前 AWTK 创建的窗口都是全屏的,所以不需要写入 x,y,w,h,同时因为窗口在创建的时候会默认加入 window_manager(窗口管理器)中,所以也不需要写父控件。

注意:在AWTK 中,必须要有一个可视化的窗口,否则画面不会刷新。

6. window_main.c 文件的 xxxxx_create 函数

 1 /**
 2  * 创建xxxxx控件对象
 3  * @param {widget_t*} parent 父控件
 4  * @param {xy_t} x x坐标
 5  * @param {xy_t} y y坐标
 6  * @param {wh_t} w 宽度
 7  * @param {wh_t} h 高度
 8  *
 9  * @return {widget_t*} 对象。
10  */
11 widget_t* xxxxx_create(widget_t* parent, xy_t x, xy_t y, wh_t w, wh_t h);
  这里 xxxxx_create 函数是泛指所有的控件创建函数,AWTK 大部分的控件创建函数都是这样子写,只是控件名字会代替上面的 xxxxx 就是该控件的创建函数。

   AWTK 采用树的结构,最顶级是窗口管理器(window_manager)单例,其子集为窗口对象,窗口对象的子集为各个控件,其中每个控件都可以作为其他控件的父集,从而构成一颗 AWTK 控件大树,如下图:
在这里插入图片描述

备注:

  1. 当父集被删除后,其子集也会被删除。
  2. AWTK 的坐标系是左上角为(0,0),从左上角到右下角,x 和 y 的值越来越大。
  3. 在 awtk\src\widgets 和 awtk\src\ext_widgets 文件夹下放在各种各样的控件,有兴趣的朋友可以去看一下。

7. window_main.c 文件的 widget_set_text 函数

/**
 * @method widget_set_text
 * 设置控件的文本。
 * 只是对widget\_set\_prop的包装,文本的意义由子类控件决定。
 * @param {widget_t*} widget 控件对象。
 * @param {const wchar_t*}  text 文本。
 *
 * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
 */
ret_t widget_set_text(widget_t* widget, const wchar_t* text);
 该函数主要是用来设置控件的文本,因为每一个控件都会有自己的文本,但是只有部分控件会自动显示其文本,显示文本的常见控件有:button,label,edit,check_button等。

备注:widget_set_text 函数和 widget_set_text_utf8 函数一样的函数,只不过是传入的字符串类型不一样而已。

8. window_main.c 文件的 widget_set_name 函数

/**
 * @method widget_set_name
 * 设置控件的名称。
 * @annotation ["scriptable"]
 * @param {widget_t*} widget 控件对象。
 * @param {char*} name 名称。
 *
 * @return {ret_t} 返回RET_OK表示成功,否则表示失败。
 */
ret_t widget_set_name(widget_t* widget, const char* name);
 该函数主要是设置控件的名字,主要是配合查找控件的方法使用,如果不需要查找控件的话,控件的名字有没有都无所谓。

9. window_main.c 文件的 widget_on 函数

 1 /*回调事件处理函数原型*/
 2 typedef ret_t (*event_func_t)(void* ctx, event_t* e);
 3 
 4 /**
 5  * @method widget_on
 6  * 注册指定事件的处理函数。
 7  * @annotation ["scriptable:custom"]
 8  * @param {widget_t*} widget 控件对象。
 9  * @param {event_type_t} type 事件类型。
10  * @param {event_func_t} on_event 事件处理函数。
11  * @param {void*} ctx 事件处理函数上下文。
12  * 
13  * @return {int32_t} 返回id,用于widget_off。
14  */
15 int32_t widget_on(widget_t* widget, uint32_t type, event_func_t on_event, void* ctx);
 该函数主要是设置事件回调函数,widget_on 函数是一个很重要的函数,后面会经常使用的来设置各种事件的触发回调函数,例如常见的事件类型有:鼠标点击事件(EVT_CLICK),键盘按下事件(EVT_KEY_DOWN),长按按钮事件(EVT_LONG_PRESS)等等,具体可以查 awtk\src\tkc\event.h 中的事件枚举。

 例如当用户注册了鼠标点击事件的回调函数后,如下代码把 on_dec_click 函数注册为 dec 按钮的点击回调函数,当鼠标点击这个 dec 按钮后,就会触发 on_dec_click 函数,同时会把 widget_on 函数的第四个参数(下面的代码的第四个参数是设置 NULL)作为 on_dec_click 函数的第一个参数传入到 on_dec_click 函数中。

1 /* 创建递减按钮 */
2 widget_t* dec_btn = button_create(win, 160, 288, 160, 40);
3 widget_set_text(dec_btn, L"dec");
4 widget_on(dec_btn, EVT_CLICK, on_dec_click, NULL);

五,总结

   本文介绍的是采用 C 语言直接简单 UI demo,希望大家可以看完后可以自己去写一个简单的 demo,因为 AWTK 支持采用 XML 来表述 UI 界面,所以在下一章节会用 XML 来写和本文相同的 UI demo。