qt更改类名_Qt编写自定义控件属性设计器
以前做.NET開發(fā)中,.NET直接就集成了屬性設(shè)計(jì)器,VS不愧是宇宙第一IDE,你能夠想到的都給你封裝好了,用起來不要太爽!因?yàn)轫?xiàng)目需要自從全面轉(zhuǎn)Qt開發(fā)已經(jīng)6年有余,在工業(yè)控制領(lǐng)域,有一些應(yīng)用場(chǎng)景需要自定義繪制一些控件滿足特定的需求,比如儀器儀表、組態(tài)等,而且需要直接用戶通過屬性設(shè)計(jì)的形式生成導(dǎo)出控件及界面數(shù)據(jù),下次導(dǎo)入使用,要想從內(nèi)置控件或者自定義控件拿到對(duì)應(yīng)的屬性方法等,首先聯(lián)想到的就是反射,Qt反射對(duì)應(yīng)的類叫QMetaObject,著實(shí)強(qiáng)大,其實(shí)整個(gè)Qt開發(fā)框架也是超級(jí)強(qiáng)大的,本人自從轉(zhuǎn)為Qt開發(fā)為主后,就深深的愛上了她,在其他跨平臺(tái)的GUI開發(fā)框架平臺(tái)面前,都會(huì)被Qt秒成渣,Qt的跨平臺(tái)性是毋庸置疑的,幾十兆的內(nèi)存存儲(chǔ)空間即可運(yùn)行,尤其是嵌入式linux這種資源相當(dāng)緊張的情況下,Qt的性能發(fā)揮到極致。
接下來我們就一步步利用QMetaObject類和QtPropertyBrower(第三方開源屬性設(shè)計(jì)器)來實(shí)現(xiàn)自己的控件屬性設(shè)計(jì)器,其中包含了所見即所得的控件屬性控制,以及xml數(shù)據(jù)的導(dǎo)入導(dǎo)出。
第一步:獲取控件的屬性名稱集合。
所有繼承自QObject類的類,都有元對(duì)象,都可以通過這個(gè)QObject類的元對(duì)象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上場(chǎng)了。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)這個(gè)過濾非常有用,因?yàn)檎鎸?shí)用到的大部分應(yīng)用場(chǎng)景都是控件類本身的屬性,而不是父類的。
第二步:將控件類綁定到屬性設(shè)計(jì)器。
拿到了控件的屬性是第一步,接下來就是需要拿到屬性所關(guān)聯(lián)的方法等,這里省略,因?yàn)镼tPropertyBrower這個(gè)屌爆了的第三方開源的屬性設(shè)計(jì)器,全部給我們寫好了,可以查看Qt幫助文檔或者QMetaObject的頭文件看到,QMetaObject提供了哪些接口去獲取或使用這些元信息。比如classInfo獲取類的信息、enumerator獲取枚舉值信息、method獲取方法,property獲取屬性、superClass獲取父類的名稱等。
QtPropertyBrower中提供了ObjectController類,該類繼承自QWidget,這樣的話我們?cè)诮缑嫔贤弦粋€(gè)QWidget控件,鼠標(biāo)右鍵提升為ObjectController即可。
這個(gè)輪子造的不要太好,我們只需要一行代碼就可以讓所有屬性自動(dòng)羅列到屬性設(shè)計(jì)器中,代碼是ui->objectController->setObject(btn);
看下效果如圖:
到這里是不是很興奮呢,任意控件都可以這樣來展示自己的屬性。在右側(cè)動(dòng)態(tài)更改屬性會(huì)立即應(yīng)用生效。
第三步:獲取自定義控件的插件的所有控件。
接下來這一步才是最關(guān)鍵的一步,以上舉例是Qt自帶控件的,如果是自定義控件插件比如就一個(gè)DLL文件呢,怎么辦?放心,辦法肯定是有的。
該插件類QPluginLoader上場(chǎng)了。通過QPluginLoader載入后的實(shí)例,通過QDesignerCustomWidgetCollectionInterface類獲取插件容器,然后逐個(gè)遍歷容器找出單個(gè)插件,包括獲得類名+圖標(biāo)。
代碼如下:
void frmMain::openPlugin(const QString &fileName) {qDeleteAll(listWidgets);listWidgets.clear();listNames.clear();ui->listWidget->clear();//加載自定義控件插件集合信息,包括獲得類名+圖標(biāo)QPluginLoader loader(fileName);if (loader.load()) {QObject *plugin = loader.instance();//獲取插件容器,然后逐個(gè)遍歷容器找出單個(gè)插件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;}} }效果圖如下:
第四步:實(shí)例化new出控件并放到窗體。
拿到了所有的控件,前面還有個(gè)對(duì)應(yīng)控件的小圖標(biāo),是不是又有點(diǎn)小激動(dòng)呢,接下來就是怎么雙擊或者拖動(dòng)該控件到界面上立馬實(shí)例化一個(gè)控件出來。上一步我們將所有控件放到了一個(gè)鏈表變量listWidgets中,該變量在頭文件中定義如下:
QList<QDesignerCustomWidgetInterface *> listWidgets;
這里寫了個(gè)函數(shù),傳入列表中控件的索引,即該類的索引位置,和控件默認(rèn)要放置的坐標(biāo),即可在主界面生成該控件。
代碼如下:
void frmMain::newWidget(int row, const QPoint &point) {//列表按照同樣的索引生成的,所以這里直接對(duì)該行的索引就行QWidget *widget = listWidgets.at(row)->createWidget(ui->centralwidget);widget->move(point);widget->resize(widget->sizeHint());//實(shí)例化選中窗體跟隨控件一起newSelect(widget);//立即執(zhí)行獲取焦點(diǎn)以及設(shè)置屬性widgetPressed(widget); }第五步:動(dòng)態(tài)綁定控件到設(shè)計(jì)器。
這一步就比較輕松了,上面提到過,直接獲取當(dāng)前界面上選中的是哪個(gè)控件,遍歷可以得到,然后設(shè)置object到屬性設(shè)計(jì)器控件即可。
代碼如下:
void frmMain::clearFocus() {//將原有焦點(diǎn)窗體全部設(shè)置成無焦點(diǎn)foreach (SelectWidget *widget, selectWidgets) {widget->setDrawPoint(false);} } void frmMain::widgetPressed(QWidget *widget) {//清空所有控件的焦點(diǎn)clearFocus();//設(shè)置當(dāng)前按下的控件有焦點(diǎn)foreach (SelectWidget *w, selectWidgets) {if (w->getWidget() == widget) {w->setDrawPoint(true);break;}}//設(shè)置自動(dòng)加載該控件的所有屬性u(píng)i->objectController->setObject(widget); }第六步:導(dǎo)入導(dǎo)出控件屬性到xml文件。
這一步比較難,本人也是花了好幾個(gè)小時(shí)才搞定,前后折騰了好多次,因?yàn)橛龅胶脦讉€(gè)棘手的問題,比如有些自定義控件中其實(shí)里邊封裝了Qt自帶的控件例如QPushButton等,如果遍歷控件設(shè)計(jì)窗體的所有控件,也會(huì)把該控件也遍歷進(jìn)去,所以要做過濾處理。
導(dǎo)入xml數(shù)據(jù)自動(dòng)生成控件代碼如下:
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();//存儲(chǔ)坐標(biāo)+寬高int x, y, width, height;//存儲(chǔ)其他自定義控件屬性QList<QPair<QString, QVariant> > propertys;//節(jié)點(diǎn)名稱不為空才繼續(xù)if (!name.isEmpty()) {//遍歷節(jié)點(diǎn)的屬性名稱和屬性值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;//優(yōu)先取出坐標(biāo)+寬高屬性,這幾個(gè)屬性不能通過setProperty實(shí)現(xiàn)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;//根據(jù)不同的控件類型實(shí)例化控件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);//逐個(gè)設(shè)置自定義控件的屬性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);}//設(shè)置坐標(biāo)+寬高widget->setGeometry(x, y, width, height);//實(shí)例化選中窗體跟隨控件一起newSelect(widget);break;}}//移動(dòng)到下一個(gè)節(jié)點(diǎn)node = node.nextSibling();element = node.toElement();}} }導(dǎo)出所有控件到xml文件代碼如下:
void frmMain::saveFile(const QString &fileName) {QFile file(fileName);if (!file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {return;}//以流的形式輸出文件QTextStream stream(&file);//構(gòu)建xml數(shù)據(jù)QStringList list;//添加固定頭部數(shù)據(jù)list << "<?xml version="1.0" encoding="UTF-8"?>";list << QString("<canvas width="%1" height="%2">").arg(ui->centralwidget->width()).arg(ui->centralwidget->height());//從容器中找到所有控件,根據(jù)控件的類名保存該類的所有屬性QList<QWidget *> widgets = ui->centralwidget->findChildren<QWidget *>();foreach (QWidget *w, widgets) {const QMetaObject *metaObject = w->metaObject();QString className = metaObject->className();QStringList values;//如果當(dāng)前控件的父類不是主窗體則無需導(dǎo)出,有些控件有子控件無需導(dǎo)出if (w->parent() != ui->centralwidget || className == "SelectWidget") {continue;}//metaObject->propertyOffset()表示當(dāng)前控件的屬性開始索引,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);//枚舉值要特殊處理,需要以字符串形式寫入,不然存儲(chǔ)到配置文件數(shù)據(jù)為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;}//逐個(gè)添加界面上的控件的屬性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; } //添加固定尾部數(shù)據(jù)list << "</canvas>";//寫入文件QString data = list.join("n");stream << data;file.close(); }xml數(shù)據(jù)格式效果圖:
完整效果圖:
總結(jié)
以上是生活随笔為你收集整理的qt更改类名_Qt编写自定义控件属性设计器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AGC002E Candy Piles
- 下一篇: [AHOI2014/JSOI2014]支