一,前序
在上一篇教程中(AWTK 的 Window 開發環境安裝教程),我們已經配置好 AWTK 的開發環境了,今天我們使用 C 語言寫一個簡單的小例子,讓大家更加容易理解 AWTK 的工作原理。
在 windows 平台上面開發,馬上想到的開發工具就是 vs 了,作為宇宙最強的 IDE,在開發上的便捷性和易用性都是沒得說的,雖然我們也可以使用 vscode 作為開發工具,但是為了讓大家更加簡單的理解 AWTK 這一個 GUI 的用法,所有我決定采用 vs 作為開發工具,並且不會采用 scons 來生成項目,盡量簡單化一點,讓大家看的明白。(畢竟在 windows 上開發,應該大部分人都會用 vs 這個 IDE 的吧)
本章節中,采用的代碼為 ZLG 提供的 HelloWorld-Demo 項目為原型來介紹如果做一個簡單的 GUI,其界面為下圖:
備注:
- 雖然本文采用 ZLG 提供的 HelloWorld-Demo 項目為原型來介紹,但UI 界面只是大致一樣,同時為了更好的讓讀者了解,所以其代碼會修改過,其目的是盡可能的使用最簡單的代碼和邏輯帶讀者入門。
- 附上 ZLG 提供 HelloWorld-Demo 項目的 github 地址:https://github.com/zlgopen/awtk-examples
- 附上本人修改后的 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.配置項目
- 把平台改為x64,如下圖:
備注:因為 AWTK 默認編譯為 64 位的類庫。
- 給項目新建兩個 .c 文件,分別名為 app_main.c 和 window_main.c。
備注:這兩個 .c 文件是空文件,沒有任何東西的。
- 給項目加入 AWTK 相關的頭文件,如下圖:
頭文件路徑為:
- D:\OpenLibraries\awtk\awtk\src
- D:\OpenLibraries\awtk\awtk\src\ext_widgets;
- 給項目加入對應的宏,如下圖:
這里主要是加入的宏分別是: WIN32 。
- 給項目加入 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跑起來的最少類庫。
- 其中 AWTK 的類庫為:
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。
3.部署資源
由於本項目中只用到很少的資源,只需要把 AWTK 的少量資源拷貝過來就可以了,本文暫時不介紹如何配置資源和生成資源。
在這個 demo 中主要是資源分別是字體資源和風格資源,風格資源是必須要的(每一個 AWTK 的項目都必須要有一個 default 風格),而字體資源的話,如果項目中需要顯示文字的話,則需要增加字體資源,否則可以不需要,接下來把 AWTK 源碼中的資源直接拷貝過來。
- 把在程序目錄下創建 res 的文件夾,如下圖。
備注:為了讓代碼結構好看一點,所以講上面創建的 app_main.c 和 window_main.c 放到 src 文件夾中。
- 把 D:\OpenLibraries\awtk\awtk\demos 目錄下的 assets 文件夾拷貝到剛剛創建的 res 的文件夾中,如下圖:
- 把 res 文件夾下多余用不到的文件刪除。(這一步其實不做也是無所謂的,只不過為了讓后面大家更好理解而已)
需要刪除的文件分別是:( ./表示 HelloWorld-Demo 項目路徑)
- ./res/assets/dark (文件夾)
- ./res/assets/README.md (文件)
- ./res/assets/default/inc (文件夾)
- ./res/assets/default/raw/data (文件夾)
- ./res/assets/default/raw/images (文件夾)
- ./res/assets/default/raw/scripts (文件夾)
- ./res/assets/default/raw/strings (文件夾)
- ./res/assets/default/raw/ui (文件夾)
- ./res/assets/default/raw/xml (文件夾)
- ./res/assets/default/raw/fonts/ap.ttf (文件)
- ./res/assets/default/raw/fonts/default_full.ttf (文件)
- ./res/assets/default/raw/fonts/README.md (文件)
- ./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 控件大樹,如下圖:
備注:
- 當父集被刪除后,其子集也會被刪除。
- AWTK 的坐標系是左上角為(0,0),從左上角到右下角,x 和 y 的值越來越大。
- 在 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。