前言
1. Qt实现自定义控件的方法主要有两种,一是提升法,二是插件法,本文将对这两种方法进行详细介绍。
2. 完整的代码示例在绑定的资源中,审核通过后,大家可以免费下载。
一 提升法
1. 说明
1.1 Qt 提升法是一种将界面设计工具(Qt Designer)中的标准控件替换为自定义控件,并对自定义控件进行高度定制的技术。
1.2 提升法制作的自定义控件是对标准控件的扩展。
1.3 提升法制作的自定义控件只能在本程序中使用。
2. 步骤
1. 在UI设计界面中拖拽一个标准控件,可以拖拽任意类型的控件,例如:QWidget、QPushButton 等。
2. 创建一个自定义类,继承于1中拖拽的控件类型。
3. 在UI涉及界面中选中控件,右键点击,选择"提升为",在弹出的界面中填写相关信息,最后点击"提升"按钮完成提升操作。此时,Qt Designer 会将所选的控件替换为自定义控件。
4. 使用提升后的控件:在UI设计界面修改自定义控件的属性、在代码中重写自定义控件的方法等
补充:将多个控件组合为自定义控件的方法:
4.1 在UI设计界面中拖拽一个 QWidget 控件
4.2 创建一个自定义类,继承于 QWidget;并在类中创建组合控件中的各个子控件实例对象,在类的构造函数中使用布局来管理子控件组件,重写方法实现自定义组合控件的功能。
4.3 将 QWidget 控件提升为自定义类。
3. 举例
创建一个自定义控件,根据鼠标的不同事件,自定义控件显示不同的状态。
完整代码在绑定的资源中,审核通过后,大家可以免费下载。
二 插件法
1. 说明
1.1 Qt 插件法是一种通过创建插件并在插件中添加自定义控件,使自定义控件能在多个 Qt 项目中共享和使用的技术。
1.2 插件法制作的自定义控件是完全独立的控件。
1.3 插件法制作的自定义控件是一个插件,可以提供给其他程序使用;还可以在 Qt Designer 中拖拽使用。
2. 制作自定义控件插件
2.1 使用 Qt 向导创建一个包含 Widgets 模块的动态库项目。
2.2 创建插件
第一步:添加一个继承于 QObject 和 QDesignerCustomWidgetInterface 的插件类,生成插件类的 .h 和 .cpp 文件。
第二步:修改 CMakeLists.txt,添加 QDesignerCustomWidgetInterface 模块:
#查找 Designer 模块
find_package(Qt${QT_VERSION_MAJOR} NAMES Qt6 Qt5 QUIET COMPONENTS Designer)add......
#指定 Designer 模块头文件的搜索路径
target_include_directories(QtPlugin PRIVATE ${Qt${QT_VERSION_MAJOR}_DESIGNER_INCLUDE_DIRS})#链接 Designer 模块的库文件
target_link_libraries(QtPlugin PRIVATE Qt${QT_VERSION_MAJOR}::Designer)第三步:声明插件必须的宏
//启用 Qt 的元对象系统
Q_OBJECT;//声明插件类实现的接口
Q_INTERFACES(QDesignerCustomWidgetInterface)//向插件中添加接口IID,用于标识插件
Q_PLUGIN_METADATA(IID QDesignerCustomWidgetInterface_iid)
第四步 重写 QDesignerCustomWidgetInterface 的纯虚函数
2.3 创建自定义控件
第一步:添加一个继承于 QWidget 的自定义控件类,生成自定义控件类的 .h 和 .cpp 文件;不要创建UI文件。
第二步:通过代码实现自定义控件的功能,不要使用UI设计界面拖拽控件。第三步:使用宏 QTPLUGIN_EXPORT 将自定义控件类声明为导入/导出类。
第四步:在插件类的 createWidget() 函数中实例化自定义控件,并传出(QWidget *)。
3. 使用自定义控件插件
使用自定义插件的方法有四种,如下:
3.1 使用 Qt Designer 加载自定义插件
3.1.1 将编译生成的插件库(.dll)复制到MSVC编译器路径下,路径参考:...\MSVC...\plugins\designer3.1.2 打开MSVC2019版本的qt设计师软件,在软件左侧最下面就可以看到刚刚我们生成的插件,可以通过拖拽的方式使用自定义控件。
3.1.3 说明:因为我使用的编译器是 MinGW 而不是 MSVC,所以不再做深层次的研究。
3.2 使用 QtCreator 加载自定义插件
3.2.1 将编译生成的插件库(.dll)复制到 QtCreator 路径下3.2.2 重启 QtCreator,创建一个新的 Qt 窗口程序,进入UI设计界面,点击“工具”—>“Form Editor"—>”About Qt Designer Plugins“,在弹窗界面点一下”刷新“,然后就可以看到已经识别到我们的插件了。可以通过拖拽的方式使用自定义控件。
3.2.3 注意:生成自定义控件插件的编译器版本要和 QtCreator 版本一致。因为我使用的编译器是 MinGW,QtCreator 版本是 Based on Qt 6.6.3 (MSVC 2019, x86_64),所以不再做深层次的研究。
3.3 像使用动态库一样,隐式链接插件库,静态调用插件提供的功能函数
3.4 通过 PluginInterface 动态加载插件,使用插件提供的功能函数
4. 部分代码示例,完整的代码在绑定的资源中,审核通过后大家可以免费下载
//一 插件类
class CustomControlPlugin : public QObject, public QDesignerCustomWidgetInterface
{
//启用 Qt 的元对象系统
Q_OBJECT;
//声明插件类实现的接口
Q_INTERFACES(QDesignerCustomWidgetInterface)
//向插件中添加接口IID,用于标识插件
Q_PLUGIN_METADATA(IID QDesignerCustomWidgetInterface_iid)
public:
explicit CustomControlPlugin(QObject *parent = nullptr);
~CustomControlPlugin();
bool isContainer() const override;
bool isInitialized() const override;
QIcon icon() const override;
QString domXml() const override;
QString group() const override;
QString includeFile() const override;
QString name() const override;
QString toolTip() const override;
QString whatsThis() const override;
QWidget *createWidget(QWidget *parent) override;
void initialize(QDesignerFormEditorInterface *core) override;
private:
bool m_initialized = false;
//自定义控件对象
QWidget *m_pWidget;
};
2. 源文件:
CustomControlPlugin::CustomControlPlugin(QObject *parent)
: QObject(parent)
{}
CustomControlPlugin::~CustomControlPlugin()
{
qDebug()<<"插件法创建自定义控件的插件的析构函数调用!";
delete m_pWidget;
}
void CustomControlPlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
if (m_initialized)
return;
// Add extension registrations, etc. here
m_initialized = true;
}
bool CustomControlPlugin::isInitialized() const
{
return m_initialized;
}
//在插件类的 createWidget 函数中实例化自定义控件对象,并传出对象指针
QWidget *CustomControlPlugin::createWidget(QWidget *parent)
{
m_pWidget = new CustomControl(parent);
return m_pWidget;
}
QString CustomControlPlugin::name() const
{
return QLatin1String("CustomControl");
}
QString CustomControlPlugin::group() const
{
return QLatin1String("");
}
QIcon CustomControlPlugin::icon() const
{
return QIcon(":/new/prefix1/qyc.png");
}
QString CustomControlPlugin::toolTip() const
{
return QLatin1String("");
}
QString CustomControlPlugin::whatsThis() const
{
return QLatin1String("");
}
bool CustomControlPlugin::isContainer() const
{
return false;
}
QString CustomControlPlugin::domXml() const
{
return QLatin1String(R"(<widget class="CustomControl" name="customControl">
</widget>)");
}
QString CustomControlPlugin::includeFile() const
{
return QLatin1String("customcontrol.h");
}
//二 自定义控件类
class QTPLUGIN_EXPORT CustomControl : public QWidget
{
Q_OBJECT
public:
explicit CustomControl(QWidget *parent = nullptr);
~CustomControl();
private:
//标签对象
QLabel *m_pLabel;
//定时器对象
QTimer* m_pTimer;
};
3.2 源文件
CustomControl::~CustomControl()
{
qDebug()<<"插件法创建的自定义控件析构函数调用!";
//停止定时器
m_pTimer->stop();
delete m_pTimer;
}
//在自定义控件类的构造函数中定制自定义控件
CustomControl::CustomControl(QWidget *parent)
: QWidget{parent}
{
//设置自定义控件的大小
this->resize(300, 300);
//实例化标签对象
m_pLabel = new QLabel(this);
//设置标签控件的大小
m_pLabel->resize(200, 100);
//设置标签控件显示的初始值
m_pLabel->setText("自定义控件测试");
//创建水平布局对象
QHBoxLayout *layout = new QHBoxLayout(this);
//将子控件添加到布局中
layout->addWidget(m_pLabel);
//将水平布局设置给父窗口对象
this->setLayout(layout);
// 实例化定时器对象
m_pTimer = new QTimer;
// 设置定时器的时间间隔为1000毫秒
m_pTimer->setInterval(1000);
// 设置定时器的精度级别为实时
m_pTimer->setTimerType(Qt::PreciseTimer);
// 将定时器信号和 Lambda 表达式表示的槽函数关联
connect(m_pTimer, &QTimer::timeout, this, [&](){
// 获取当前系统时间
QTime curTime = QTime::currentTime();
// 格式化时间显示
QString strTime = curTime.toString("hh时 mm分 ss秒 zzz 毫秒");
// 在控件中显示当前时间
m_pLabel->setText(strTime);
});
//启动定时器
m_pTimer->start();
}
//三 加载插件,使用插件
//实例化 QPluginLoader,传入父对象,以管理其生命周期
loader = new QPluginLoader(this);
//设置插件的文件路径
loader->setFileName("G:\\0_Code\\Qt\\15_CustomControl\\2_CustomControl_Plugin\\QtPlugin\\build\\Desktop_Qt_6_7_1_MinGW_64_bit-Debug\\libQtPlugin.dll");
//获取插件实例
QObject *plugin = loader->instance();
if (plugin)
{
//将插件实例转换为接口指针
QDesignerCustomWidgetInterface* interface = qobject_cast<QDesignerCustomWidgetInterface*>(plugin);
if (interface)
{
#if 1
//传入父对象(当前窗口)
//创建自定义控件对象,并获取指向自定义控件对象的指针
QWidget *widget = interface->createWidget(this);
#else
//不传入父对象
//创建自定义控件对象,并获取指向自定义控件对象的指针
QWidget *widget = interface->createWidget(nullptr);
//显示自定义控件
widget->show();
#endif
}
else
{
qDebug() << "插件实例转换为接口指针失败!";
}
}
else
{
//若获取插件实例失败,打印错误信息
qDebug() << "获取插件实例失败,错误信息:" << loader->errorString();
}
//2. 卸载插件
//5秒后卸载插件
QTimer::singleShot(5000, this, [&](){
//检查插件是否已加载
if (loader->isLoaded())
{
//卸载插件
loader->unload();
}
//delete loader;
});