控件注册与框架集成
控件注册与框架集成
在控件开发指南中,我们实现了一个 C++ 控件类。但此时它还只是一个普通的 C++ 对象,应用开发者无法在页面代码中直接使用。本文介绍如何将控件注册到框架,使其成为可在应用中使用的组件。
注册控件
控件的注册通过 AppletKit::installWidget<T>() 完成,通常在 AppletKit 实例化之后、第一个应用启动之前调用:
// 初始化流程(主函数或平台启动代码)
Application app;
JsVM vm;
Widget window;
AppletKit kit(&window, "/pkgs.db");
// 注册内置控件(如果未定义 GX_BUILTIN_BINDINGS,需要手动调用)
kit.installBuiltinWidgets();
// 注册自定义控件
kit.installWidget<ProgressRing>();
kit.installWidget<MySpecialChart>();
// 也可以用自定义名称注册(适合和类名有差异的场合)
kit.installWidget<MySpecialChart>("special-chart");
app.exec();
注册后,框架会根据控件类的 GX_OBJECT 元数据,自动提取其属性和信号,使其在应用层可用。
在应用页面中使用
注册成功后,应用开发者可以在页面模板中像使用内置控件那样使用它,标签名即注册时的名称(默认取 GX_OBJECT 声明的类名,首字母小写转短横线形式,或注册时指定的自定义名称):
<!-- 以 progress-ring 组件为例 -->
<progress-ring
class="ring"
value="{{ progress }}"
@completed="onDone">
</progress-ring>
这里的 value 属性对应 GX_PROPERTY(int value, ...) 声明,@completed 对应 Signal<void> completed 信号。框架自动完成 JavaScript 值与 C++ 属性的互相转换,开发者无需写任何"桥接代码"。
属性的自动导出
GX_PROPERTY 声明的属性会被自动导出,规则如下:
- 属性名即框架组件中的属性名,二者直接对应
- 声明了 setter 的属性(
set xxx)可以被应用赋值 - 声明了 getter 的属性(
get xxx)可以被应用读取 - 声明了 signal(
signal xxxChanged)时,属性变化的信号会被传递给绑定
例如以下完整的属性声明:
class ProgressRing : public Widget {
GX_OBJECT
public:
GX_PROPERTY(int value, set setValue, get value, signal valueChanged)
GX_PROPERTY(Color ringColor, set setRingColor, get ringColor)
GX_PROPERTY(bool showLabel, set setShowLabel, get showLabel)
// ...
};
在应用层对应的用法:
<progress-ring
value="{{ jobProgress }}"
ring-color="#409EFF"
show-label="true">
</progress-ring>
属性名由驼峰(camelCase)自动转换为短横线(kebab-case)格式:ringColor → ring-color,showLabel → show-label。这符合前端开发者的习惯。
信号的自动导出
Signal<> 类型的成员变量也会自动导出为组件事件,应用侧用 @信号名 的语法监听:
class ProgressRing : public Widget {
GX_OBJECT
public:
Signal<void> completed; // 进度完成时发射
Signal<int> valueChanged; // 值变化时发射,携带新值
Signal<String> errorOccurred; // 发生错误时,携带错误消息
};
应用侧:
<progress-ring
@completed="handleDone"
@value-changed="handleProgress"
@error-occurred="handleError">
</progress-ring>
信号名同样遵循驼峰转短横线的规则:valueChanged → value-changed。
自定义组件名称
默认情况下,控件以类名注册。如果类名不符合组件命名习惯,可以在注册时指定:
// 将 VendorWaveformGraph 注册为 waveform-graph
kit.installWidget<VendorWaveformGraph>("waveform-graph");
子控件插槽
如果你的控件需要在内部容纳应用定义的子内容(类似 HTML 的 slot),在 C++ 侧继承并实现对应的容器逻辑,然后按正常方式使用子控件即可——框架会自动将应用层声明的子组件创建并挂载到合适的父控件下。
在应用层,就像嵌套子标签一样自然:
<my-container padding="16">
<label text="Hello"/>
<progress-ring value="{{ progress }}"/>
</my-container>
一个完整的例子
以下定义一个简单的数字显示控件,并将其注册为框架组件:
// number_display.h
#pragma once
#include "gx_widget.h"
#include "gx_color.h"
class NumberDisplay : public Widget {
GX_OBJECT
public:
explicit NumberDisplay(Widget *parent = nullptr);
int value() const { return m_value; }
Color textColor() const { return m_color; }
void setValue(int v);
void setTextColor(const Color &c);
GX_PROPERTY(int value, set setValue, get value, signal valueChanged)
GX_PROPERTY(Color textColor, set setTextColor, get textColor)
Signal<int> valueChanged;
protected:
void paintEvent(PaintEvent *event) override;
Size sizeHint() const override;
private:
int m_value = 0;
Color m_color{0, 0, 0};
};
// number_display.cpp
#include "number_display.h"
#include "gx_format.h"
#include "gx_painter.h"
NumberDisplay::NumberDisplay(Widget *parent)
: Widget(parent) {}
void NumberDisplay::setValue(int v) {
if (m_value == v) return;
m_value = v;
update();
valueChanged(v);
}
void NumberDisplay::setTextColor(const Color &c) {
if (m_color == c) return;
m_color = c;
update();
}
Size NumberDisplay::sizeHint() const {
return Size(60, 30);
}
void NumberDisplay::paintEvent(PaintEvent *) {
Painter p(this);
p.setPen(Pen(m_color));
p.setFont(Font(20));
p.drawText(rect(), format("{}", m_value), Painter::AlignCenter);
}
注册:
kit.installWidget<NumberDisplay>();
应用层使用:
<number-display
value="{{ count }}"
text-color="#333333"
@value-changed="onCountChanged">
</number-display>
至此,从 C++ 实现到应用使用,整个流程完整。每当应用的 count 数据变化时,框架会自动调用 NumberDisplay::setValue();当控件发出 valueChanged 信号时,框架会自动调用应用的 onCountChanged 函数。
