Qt VS 自定义组件 多组件合并 控件 插件 静态链接 踩坑指南

it2025-03-06  22

折腾这个花了一天时间,记一文吧~

 

环境我用的 Visual Studio + Qt Designer,没有用 Qt Creator,个人习惯问题。

 

1、首先在 Visual Studio 里创建一个 “Qt Designer Custom Widget” 项目,没有的话先装 Qt 扩展,这里不多赘述。

由于本文是针对“在同一个项目中编写多个自定义组件”,所以后面的命名可供参考,尽量不要以其中一个自定义组件为名字命名。

这里我用的项目名称是 “QtCustomWidgets”

 

2、点击创建,会弹出 Qt 的向导窗口,点 Next。

我写文章的这个时间点,Qt 扩展的这个向导是有 BUG 的,比如可能会出现“未将对象引用设置到对象的示例”对话框,点击确定后,Visual Studio 就崩溃了,所以接下来的步骤最好完全按照文中的指导进行操作。

 

3、在向导进行配置平台界面时,将该向导窗口拉到最大(拖动窗口右下角)。

我们编写的是自定义组件插件,自然希望它更具有兼容性,比如同时兼容 x86 和 x64,所以我们按照以下图中步骤进行操作,建立好多个平台的配置,然后点击 Next。

 

4、命名组件,这里我推荐大家命名为 “QCTemplate”。

Q 意为 Qt,防止于变量名冲突。

C 意为 Custom,区别于官方命名。

Template 意为模板,我们需要制作多个自定义组件,所以在今后可以直接复制粘贴该名称的文件。

注意:Custom Widget Class Name,修改一个文本框,即可自动应用到其他两个文本框。但是 Plugin Class Name 三个文本框都需要手动修改,切记保留末尾的 Plugin 后缀。

点击 Finish。

 

5、项目创建好后,我们需要整理一下文件结构和项目结构,方便后面的操作。

刚刚的结构:

整理后的结构:

也就是创建两个文件夹:第一个命名“QCustomWidgets”,在这里面再创建一个“QCTemplate”,然后把“QCTemplate”的 5 个代码文件剪切进去。

 

接着整理项目结构,在 VS 中把那 5 个文件先移除,建好文件夹后再重新添加,因为我们刚刚移动了文件位置。

刚刚的结构:

整理后的结构:

具体看图就行啦,不多赘述了。

 

6、删除 json 文件,并在模板自定义组件代码中删除 Q_PLUGIN_METADATA。

右键 qctemplateplugin.json,点击移除,点击删除。

进入 QCTemplatePlugin.h,删除掉 Q_PLUGIN_METADATA 那行。

具体原因是,Q_PLUGIN_METADATA 只能在一个 DLL 中定义一次,而我们的 QCTemplate 属于自定义组件,在以后会被复制多次,从而导致某些符号多次定义。

Q_PLUGIN_METADATA 会在稍后定义在另一个地方。

 

7、在项目根目录新建两个文件,“WidgetsCollection.cpp” 和 “WidgetsCollection.h”。

如图

键入以下代码。

WidgetsCollection.h:

#pragma once #include <QtUiPlugin/QDesignerCustomWidgetCollectionInterface> class WidgetsCollection : public QObject, public QDesignerCustomWidgetCollectionInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface") Q_INTERFACES(QDesignerCustomWidgetCollectionInterface) public: WidgetsCollection(QObject *parent = 0); QList<QDesignerCustomWidgetInterface*> customWidgets() const override; private: QList<QDesignerCustomWidgetInterface*> widgets; };

WidgetsCollection.cpp:

#include "WidgetsCollection.h" WidgetsCollection::WidgetsCollection(QObject *parent) : QObject(parent) { #define ENABLE_WIDGET(name) widgets.append(new name(this)) #undef ENABLE_WIDGET } QList<QDesignerCustomWidgetInterface*> WidgetsCollection::customWidgets() const { return widgets; }

 

8、在项目根目录新建一个 “Global.h” 文件。

项目结构如图

在其中键入代码:

#pragma once #define QT_CUSTOM_GROUP_NAME "Custom Widgets"

该宏定义指出小组件在 Qt Designer 中的分组名称,可以根据自己需要修改。

 

9、增加包含目录。

右键项目 -> “属性” -> 选择 “所有配置” 和 “所有平台” -> “C/C++” -> “常规” -> “附加包含目录” -> 点击文本框右边的小三角 -> “<编辑...>” -> 添加 “$(ProjectDir)” -> “确定” -> “应用” -> 关闭属性窗口。

 

10、在 “QCTemplatePlugin.cpp” 的最开头包含 “Global.h” 头文件,并修改 group 和 includeFile 两个虚函数的返回值。

我将修改之前的代码和位置以注释的形式展示出来了,方便你们对比。

#include <Global.h> // <-------------------------------------------------- #include "QCTemplate.h" #include "QCTemplatePlugin.h" #include <QtCore/QtPlugin> QCTemplatePlugin::QCTemplatePlugin(QObject *parent) : QObject(parent) { initialized = false; } void QCTemplatePlugin::initialize(QDesignerFormEditorInterface * /*core*/) { if (initialized) return; initialized = true; } bool QCTemplatePlugin::isInitialized() const { return initialized; } QWidget *QCTemplatePlugin::createWidget(QWidget *parent) { return new QCTemplate(parent); } QString QCTemplatePlugin::name() const { return "QCTemplate"; } QString QCTemplatePlugin::group() const { // return "My Plugins"; return QT_CUSTOM_GROUP_NAME; // <-------------------------------------------------- } QIcon QCTemplatePlugin::icon() const { return QIcon(); } QString QCTemplatePlugin::toolTip() const { return QString(); } QString QCTemplatePlugin::whatsThis() const { return QString(); } bool QCTemplatePlugin::isContainer() const { return false; } QString QCTemplatePlugin::domXml() const { return "<widget class=\"QCTemplate\" name=\"QCTemplate\">\n" " <property name=\"geometry\">\n" " <rect>\n" " <x>0</x>\n" " <y>0</y>\n" " <width>100</width>\n" " <height>100</height>\n" " </rect>\n" " </property>\n" "</widget>\n"; } QString QCTemplatePlugin::includeFile() const { // return "QCTemplate.h"; return "QCustomWidgets/QCTemplate/QCTemplate.h"; // <-------------------------------------------------- }

 

11、在 “QCustomWidgets” 文件夹中创建一个 “QCHeader.h” 文件。

文件结构

项目结构

键入以下代码:

#pragma once #if !defined QT_PLUGIN # define QT_CUSTOME_DECL_PORT Q_DECL_IMPORT # pragma comment(lib, "QtCustomWidgets.lib") #else # define QT_CUSTOME_DECL_PORT Q_DECL_EXPORT #endif

该文件用于控制所有自定义组件类的导出或导入修饰,并在其他项目中自动链接库文件。

 

12、在 “QCTemplate.h” 文件中包含该头文件,并在类声明中使用该宏。

#pragma once #include "QCustomWidgets/QCHeader.h" // <-------------------------------------------------- #include <QtWidgets/QWidget> // vvvvvvvvvvvvvvvvvvvv class QT_CUSTOME_DECL_PORT QCTemplate : public QWidget { Q_OBJECT public: QCTemplate(QWidget *parent = Q_NULLPTR); };

 

13、将 QCTemplate 的几个代码文件从编译中排除。

在项目中选中这四个文件

右键属性,从生成中排除,选“是”。

注意配置和平台依然要改为 “所有配置” 和 “所有平台”。

应用,关闭窗口。

得到如下效果

 

14、接下来可以开始写我们的自定义组件了,在 “QtCustomWidgets\QCustomWidgets” 目录中,原地复制粘贴 “QCTemplate” 文件夹,并重命名为你需要的自定义组件名称,这里作为示范命名为 “QCTest1”。

并将文件夹内的所有文件名 QCTemplate 改为 QCTest1,注意有 Plugin 后缀的依旧要保留。

在项目中添加它们。

 

替换这 4 个文件中的所有 QCTemplate 文字,Ctrl + H 呼出文本替换框,源文本 “QCTemplate”,目标文本 “QCTest1”,区分大小写,忽略全字匹配,当前文档。如图所示。点击全部替换。

4个文件都需要替换一次。

 

接着进入 “QCTest1Plugin.cpp”,找到 domXml 函数,返回字符串其中的 name 将会被用作 Qt 自动生成代码的变量名,将它的值改为小驼峰,避免和类名冲突。

原内容是 “QCTest1”,我们改成 “test1”。

举个例子你的自定义组件类名为 “QCSuperLabel”,这里你就可以改为 “superLabel”。

小驼峰不是强制性的,是为了尽量和 Qt 命名方式统一。

QString QCTest1Plugin::domXml() const { // vvvvv return "<widget class=\"QCTest1\" name=\"test1\">\n" " <property name=\"geometry\">\n" " <rect>\n" " <x>0</x>\n" " <y>0</y>\n" " <width>100</width>\n" " <height>100</height>\n" " </rect>\n" " </property>\n" "</widget>\n"; }

 

最后,编写我们自定义组件的代码,这里我简单写一段用作例子。

QCTest1.h:

#pragma once #include "QCustomWidgets/QCHeader.h" #include <QtWidgets/QWidget> #include <QHBoxLayout> #include <QLabel> #include <QPushButton> class QT_CUSTOME_DECL_PORT QCTest1 : public QWidget { Q_OBJECT public: QCTest1(QWidget *parent = Q_NULLPTR); private: QHBoxLayout *layout; QLabel *label; QPushButton *button; };

QCTest1.cpp:

#include "QCTest1.h" QCTest1::QCTest1(QWidget *parent) : QWidget(parent) { layout = new QHBoxLayout{this}; label = new QLabel{"QCTest1", this}; button = new QPushButton{"QCTest1", this}; layout->addWidget(label); layout->addWidget(button); setLayout(layout); }

 

15、启用该自定义组件。

进入 “WidgetsCollection.cpp”,包含 “QCTest1Plugin.h” 头文件,并使用 ENABLE_WIDGET 宏。

如下代码所示:

#include "WidgetsCollection.h" #include "QCustomWidgets/QCTest1/QCTest1Plugin.h" // <-------------------------------------------------- WidgetsCollection::WidgetsCollection(QObject *parent) : QObject(parent) { #define ENABLE_WIDGET(name) widgets.append(new name(this)) ENABLE_WIDGET(QCTest1Plugin); // <-------------------------------------------------- #undef ENABLE_WIDGET } QList<QDesignerCustomWidgetInterface*> WidgetsCollection::customWidgets() const { return widgets; }

 

16、设置自动化脚本,在编译后自动复制 .DLL .LIB .H 文件到 Qt 目录。 

项目右键属性 -> 生成事件 -> 生成后事件 -> 命令行 -> 小三角 -> 编辑 -> 键入以下代码 -> 应用

copy $(OutDir)$(TargetFileName) $(QTDIR)\plugins\designer\$(TargetFileName) copy $(OutDir)$(TargetName).lib $(QTDIR)\lib mkdir $(QTDIR)\include\QCustomWidgets xcopy $(ProjectDir)QCustomWidgets $(QTDIR)\include\QCustomWidgets /s /y

 

17、到这里就快大功告成了。

我们使用 Release 模式编译(切记使用 Debug 编译的 DLL 是没有办法被 Qt Designer 中加载的)后等待数秒,如果编译成功,我们就可以在 Qt Designer 看到我们的自定义组件了,不需要任何手动拷贝操作。

打开一个其他项目,在 Qt Designer 中编辑好后,保存返回 VS,此时依旧在其他项目中。

 

18、自动将我们需要的文件拷贝到其他项目中。

(不知道什么原因, $(QTDIR) 这个环境宏 在 VC++ 目录 的 库目录 中无效。

经测试在生成事件的命令行中宏是有效的,所以这里我们绕一下路,将库文件拷贝到当前项目中来使用。)

右键其他项目 -> 属性 -> 生成事件 -> 生成前事件 -> 命令行 -> 小三角 -> 编辑 -> 键入以下代码 -> 确定 -> 应用。

copy $(QTDIR)\lib\QtCustomWidgets.lib $(ProjectDir)

另外我们的其他项目需要依赖插件的 DLL 文件,所以我们在写一个生成后事件来拷贝 DLL 文件到输出目录。

生成后事件 -> 命令行 -> 小三角 -> 编辑 -> 键入以下代码 -> 确定 -> 应用。

copy $(QTDIR)\plugins\designer\QtCustomWidgets.dll $(OutDir)

 

19、重新编译,大功告成。

 

20、如果后续需要添加更多自定义组件的话,重复 第 14 步 和 第 15 步 即可。

 

想偷懒的朋友,可以直接下载代码使用。

链接:https://download.csdn.net/download/u012088909/13000508

 

这篇文可能写的有些随意,因为在写的时候身体不太舒服,感觉写太详细的话更复杂,就左右出了这么一篇文章,希望不影响大家理解。

如果发现文章有不正之处或有疑问,欢迎在下方评论。如果方便,可以在下面为该文点个赞!

感谢阅读!

 

最新回复(0)