讲讲WidgetContainer,WidgetFactory,WidgetProvider还有ContentPlugin的关系
这四者分别负责:装窗口、找创建方法、规定创建接口、实现具体插件。
| 类 | 可以理解为 | 职责 |
|---|---|---|
WidgetContainer | 窗口的外壳 | 装入、替换和释放内容窗口 |
WidgetFactory | 创建方法的登记表 | 根据名称找到对应的创建函数 |
WidgetProvider | 公共接口 | 规定插件如何声明名称、创建窗口 |
ContentPlugin | 接口的具体实现 | 提供plugin.f、plugin.g,创建FWidget、GWidget |
它们的关系如下,箭头上的文字表示具体关系:
用main.ui中选择plugin.f的例子串起来:
第一步:插件声明自己能提供什么。
ContentPlugin实现了WidgetProvider:
QStringList ContentPlugin::widgetClasses() const { return {"plugin.f", "plugin.g"}; }这相当于告诉主程序:“我可以创建这两种内容。”
第二步:加载器把创建方法登记到工厂。
PluginLoader读取这些名称,将对应回调注册到WidgetFactory:
plugin.f → 调用插件,创建 FWidget plugin.g → 调用插件,创建 GWidget此时只是登记方法,还没有创建内容窗口。
第三步:容器根据 UI 配置请求内容。
main.ui给某个容器配置:
contentClass = "plugin.f"UI 加载器读取后,让容器调用setContentClass("plugin.f")。容器再通过自己持有的工厂副本创建:
这里的this就是容器,作为新窗口的父对象。
第四步:工厂执行回调,插件创建窗口。
回调最终通过WidgetProvider接口,进入:
QWidget *ContentPlugin::createWidget( const QString &name, QWidget *parent) { if (name == "plugin.f") return new FWidget(services, parent); // 其他名称…… }返回的FWidget随后被放进容器的布局中。
最关键的一点是:ContentPlugin不是那个被显示的内容窗口。它是插件入口,负责创建内容;实际显示的是FWidget或GWidget。而WidgetProvider只是它遵守的创建规则。
思考,已有main.ui,怎么知道当前我有6个WidgetContainer对象?
程序不是提前写死“有六个”,而是加载main.ui后,从生成的对象树中查找出来的。
分为两步:
第一步:根据 UI 文件创建对象。
main.ui里有六处这样的描述:
<widget class="WidgetContainer" name="contentCounterContainer"> ... </widget>RuntimeUiLoader读到每一处时,都会进入它重写的createWidget():
if (className == "WidgetContainer") { auto *container = new WidgetContainer(factory, parent); container->setObjectName(name); return container; }因此,六处描述就会创建六个对象。
第二步:从主窗口下面查找所有容器。
UI 加载完成后,populateContainers()执行:
auto containers = root->findChildren<WidgetContainer *>();findChildren默认会递归查找所有后代对象。所以即使容器放在QGroupBox内部,也能找到。
当前对象树大致是:
主窗口 root ├── 内容计数组 → WidgetContainer ├── 内容文本组 → WidgetContainer ├── 设备监控组 → WidgetContainer ├── 设备设置组 → WidgetContainer ├── 报表预览组 → WidgetContainer └── 报表编辑组 → WidgetContainer然后逐个读取contentClass,装入对应内容。
这样看的话,这个RuntimeUiLoader类很有意思啊
是的,
RuntimeUiLoader正好处在UI 文件描述和实际 C++ 对象之间,所以它很适合做扩展。Qt 原有的
QUiLoader已经知道如何创建QPushButton、QLabel等标准控件。我们重写createWidget(),让它额外认识WidgetContainer:
if (className == "WidgetContainer") { // 创建我们自己的容器。 ... } // 其他控件继续交给 Qt 创建。 return QUiLoader::createWidget(className, parent, name);它在当前程序中做了两件事:
- 加载时创建容器:看到
WidgetContainer,就创建对应的 C++ 对象。 - 加载后填充内容:找到所有容器,读取各自的
contentClass,再让容器装入插件界面。
这也解释了为什么分成两个阶段:执行createWidget()时,该控件在 UI 文件中的属性还没有全部设置好。等整个 UI 加载完成,再读取contentClass,才能可靠地知道要装入什么内容。
你可以把它看成一个“装配员”:main.ui给出布局和配置,RuntimeUiLoader把这些描述组装成真正可运行的界面。
你已经把主要流程串起来了:
main.ui描述界面 →RuntimeUiLoader创建容器 → 容器找工厂 → 工厂调用插件 → 插件创建 QWidget。