折腾这个花了一天时间,记一文吧~
环境我用的 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
这篇文可能写的有些随意,因为在写的时候身体不太舒服,感觉写太详细的话更复杂,就左右出了这么一篇文章,希望不影响大家理解。
如果发现文章有不正之处或有疑问,欢迎在下方评论。如果方便,可以在下面为该文点个赞!
感谢阅读!