以前做.NET开发中,.NET直接就集成了属性设计器,VS不愧是宇宙第一IDE,你能够想到的都给你封装好了,用起来不要太爽!因为项目需要自从全面转Qt开发已经6年有余,在工业控制领域,有一些应用场景需要自定义绘制一些控件满足特定的需求,比如仪器仪表、组态等,而且需要直接用户通过属性设计的形式生成导出控件及界面数据,下次导入使用,要想从内置控件或者自定义控件拿到对应的属性方法等,首先联想到的就是反射,Qt反射对应的类叫QMetaObject,着实强大,其实整个Qt开发框架也是超级强大的,本人自从转为Qt开发为主后,就深深的爱上了她,在其他跨平台的GUI开发框架平台面前,都会被Qt秒成渣,Qt的跨平台性是毋庸置疑的,几十兆的内存存储空间即可运行,尤其是嵌入式linux这种资源相当紧张的情况下,Qt的性能发挥到极致。

接下来我们就一步步利用QMetaObject类和QtPropertyBrower(第三方开源属性设计器)来实现自己的控件属性设计器,其中包含了所见即所得的控件属性控制,以及xml数据的导入导出。

第一步:获取控件的属性名称集合。

所有继承自QObject类的类,都有元对象,都可以通过这个QObject类的元对象metaObject()获取属性+事件+方法等。

代码如下:

QPushButton *btn = new QPushButton;
const QMetaObject *metaobject = btn->metaObject();
int count = metaobject->propertyCount();
for (int i = 0; i < count; ++i) {QMetaProperty metaproperty = metaobject->property(i);const char *name = metaproperty.name();QVariant value = btn->property(name);qDebug() << name << value;
}

打印输出如下:

objectName QVariant(QString, "")
modal QVariant(bool, false)
windowModality QVariant(int, 0)
enabled QVariant(bool, true)
geometry QVariant(QRect, QRect(0,0 640x480))
frameGeometry QVariant(QRect, QRect(0,0 639x479))
normalGeometry QVariant(QRect, QRect(0,0 0x0))
省略后面很多…

可以看到打印了很多父类的属性,这些基本上我们不需要的,那怎么办呢,放心,Qt肯定帮我们考虑好了,该propertyOffset上场了。metaObject->propertyOffset()表示出了父类外,自己类本身属性的偏移位置即索引开始的位置,这下就好办了。

代码改为:

QPushButton *btn = new QPushButton;
const QMetaObject *metaobject = btn->metaObject();
int count = metaobject->propertyCount();
int index = metaobject->propertyOffset();
for (int i = index; i < count; ++i) {QMetaProperty metaproperty = metaobject->property(i);const char *name = metaproperty.name();QVariant value = btn->property(name);qDebug() << name << value;
}

就是将i的起始位置改为偏移位置即可。

打印输出如下:

autoDefault QVariant(bool, false)
default QVariant(bool, false)
flat QVariant(bool, false)

这个过滤非常有用,因为真实用到的大部分应用场景都是控件类本身的属性,而不是父类的。

第二步:将控件类绑定到属性设计器。

拿到了控件的属性是第一步,接下来就是需要拿到属性所关联的方法等,这里省略,因为QtPropertyBrower这个屌爆了的第三方开源的属性设计器,全部给我们写好了,可以查看Qt帮助文档或者QMetaObject的头文件看到,QMetaObject提供了哪些接口去获取或使用这些元信息。比如classInfo获取类的信息、enumerator获取枚举值信息、method获取方法,property获取属性、superClass获取父类的名称等。

QtPropertyBrower中提供了ObjectController类,该类继承自QWidget,这样的话我们在界面上拖一个QWidget控件,鼠标右键提升为ObjectController即可。

这个轮子造的不要太好,我们只需要一行代码就可以让所有属性自动罗列到属性设计器中,代码是ui->objectController->setObject(btn);

看下效果如图:

到这里是不是很兴奋呢,任意控件都可以这样来展示自己的属性。在右侧动态更改属性会立即应用生效。

第三步:获取自定义控件的插件的所有控件。

接下来这一步才是最关键的一步,以上举例是Qt自带控件的,如果是自定义控件插件比如就一个DLL文件呢,怎么办?放心,办法肯定是有的。

该插件类QPluginLoader上场了。通过QPluginLoader载入后的实例,通过QDesignerCustomWidgetCollectionInterface类获取插件容器,然后逐个遍历容器找出单个插件,包括获得类名+图标。

代码如下:

void frmMain::openPlugin(const QString &fileName)
{qDeleteAll(listWidgets);listWidgets.clear();listNames.clear();ui->listWidget->clear();//加载自定义控件插件集合信息,包括获得类名+图标QPluginLoader loader(fileName);if (loader.load()) {QObject *plugin = loader.instance();//获取插件容器,然后逐个遍历容器找出单个插件QDesignerCustomWidgetCollectionInterface *interfaces = qobject_cast<QDesignerCustomWidgetCollectionInterface *>(plugin);if (interfaces)  {listWidgets = interfaces->customWidgets();int count = listWidgets.count();for (int i = 0; i < count; i++) {QIcon icon = listWidgets.at(i)->icon();QString className = listWidgets.at(i)->name();QListWidgetItem *item = new QListWidgetItem(ui->listWidget);item->setText(className);item->setIcon(icon);listNames << className;}}//获取所有插件的类名const QObjectList objList = plugin->children();foreach (QObject *obj, objList) {QString className = obj->metaObject()->className();//qDebug() << className;}}
}

效果图如下:

第四步:实例化new出控件并放到窗体。

拿到了所有的控件,前面还有个对应控件的小图标,是不是又有点小激动呢,接下来就是怎么双击或者拖动该控件到界面上立马实例化一个控件出来。上一步我们将所有控件放到了一个链表变量listWidgets中,该变量在头文件中定义如下:

QList<QDesignerCustomWidgetInterface *> listWidgets;

这里写了个函数,传入列表中控件的索引,即该类的索引位置,和控件默认要放置的坐标,即可在主界面生成该控件。

代码如下:

void frmMain::newWidget(int row, const QPoint &point)
{//列表按照同样的索引生成的,所以这里直接对该行的索引就行QWidget *widget = listWidgets.at(row)->createWidget(ui->centralwidget);widget->move(point);widget->resize(widget->sizeHint());//实例化选中窗体跟随控件一起newSelect(widget);//立即执行获取焦点以及设置属性widgetPressed(widget);
}

第五步:动态绑定控件到设计器。

这一步就比较轻松了,上面提到过,直接获取当前界面上选中的是哪个控件,遍历可以得到,然后设置object到属性设计器控件即可。

代码如下:

void frmMain::clearFocus()
{//将原有焦点窗体全部设置成无焦点foreach (SelectWidget *widget, selectWidgets) {widget->setDrawPoint(false);}
} void frmMain::widgetPressed(QWidget *widget)
{//清空所有控件的焦点clearFocus();//设置当前按下的控件有焦点foreach (SelectWidget *w, selectWidgets) {if (w->getWidget() == widget) {w->setDrawPoint(true);break;}}//设置自动加载该控件的所有属性ui->objectController->setObject(widget);
}

第六步:导入导出控件属性到xml文件。

这一步比较难,本人也是花了好几个小时才搞定,前后折腾了好多次,因为遇到好几个棘手的问题,比如有些自定义控件中其实里边封装了Qt自带的控件例如QPushButton等,如果遍历控件设计窗体的所有控件,也会把该控件也遍历进去,所以要做过滤处理。

导入xml数据自动生成控件代码如下:

void frmMain::openFile(const QString &fileName)
{//打开文件QFile file(fileName);if (!file.open(QFile::ReadOnly | QFile::Text)) {return;}//将文件填充到dom容器QDomDocument doc;if (!doc.setContent(&file)) {file.close();return;}file.close();//先清空原有控件QList<QWidget *> widgets = ui->centralwidget->findChildren<QWidget *>();qDeleteAll(widgets);widgets.clear();//先判断根元素是否正确QDomElement docElem = doc.documentElement();if (docElem.tagName() == "canvas") {QDomNode node = docElem.firstChild();QDomElement element = node.toElement();while(!node.isNull()) {QString name = element.tagName();//存储坐标+宽高int x, y, width, height;//存储其他自定义控件属性QList<QPair<QString, QVariant> > propertys;//节点名称不为空才继续if (!name.isEmpty()) {//遍历节点的属性名称和属性值QDomNamedNodeMap attrs = element.attributes();for (int i = 0; i < attrs.count(); i++) {QDomNode n = attrs.item(i);QString nodeName = n.nodeName();QString nodeValue = n.nodeValue();//qDebug() << nodeName << nodeValue;//优先取出坐标+宽高属性,这几个属性不能通过setProperty实现if (nodeName == "x") {x = nodeValue.toInt();} else if (nodeName == "y") {y = nodeValue.toInt();} else if (nodeName == "width") {width = nodeValue.toInt();} else if (nodeName == "height") {height = nodeValue.toInt();} else {propertys.append(qMakePair(nodeName, QVariant(nodeValue)));}}}//qDebug() << name << x << y << width << height;//根据不同的控件类型实例化控件int count = listWidgets.count();for (int i = 0; i < count; i++) {QString className = listWidgets.at(i)->name();if (name == className) {QWidget *widget = listWidgets.at(i)->createWidget(ui->centralwidget);//逐个设置自定义控件的属性int count = propertys.count();for (int i = 0; i < count; i++) {QPair<QString, QVariant> property = propertys.at(i);widget->setProperty(property.first.toLatin1().constData(), property.second);}//设置坐标+宽高widget->setGeometry(x, y, width, height);//实例化选中窗体跟随控件一起newSelect(widget);break;}}//移动到下一个节点node = node.nextSibling();element = node.toElement();}}
}

导出所有控件到xml文件代码如下:

void frmMain::saveFile(const QString &fileName)
{QFile file(fileName);if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {return;}//以流的形式输出文件QTextStream stream(&file);//构建xml数据QStringList list;//添加固定头部数据list << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";list << QString("<canvas width=\"%1\" height=\"%2\">").arg(ui->centralwidget->width()).arg(ui->centralwidget->height());//从容器中找到所有控件,根据控件的类名保存该类的所有属性QList<QWidget *> widgets = ui->centralwidget->findChildren<QWidget *>();foreach (QWidget *w, widgets) {const QMetaObject *metaObject = w->metaObject();QString className = metaObject->className();QStringList values;//如果当前控件的父类不是主窗体则无需导出,有些控件有子控件无需导出if (w->parent() != ui->centralwidget || className == "SelectWidget") {continue;}//metaObject->propertyOffset()表示当前控件的属性开始索引,0开始的是父类的属性int index = metaObject->propertyOffset();for (int i = index; i < metaObject->propertyCount(); i++) {QMetaProperty p = metaObject->property(i);QString nodeName = p.name();QVariant nodeValue = p.read(w);//枚举值要特殊处理,需要以字符串形式写入,不然存储到配置文件数据为intif (p.isEnumType()) {QMetaEnum enumValue = p.enumerator();nodeValue = enumValue.valueToKey(nodeValue.toInt());}QString temp = nodeValue.toString().toLocal8Bit().constData();values << QString("%1=\"%2\"").arg(nodeName).arg(temp);//qDebug() << nodeName << nodeValue;}//逐个添加界面上的控件的属性QString str = QString("\t<%1 x=\"%2\" y=\"%3\" width=\"%4\" height=\"%5\" %6/>").arg(className).arg(w->x()).arg(w->y()).arg(w->width()).arg(w->height()).arg(values.join(" "));list << str;}//添加固定尾部数据list << "</canvas>";//写入文件QString data = list.join("\n");stream << data;file.close();}

  xml数据格式效果图:

完整效果图:

最后分享一些自己整理好的Qt开发过程中的小技巧,Qt武林秘籍。

1:当编译发现大量错误的时候,从第一个看起,一个一个的解决,不要急着去看下一个错误,往往后面的错误都是由于前面的错误引起的,第一个解决后很可能都解决了。

2:定时器是个好东西,学会好使用它,有时候用QTimer::singleShot可以解决意想不到的问题。

3:打开creator,在构建套件的环境中增加MAKEFLAGS=-j8,可以不用每次设置多线程编译。珍爱时间和生命。

4:如果你想顺利用QtCreator部署安卓程序,首先你要在AndroidStudio 里面配置成功,把坑全部趟平。

5:很多时候找到Qt对应封装的方法后,记得多看看该函数的重载,多个参数的,你会发现不一样的世界,有时候会恍然大悟,原来Qt已经帮我们封装好了。

6:可以在pro文件中写上标记版本号+ico图标

VERSION             = 2018.7.25
win32:RC_ICONS      = main0.ico

7:管理员运行程序,限定在MSVC编译器。

QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\'\" #以管理员运行
QMAKE_LFLAGS += /SUBSYSTEM:WINDOWS,\"5.01\" #VS2013 在XP运行

8:运行文件附带调试输出窗口,有时候程序双击了没有反应,这样可以很方便的知道哪里出了问题。

CONFIG += console pro

9:绘制平铺背景QPainter::drawTiledPixmap

绘制圆角矩形QPainter::drawRoundedRect(),而不是QPainter::drawRoundRect();

10:移除旧的样式

style()->unpolish(ui->btn);

重新设置新的该控件的样式。

style()->polish(ui->btn);

11:获取类的属性

const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i = 0; i < count; ++i) {QMetaProperty metaproperty = metaobject->property(i);const char *name = metaproperty.name();QVariant value = object->property(name);qDebug() << name << value;
}

12:Qt内置图标封装在QStyle中,总共七十多个,可以直接拿来用。

QStyle :: SP_TitleBarMenuButton

13:根据操作系统位数判断加载

win32 {contains(DEFINES, WIN64) {DESTDIR = $${PWD}/../../bin64} else {DESTDIR = $${PWD}/../../bin32}
}

14:Qt5增强了很多安全性验证,如果出现setGeometry: Unable to set geometry,请将该控件的可见移到加入布局之后。

15:可以将控件A添加到布局,然后控件B设置该布局,这种灵活性大大提高了控件的组合度,比如可以在文本框左侧右侧增加一个搜索按钮,按钮设置图标即可。

QPushButton *btn = new QPushButton;
btn->resize(30, ui->lineEdit->height());
QHBoxLayout *layout = new QHBoxLayout(ui->lineEdit);
layout->setMargin(0);
layout->addStretch();
layout->addWidget(btn);

16:对QLCDNumber控件设置样式,需要将QLCDNumber的segmentstyle设置为flat。

17:巧妙的使用findChildren可以查找该控件下的所有子控件。findChild为查找单个。

//查找指定类名objectName的控件
QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");
//查找所有QPushButton
QList<QPushButton *> allPButtons = parentWidget.findChildren<QPushButton *>();
//查找一级子控件,不然会一直遍历所有子控件
QList<QPushButton *> childButtons = parentWidget.findChildren<QPushButton *>(QString(), Qt::FindDirectChildrenOnly);

18:巧妙的使用inherits判断是否属于某种类。

QTimer *timer = new QTimer;         // QTimer inherits QObject
timer->inherits("QTimer");          // returns true
timer->inherits("QObject");         // returns true
timer->inherits("QAbstractButton"); // returns false

19:使用弱属性机制,可以存储临时的值用于传递判断。

20:如果遇到问题搜索Qt方面找不到答案,试着将关键字用JAVA C# android打头,你会发现别有一番天地,其他人很可能做过!

转载于:https://www.cnblogs.com/feiyangqingyun/p/9694025.html

Qt编写自定义控件属性设计器相关推荐

  1. qt更改类名_Qt编写自定义控件属性设计器

    以前做.NET开发中,.NET直接就集成了属性设计器,VS不愧是宇宙第一IDE,你能够想到的都给你封装好了,用起来不要太爽!因为项目需要自从全面转Qt开发已经6年有余,在工业控制领域,有一些应用场景需 ...

  2. linux QT 结束当前进程_Qt编写控件属性设计器7-串口采集

    一.前言 数据源是组态软件的核心灵魂,少了数据源,组态就是个花架子没卵用,一般数据源有三种方式获取,串口.网络.数据库,至于数据规则是什么,这个用户自己指定,本设计器全部采用第一个字节作为数据来演示. ...

  3. c++ qt qlistwidget清空_Qt编写控件属性设计器12-用户属性

    一.前言 用户属性是后面新增加的一个功能,自定义控件如果采用的Q_PROPERTY修饰的属性,会自动识别到属性栏中,这个一般称为控件属性,在组态设计软件中,光有控件本身的控件属性还是不够的,毕竟这些属 ...

  4. Qt编写控件属性设计器4-加载属性

    一.前言 控件能加载拖曳拉伸了,这些都是基本的前提工作,接下来的重点就是要动态加载选中控件的属性了,Qt的属性机制那是异常的强大,只能用强大到爆来形容,Qt中编写自定义控件,如果属性都用Q_PROPE ...

  5. Qt编写控件属性设计器

    一.前言 自从研究Qt编写自定义控件以来,一发不可收拾,越多越多人有类似的需求找我定制控件,陆陆续续写了上百个控件,目前已超过150个,于是逐渐衍生了另外一个需求,提供一个控件属性设计器,类似QtDe ...

  6. Qt编写控件属性设计器7-串口采集

    一.前言 数据源是组态软件的核心灵魂,少了数据源,组态就是个花架子没卵用,一般数据源有三种方式获取,串口.网络.数据库,至于数据规则是什么,这个用户自己指定,本设计器全部采用第一个字节作为数据来演示. ...

  7. Qt编写控件属性设计器5-属性中文

    一.前言 在上一篇文章中就提到过,使用qtpropertybrowser来加载属性,对应加载到的属性是英文的,也就是控件类中Q_PROPERTY描述的变量名称,如何变成中文或者其他语言显示呢?这个就需 ...

  8. Qt编写控件属性设计器6-动态属性

    一.前言 之前就提过,Qt的属性机制强大到爆,这次的动态属性功能就是要让他爆,很难想象只要一行代码即可widget->setProperty("value", value); ...

  9. Qt编写控件属性设计器2-拖曳控件

    一.前言 上一篇文章把插件加载好了,并且把插件中的所有控件都显示到了列表框中,这次要做的就是实现拖曳控件的功能,用户选择一个控件拖曳到画布上,松开,在松开位置处自动实例化该控件,这个需要用到dropE ...

  10. Qt编写控件属性设计器3-拉伸控件

    一.前言 插件控件加载了,拖曳控件也实现了,接下来就是一个最难点了,跟QtDesigner或者其他开发环境一样,能够任意自由的拉伸控件大小,移动位置,为了这个功能,还特别编写了一个控件来实现这个功能, ...

最新文章

  1. 58同城数据库架构最佳实践
  2. 定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句
  3. js $.inArray
  4. 让Cygwin支持中文
  5. 生信识图 之 点图进阶-2(PCA)
  6. linux ip转发 丢包,sendto频率过快导致发送丢包
  7. 信息安全制度管理办法
  8. RationalDMIS 2020 手动特征测量注意事项
  9. Java学习——File类
  10. 发送报警信息到微信公众号
  11. 用MySQL绘制新年祝福图形_2020年春节,你有哪些与众不同的祝福语?
  12. 戴尔计算机主机型号,戴尔电脑在哪看型号_戴尔电脑型号怎么看
  13. LQA: Time of day is written with a “dot” not a “colon” (13.14 not 13:14)
  14. supervisor reload报错及恢复方法
  15. 金航数码再度携手 The Open Group年度峰会
  16. 刘强东:拿 PPT 和假大空词汇忽悠的人就是骗子
  17. 【正点原子FPGA连载】第三十一章RTC实时时钟数码管显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1
  18. mysql 计算gps坐标距离_mysql实现经纬度计算两个坐标之间的距离
  19. [BugKu Web]ez_serialize
  20. Python3:《机器学习实战》之支持向量机(2)简化版SMO

热门文章

  1. Ovum 最新市场报告称数据中心持续改变光网络市场
  2. 屏幕适配 percent_库的扩展
  3. java工程展示问题
  4. 搭建新环境的准备工作
  5. udev文件系统的使用和基本工作原理分析
  6. 防止ADSL被***的一些技巧
  7. wine android6,安卓能跑Windows程序,Wine 2.10开发版发布
  8. Android 四大组件学习之Activity二
  9. Android分区查看方法
  10. makefile中=、:=和+=的区别