在 SerenityOS 中使用 REGISTER_WIDGET 注册库级与应用级自定义 Widget:GML 自定义组件完全指南 在 SerenityOS 中使用 REGISTER_WIDGET 注册库级与应用级自定义 WidgetGML 自定义组件完全指南【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenitySerenityOS 的图形界面基于GMLGUI Markup Language描述而 GML 之所以能实例化任意控件核心机制就是REGISTER_WIDGET()宏的组件注册体系。本文以 Base/usr/share/man/man5/GML/Define-widget.md 手册页为主线结合 LibGUI 的 Widget.h 宏实现 与 LibWebView 的真实使用案例完整讲解如何让库或应用程序自定义的 Widget 获得 GML 实例化能力读完即可在自己的应用或库中落地自定义控件。什么是库或应用程序自定义 WidgetSerenityOS 的 GML 文件通过命名空间::类名 { ... }的形式声明控件树参见 GML 语法手册例如GUI::Button { text: Hello }其中GUI::Button是LibGUI 库内置控件。但一些应用和库会发现把自己定义的控件写进 GML 往往非常有用——例如浏览器应用需要WebView这样的自定义视图系统监控器需要GraphWidget、MemoryStatsWidget这类专用组件。这正是REGISTER_WIDGET()宏存在的意义它把自定义 C 控件类登记进 GML 的类注册表使 GML 解析器能够在运行时按名字构造出对应的控件实例。在 GML 语法手册 中可以看到这种能力的直接体现——注册过的应用专属控件可以直接写进 GML// 注册后即可在 GML 中使用应用自己的控件 SystemMonitor::GraphWidget { stack_values: true name: memory_graph } SystemMonitor::MemoryStatsWidget { name: memory_stats memory_graph: memory_graph }REGISTER_WIDGET 宏的语法与要求宏签名REGISTER_WIDGET()的调用方式与LibGUI内部注册内置控件的方式完全一致REGISTER_WIDGET(namespace, class_name)该宏在 Userland/Libraries/LibGUI/Widget.h 中定义其完整展开逻辑为#define REGISTER_WIDGET(namespace_, class_name) \ namespace GUI::Registration { \ GUI::ObjectClassRegistration registration_##class_name( \ #namespace_ :: #class_name##sv, []() - ErrorOrNonnullRefPtrGUI::Object { return static_ptr_castGUI::Object(TRY(namespace_::class_name::try_create())); }, registration_Widget); \ }从展开代码可以看出三件事宏生成一个全局对象GUI::Registration::registration_class_name类型为GUI::ObjectClassRegistration该对象以字符串命名空间::类名如Web::OutOfProcessWebView作为注册键并关联一个工厂 lambda工厂 lambda 调用namespace_::class_name::try_create()来构造实例父类注册项指向registration_Widget即GUI::Widget从而继承 Widget 的全部属性。命名空间是硬性要求宏展开后类名以namespace_::class_name的完整限定形式作为注册键因此每个注册的 Widget 都必须放在某个命名空间中。对于通常没有专属命名空间的应用社区通用的做法是直接使用应用名作为命名空间例如浏览器应用使用Web当前仓库中对应代码为 LibWebView/OutOfProcessWebView.cpp 中的REGISTER_WIDGET(WebView, OutOfProcessWebView)即以库名WebView作为命名空间系统监控器使用SystemMonitor。这样既能避免命名冲突也能让 GML 中的命名空间::类名与 C 类型一一对应。必须提供无参构造函数注册的 Widget必须能够在没有任何参数的情况下被构造no-argument constructor。原因从宏展开中即可看出GML 解析器只知道类名 属性键值对它无法猜测构造参数只能依赖无参构造 随后设置属性的流程来还原控件。这条约束同时适用于类自身的构造函数如OutOfProcessWebView::OutOfProcessWebView()注册工厂所调用的try_create()可返回ErrorOr便于在构造阶段报错。注意手册页示例写作REGISTER_WIDGET(Web, OutOfProcessWebView)而当前仓库 LibWebView/OutOfProcessWebView.cpp 的实际代码为REGISTER_WIDGET(WebView, OutOfProcessWebView)——请以仓库源码为准命名空间取WebView。一个完整的实战示例OutOfProcessWebViewGML 侧声明自定义控件注册完成后GML 文件就能像使用内置控件一样使用自定义控件Web::OutOfProcessWebView { name: web_view min_width: 340 min_height: 160 visible: false }属性含义与内置控件完全一致name控件标识供 C 侧通过find_descendant_of_type_named查找详见 GML 使用手册min_width/min_height最小尺寸约束支持ui_dimension类型见 UI Dimensions 手册visible初始可见性。C 侧注册与构造对应实现位于 Userland/Libraries/LibWebView/OutOfProcessWebView.cpp// OutOfProcessWebView.cpp REGISTER_WIDGET(WebView, OutOfProcessWebView) ... OutOfProcessWebView::OutOfProcessWebView() { set_focus_policy(GUI::FocusPolicy::StrongFocus); initialize_client(CreateNewClient::Yes); on_ready_to_paint [this]() { update(); }; // ... 其余回调初始化 }值得注意的实现细节注册语句放在命名空间外、源文件顶部REGISTER_WIDGET展开后自建GUI::Registration命名空间因此在.cpp文件顶部namespace WebView {之前直接书写即可无需包裹在任何命名空间内构造函数中完成默认初始化由于 GML 实例化走无参构造任何必须存在的默认行为焦点策略、客户端创建、回调绑定都要在构造函数里完成而不是依赖外部传入参数手册页原示例中的set_should_hide_unnecessary_scrollbars(true)在当前仓库版本已由滚动条机制统一管理但构造函数内做默认初始化的原则不变。注册机制与 GML 解析器的联动原理从注册表到实例化REGISTER_WIDGET生成的GUI::ObjectClassRegistration对象在程序启动时完成静态初始化把命名空间::类名注册进全局类注册表。GML 解析器位于 LibGUI 的GML子目录遇到Foo::Bar {}时会按字符串在注册表中查找对应的注册项并调用其工厂 lambda 完成try_create()构造。这在 Userland/Libraries/LibGUI/GML/AutocompleteProvider.cpp 中有直接佐证——GML 的自动补全与校验正是通过GUI::ObjectClassRegistration::find()与for_each()遍历所有已注册控件类来工作的。控件树加载方式新式加载采用编译期生成 C 代码的方式在应用的 CMakeLists 中加入compile_gml()生成实现try_create()的源文件详见 GML 使用手册。加载后即可用find_descendant_of_type_named按name属性取回控件MyApp::Widget { GUI::Button { name: mem_add_button text: M } }m_mem_add_button *find_descendant_of_type_namedGUI::Button(mem_add_button);在运行时还会自动调用类的initialize()成员若存在用于绑定回调、挂接数据模型等初始化逻辑。配套机制自定义属性REGISTER_PROPERTY 系列宏自定义控件只有控件结构往往还不够通常还需要暴露控件专属属性例如SystemMonitor::MemoryStatsWidget的memory_graph属性。这由REGISTER_*_PROPERTY系列宏完成其通用语法为详见 GML 属性定义手册REGISTER_TYPENAME_PROPERTY(property_name, getter, setter [, additional parameters...]);示例REGISTER_STRING_PROPERTY(alt_text, alt_text, set_alt_text); // enum 属性必须列出所有枚举值与其 GML 字符串表示 REGISTER_ENUM_PROPERTY( button_style, button_style, set_button_style, Gfx::ButtonStyle, { Gfx::ButtonStyle::Normal, Normal }, { Gfx::ButtonStyle::Coolbar, Coolbar });关键约束宏在构造函数中调用property_name是 GML 可用的字符串键getter不接收参数、返回属性类型值setter接收属性类型值int/string/readonly_string/enum/bool为 C 值其余类型为 JSON 值两者都不可省略但可做成 no-op只读字符串用REGISTER_READONLY_STRING_PROPERTY大多数宏定义在Core::Object头文件中布局相关属性位于GUI::Layout头文件中自定义控件自动继承父类GUI::Widget的所有属性如x、y、name、visible、min_width等。实践要点速查要点说明宏位置在.cpp文件顶部、命名空间外调用REGISTER_WIDGET(ns, Class)命名空间必须存在应用无专属命名空间时惯例用应用名作命名空间构造函数必须无参可构造默认行为初始化一律放入构造函数注册键命名空间::类名GML 中必须用命名空间::类名完全一致地引用属性继承自定义控件自动获得GUI::Widget的全部通用属性自定义属性用REGISTER_*_PROPERTY系列宏在构造函数中注册专属属性运行时查找通过find_descendant_of_type_namedT(name)获取 GML 中的实例参见GML 属性定义手册Define-propertyREGISTER_*_PROPERTY宏详解GML 语法手册SyntaxGML 文件结构、属性类型与布局语法GML 使用手册Usagecompile_gml()与try_create()的加载流程GML 内置控件参考WidgetLibGUI 各内置控件的可用属性UI Dimensions 手册min_width等尺寸属性的取值规则源码宏定义见 Userland/Libraries/LibGUI/Widget.h实际使用案例见 Userland/Libraries/LibWebView/OutOfProcessWebView.cpp【免费下载链接】serenityThe Serenity Operating System 项目地址: https://gitcode.com/GitHub_Trending/se/serenity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考