跳到主要内容
版本:3.x

快速入门

欢迎来到 LCUI 文档!本章节将介绍一些基础概念和用法,以帮助你快速入门。

备注

你将会学习到:

  • 如何创建应用程序
  • 如何创建和嵌套组件
  • 如何创建自定义组件
  • 如何设置工作目录以让程序正确加载文件
  • 如何调整布局
  • 如何使用 XML 描述界面
  • 如何对事件做出响应并更新界面

在你开始之前

本文档面向有开发经验的中级开发者,且具备以下条件:

  • 熟练掌握 C 语言及构建工具链,能够解决常见的编译问题。
  • 熟悉至少一个 GUI 开发库/框架,理解 GUI 应用程序工作原理。
  • 了解 Web 开发技术,包括 HTML、CSS。

安装 LCUI

阅读《安装》文档。

创建应用程序

创建 main.c 文件,写入如下代码:

src/main.c
#include <LCUI.h>
#include <LCUI/main.h>

int main(int argc, char *argv[])
{
lcui_init();
return lcui_main();
}

这段代码比较简单,主要是包含头文件和调用函数,其作用具体如下:

  • LCUI.h 是 LCUI 库的头文件。
  • LCUI/main.h 是则是程序主入口的头文件,仅需由 main 函数所在的源文件包含,它封装了各个平台的程序入口代码,使得 LCUI 应用程序能够统一用 main 函数作为入口。
  • 调用 lcui_init() 初始化 LCUI 库的各项功能。
  • 调用 lcui_main() 进入主循环,让应用程序保持运行状态并能够响应用户操作。

创建和嵌套组件

LCUI 应用程序的用户界面是由组件组成的。组件拥有自己的逻辑和外观,可以小到一个按钮,也可以大到整个页面。

接下来,我们将使用 text 组件来显示一段文本:

src/main.c
#include <LCUI.h>
#include <LCUI/main.h>

int main(int argc, char *argv[])
{
lcui_init();

ui_widget_t *text = ui_create_widget("text");
ui_text_set_content(text, "Welcome to my app");
ui_root_append(text);

return lcui_main();
}

在这段代码中我们创建了一个 text 类型的组件,将它的内容设置为 "Welcome to my app",然后追加到根组件中。

运行命令 xmake && xmake run 重新构建和运行它,你会看到这样的窗口:

logoLCUI Display
Welcome to my app
备注

在默认的显示模式下,根组件与主窗口绑定,它的尺寸、内容都会同步到主窗口,这意味着将组件追加到根组件内就能让它在窗口中显示。

创建自定义组件

LCUI 的组件基于原型来实现组件的抽象和继承,组件原型记录了组件的创建、销毁、绘制、估算尺寸等方法,通过创建组件原型然后修改这些方法即可创建你的自定义组件。

我们将添加一个名为 my-button 的自定义组件,它的表现形式是按钮,初始内容是 I'm a button

首先定义原型指针和注册函数:

src/main.c
ui_widget_prototype_t *my_button_proto;

void register_my_button(void)
{
my_button_proto = ui_create_widget_prototype("my-button", "text");
my_button_proto->init = my_button_init;
}

ui_create_widget_prototype() 用于创建组件原型,它参数分别是组件的类名和基类名。因为 my-button 组件需要用到文本显示能力,所以将第二个参数设置为 "text" 以继承 text 组件。

然后定义初始化函数,在里面初始化基组件和自身内容:

src/main.c
void my_button_init(ui_widget_t *w)
{
my_button_proto->proto->init(w);
ui_text_set_content(w, "I'm a button");
}

最后,在主函数中注册和使用我们的自定义组件:

src/main.c
#include <LCUI.h>
#include <LCUI/main.h>

ui_widget_prototype_t *my_button_proto;

void my_button_init(ui_widget_t *w)
{
my_button_proto->proto->init(w);
ui_text_set_content(w, "I'm a button");
}

void register_my_button(void)
{
my_button_proto = ui_create_widget_prototype("my-button", "button");
my_button_proto->init = my_button_init;
}

int main(int argc, char *argv[])
{
lcui_init();
register_my_button();

ui_widget_t *text = ui_create_widget("text");
ui_text_set_content(text, "Welcome to my app");
ui_root_append(text);
ui_root_append(ui_create_widget("my-button"));

return lcui_main();
}

重新构建和运行它,你会看到如下的效果:

logoLCUI Display
Welcome to my app
I'm a button

设置工作目录

接下来要让程序从外部加载资源文件。在代码中加载文件时指定的文件路径是相对路径,相对于工作目录,且会因程序的启动方式的不同而有所差异,例如:用 xmake run 命令启动、双击程序的可执行文件启动。因此,在加载文件前需手动设置工作目录。

在下面的例子中我们将 app 目录作为工作目录,需要用到 set_rundir() 函数:

xmake.lua
add_rules("mode.debug", "mode.release")
includes("vendor/LCUI/xmake.lua")

target("myapp")
set_kind("binary")
add_deps("lcui")
add_files("src/*.c")
set_rundir("app")
注意

这项配置只影响到以 xmake run 命令启动程序时的工作目录,你还需要额外编写代码根据 main() 函数的 argv 参数设置工作目录。

添加样式

LCUI 包含 CSS 解析和选择引擎,你可以使用一些简单的 CSS 规则来设置用户界面的视觉效果。

你将学会使用这几个函数:

  • ui_load_css_file():从文件中加载 CSS。
  • ui_load_css_string():从字符串中加载 CSS。
  • ui_widget_add_class():为组件设置类名,以应用对应类的 CSS 样式。

首先在工作目录内创建一个 css 文件,填入以下 CSS 规则:

app/main.css
root {
padding: 24px;
}

.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
}

my-button {
display: inline-block;
padding: 4px 8px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f4f4f4;
}

my-button:hover {
background: #eaeaea;
}

my-button:active {
background: #fbfbfb;
}

然后在 main() 函数中加载 CSS,再为 text 组件添加 title 类名。

src/main.c
int main(int argc, char *argv[])
{
lcui_init();
register_my_button();

ui_load_css_file("main.css");

ui_widget_t *text = ui_create_widget("text");
ui_text_set_content(text, "Welcome to my app");
ui_widget_add_class(text, "title");
ui_root_append(text);
ui_root_append(ui_create_widget("my-button"));

return lcui_main();
}

效果如下所示:

logoLCUI Display

Welcome to my app

除了从文件中加载 CSS,你还可以从字符串中加载 CSS:

src/main.c
const char *css_str = "\
root {\
padding: 24px;\
}\
\
.title {\
font-size: 24px;\
font-weight: bold;\
margin-bottom: 16px;\
}\
my-button {\
padding: 4px 8px;\
border: 1px solid #ddd;\
border-radius: 4px;\
background: #f4f4f4;\
}\
my-button:hover {\
background: #eaeaea;\
}\
my-button:active {\
background: #fbfbfb;\
}";

int main(int argc, char *argv[])
{
lcui_init();
register_my_button();

ui_load_css_string(css_str, __FILE__);

ui_widget_t *text = ui_create_widget("text");
ui_text_set_content(text, "Welcome to my app");
ui_widget_add_class(text, "title");
ui_root_append(text);
ui_root_append(ui_create_widget("my-button"));

return lcui_main();
}

调整布局

LCUI 支持流式布局(Flow Layout)和弹性盒子布局(Flexible Box)两种布局,用法和效果与网页的布局相似,你可通过设置组件的 CSS display 属性来更改其布局行为。

以常见的垂直水平居中布局为例,我们可以使用弹性盒子布局来实现:

app/main.css
root {
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
}
logoLCUI Display

Welcome to my app

使用 XML 描述界面

使用 C 语言以命令式编写用户界面虽然具有很高的灵活性,但当界面变得复杂时,可读性会随着函数调用和参数的数量增多而降低,界面的结构也变得难以理解。相比之下,使用 XML 声明用户界面则能更直观地表达界面结构、各个组件的属性和嵌套关系。

XML 写法如下:

app/main.xml
<?xml version="1.0" encoding="UTF-8" ?>
<lcui-app>
<resource type="text/css" src="main.css"/>
<ui>
<text class="title">
Welcome to my app
</text>
<my-button />
</ui>
</lcui-app>

resource 标签用于引入包括 CSS、字体在内的资源文件。ui 标签则用于声明用户界面内的所有组件。

main() 函数中调用 ui_load_xml_file() 函数加载这个 XML 文件:

src/main.c
int main(int argc, char *argv[])
{
lcui_init();
register_my_button();

ui_widget_t *result = ui_load_xml_file("main.xml");
if (result == NULL) {
return -1;
}
ui_root_append(result);
ui_widget_unwrap(result);
return lcui_main();
}

ui_load_xml_file() 函数返回一个组件,包含 ui 标签内声明的所有组件,你需要将它追加到目标容器(根组件)内再调用 ui_widget_unwrap() 函数将内部的子组件展开到外层。

警告

libxml2 库的体积较大,我们计划在 LCUI 未来的版本中移除对该库的依赖。

备注

推荐使用 @lcui/cli 工具将 xml 文件编译为 C 源码。

响应事件

LCUI 的组件是通过事件来实现与用户交互的。接下来让我们给应用程序添加一个交互效果,在按钮被点击后更新显示的文本。你将学会:

  • 定义事件处理函数: 事件处理函数接受三个参数,分别是绑定事件时的组件、事件对象、其它参数。
  • 绑定事件: 需要用到 ui_widget_on() 函数,它能将事件处理函数与事件绑定,并在事件触发时调用事件处理函数。它接受四个参数,分别是组件、事件名称、事件处理函数、自定义数据。

首先,修改 XML 文件,为需要操作的组件添加 id 属性,以便于在代码中访问它们。

main.xml
<?xml version="1.0" encoding="UTF-8" ?>
<lcui-app>
<resource type="text/css" src="main.css"/>
<ui>
<text class="title" id="title">
Welcome to my app
</text>
<my-button id="btn" />
</ui>
</lcui-app>

然后,添加点击(Click)事件处理函数和事件绑定。

src/main.c
void handle_my_button_click(ui_widget_t *w, ui_event_t *e, void *arg)
{
ui_text_set_content(e->data, "You clicked my button!");
}

int main(int argc, char *argv[])
{
lcui_init();
register_my_button();

ui_widget_t *result = ui_load_xml_file("main.xml");
if (result == NULL) {
return -1;
}
ui_root_append(result);
ui_widget_unwrap(result);
ui_widget_on(ui_get_widget("btn"), "click", handle_my_button_click,
ui_get_widget("title"));
return lcui_main();
}
logoLCUI Display
Welcome to my app
备注

你可以通过查看 ui/types.h 头文件里的 ui_event_type_t 的定义来了解其它事件。

更新界面

通常你会希望你的组件 “记住” 一些信息并展示出来,比如一个按钮被点击的次数。要做到这一点,你需要在你的组件中添加私有数据,而这些数据也可称为“状态”。

首先,定义组件状态的数据结构:

src/main.c
typedef struct {
int count;
} my_button_t;

在初始化函数中初始化状态:

src/main.c
void my_button_init(ui_widget_t *w)
{
my_button_proto->proto->init(w);
my_button_t *data;
data = ui_widget_add_data(w, my_button_proto, sizeof(my_button_t));
data->count = 0;
}

然后,定义一个更新函数,用于将状态转变为用户界面上实际要展示内容:

src/main.c
void my_button_update(ui_widget_t *w)
{
char str[64];
my_button_t *data = ui_widget_get_data(w, my_button_proto);
sprintf(str, "Clicked %d times", data->count);
ui_text_set_content(w, str);
}

初始化函数中也需要调用它来更新初始内容:

src/main.c
void my_button_init(ui_widget_t *w)
{
my_button_t *data;

my_button_proto->proto->init(w);
ui_text_set_content(w, "I'm a button");
data = ui_widget_add_data(w, my_button_proto, sizeof(my_button_t));
data->count = 0;
my_button_update(w);
}

之后,添加点击事件的处理函数,在里面更改状态并进行更新:

src/main.c
void my_button_on_click(ui_widget_t *w, ui_event_t *e, void *arg)
{
my_button_t *data;

data = ui_widget_get_data(w, my_button_proto);
data->count += 1;
my_button_update(w);
}

void my_button_init(ui_widget_t *w)
{
my_button_t *data;

data = ui_widget_add_data(w, my_button_proto, sizeof(my_button_t));
data->count = 0;
my_button_update(w);
ui_widget_on(w, "click", my_button_on_click, NULL);
}

最后,删掉 main() 中的事件绑定代码,像这样:

src/main.c
int main(int argc, char *argv[])
{
lcui_init();
register_my_button();

ui_widget_t *result = ui_load_xml_file("main.xml");
if (result == NULL) {
return -1;
}
ui_root_append(result);
ui_widget_unwrap(result);
return lcui_main();
}

你可以在 XML 文件中添加多个 my-button 组件,然后尝试点击不同的按钮:

main.xml
<?xml version="1.0" encoding="UTF-8" ?>
<lcui-app>
<resource type="text/css" src="main.css"/>
<ui>
<text class="title">
Counters that update separately
</text>
<my-button />
<my-button />
</ui>
</lcui-app>
logoLCUI Display
Counters that update separately

你会发现,每个按钮会 “记住” 自己的 count,而不影响其他按钮。

下一节

至此,你已经了解了如何编写 LCUI 应用程序的基本知识。接下来你可以查看教程并把它们付诸实践,用 LCUI 建立第一个迷你应用程序。

问题反馈

在体验过本章节后,你可能会发现一些问题并且产生了相关的想法和建议,比如,你觉得多次定义函数和获取组件数据太麻烦,像下面样将组件的初始化、更新、事件处理都合并进一个函数内应该能解决问题:

void my_button(ui_widget_t *w)
{
char str[64];
my_button_t *data;

data = ui_widget_get_data(my_button_proto);
if (!data) {
// 初始化
data = ui_widget_add_data(w, my_button_proto, sizeof(my_button_t));
data->count = 0;
}
// 事件处理
if (w->event.type == UI_EVENT_CLICK) {
data->count += 1;
}
// 更新
sprintf(str, "Clicked %d times", data->count);
ui_text_set_content(w, str);
}

如果你有更多类似的想法和建议,欢迎到 GiteeGitHub 代码库主页中提交。