qt更改类名_Qt编写自定义控件属性设计器
以前做.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编写自定义控件属性设计器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AGC002E Candy Piles
- 下一篇: 最大01子矩阵