logo资料库

Qt编写的自定义控件属性设计器.doc

第1页 / 共10页
第2页 / 共10页
第3页 / 共10页
第4页 / 共10页
第5页 / 共10页
第6页 / 共10页
第7页 / 共10页
第8页 / 共10页
资料共10页,剩余部分请下载后查看
以前做.NET 开发中,.NET 直接就集成了属性设计器,VS 不愧是宇宙第一 IDE,你能够想到的都给你封装好了,用起来不要太爽!因为 项目需要自从全面转 Qt 开发已经 6 年有余,在工业控制领域,有一些应用场景需要自定义绘制一些控件满足特定的需求,比如仪器仪表、 组态等,而且需要直接用户通过属性设计的形式生成导出控件及界面数据,下次导入使用,要想从内置控件或者自定义控件拿到对应的 属性方法等,首先联想到的就是反射,Qt 反射对应的类叫 QMetaObject,着实强大,其实整个 Qt 开发框架也是超级强大的,本人自从 转为 Qt 开发为主后,就深深的爱上了她,在其他跨平台的 GUI 开发框架平台面前,都会被 Qt 秒成渣,Qt 的跨平台性是毋庸置疑的,几 十兆的内存存储空间即可运行,尤其是嵌入式 linux 这种资源相当紧张的情况下,Qt 的性能发挥到极致。 接下来我们就一步步利用 QMetaObject 类和 QtPropertyBrower(第三方开源属性设计器)来实现自己的控件属性设计器,其中包含了所 见即所得的控件属性控制,以及 xml 数据的导入导出。 第一步:获取控件的属性名称集合。 所有继承自 QObject 类的类,都有元对象,都可以通过这个 QObject 类的元对象 metaObject()获取属性+事件+方法等。 代码如下: ? 1 2 3 4 5 6 7 8 9 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; } 打印输出如下: ? 1 2 3 4 5 6 7 8 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()表示出了父类外,自己类本身属性的偏移位置即索引开始的位置,这下就好办了。
代码改为: ? 1 2 3 4 5 6 7 8 9 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; 10 } 就是将 i 的起始位置改为偏移位置即可。 打印输出如下: ? 1 2 3 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(bt n); 看下效果如图:
到这里是不是很兴奋呢,任意控件都可以这样来展示自己的属性。在右侧动态更改属性会立即应用生效。 第三步:获取自定义控件的插件的所有控件。 接下来这一步才是最关键的一步,以上举例是 Qt 自带控件的,如果是自定义控件插件比如就一个 DLL 文件呢,怎么办?放心,办法肯 定是有的。 该插件类 QPluginLoader 上场了。通过 QPluginLoader 载入后的实例,通过 QDesignerCustomWidgetCollectionInterface 类获取插件容 器,然后逐个遍历容器找出单个插件,包括获得类名+图标。 代码如下: ? 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 1 5 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(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; } } } 1 6 1 7 1 8 1 9 2 0 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8 2 9 3 0 3 1 3 2 效果图如下:
第四步:实例化 new 出控件并放到窗体。 拿到了所有的控件,前面还有个对应控件的小图标,是不是又有点小激动呢,接下来就是怎么双击或者拖动该控件到界面上立马实例化 一个控件出来。上一步我们将所有控件放到了一个链表变量 listWidgets 中,该变量在头文件中定义如下: QList listWidgets; 这里写了个函数,传入列表中控件的索引,即该类的索引位置,和控件默认要放置的坐标,即可在主界面生成该控件。 代码如下: ? 1 2 3 4 5 6 7 8 9 10 11 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 到属性设计器控件即可。 代码如下: ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 数据自动生成控件代码如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 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();
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 return; } file.close(); //先清空原有控件 QList widgets = ui->centralwidget->findChildren(); 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 > 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();
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 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 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 文件代码如下: ?
分享到:
收藏