Qt通用属性工具:随心定义,随时可见(一)

一、开胃菜,没图我说个DIAO

先不BB,给大家上个效果图展示下:
在这里插入图片描述
上图我们也没干啥,几行代码:

#include "widget.h"
#include <QApplication>
#include <QObject>
#include "QtPropertyEditor.h"
#include <QHBoxLayout>int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;QtPropertyEditor::QtPropertyTreeEditor tree_editor(&w);QHBoxLayout* hlayout = new QHBoxLayout;hlayout->addWidget(&tree_editor);w.setLayout(hlayout);tree_editor.treeModel.propertyNames = QtPropertyEditor::getPropertyNames(&w);tree_editor.treeModel.setObject(&w);tree_editor.resizeColumnsToContents();w.show();return a.exec();
}

我们创建了一个最基本的QWidget对象,并将此对象作为属性展示对象传给了我们的通用属性编辑器。程序运行,帅气的属性编辑器展示出来了。当我们改变窗口时,属性编辑器中对应的数据也实时更新显示。很显然,MVC模式的运用跑不了了。属性编辑器啊属性编辑器,我们说了千万遍的Qt属性系统也是必然使用了的。准确的来说,这个属性编辑器就是基于属性系统实现的。对于 Qt属性系统 还不过关的朋友,可以去这篇《Qt 属性系统(The Property System )》 先做点准备功课。

二、核心代码

/* --------------------------------------------------------------------------------* QObject property editor UI.** Author: Marcel Paz Goldschen-Ohm  /// 尊重原作者,即使自己做了修改,也别偷偷摸摸抹除原作者* Email: marcel.goldschen@gmail.com* -------------------------------------------------------------------------------- */#ifndef __QtPropertyEditor_H__
#define __QtPropertyEditor_H__#include <functional>#include <QAbstractItemModel>
#include <QAction>
#include <QByteArray>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHash>
#include <QList>
#include <QMetaProperty>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QStyledItemDelegate>
#include <QTableView>
#include <QTreeView>
#include <QVariant>
#include <QVBoxLayout>#ifdef DEBUG
#include <iostream>
#include <QDebug>
#endifnamespace QtPropertyEditor
{// List all object property names.QList<QByteArray> getPropertyNames(QObject *object);QList<QByteArray> getMetaPropertyNames(const QMetaObject &metaObject);QList<QByteArray> getNoninheritedPropertyNames(QObject *object);// Handle descendant properties such as "child.grandchild.property".QObject* descendant(QObject *object, const QByteArray &pathToDescendantObject);// Get the size of a QTableView widget.QSize getTableSize(const QTableView *table);/* --------------------------------------------------------------------------------* Things that all QObject property models should be able to do.* -------------------------------------------------------------------------------- */class QtAbstractPropertyModel : public QAbstractItemModel{Q_OBJECTpublic:QtAbstractPropertyModel(QObject *parent = 0) : QAbstractItemModel(parent) {}QList<QByteArray> propertyNames;QHash<QByteArray, QString> propertyHeaders;void setProperties(const QString &str);void addProperty(const QString &str);virtual QObject* objectAtIndex(const QModelIndex &index) const = 0;virtual QByteArray propertyNameAtIndex(const QModelIndex &index) const = 0;const QMetaProperty metaPropertyAtIndex(const QModelIndex &index) const;virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);virtual Qt::ItemFlags flags(const QModelIndex &index) const;};/* --------------------------------------------------------------------------------* Property tree model for a QObject tree.* Max tree depth can be specified (i.e. depth = 0 --> single object only).* -------------------------------------------------------------------------------- */class QtPropertyTreeModel : public QtAbstractPropertyModel{Q_OBJECTpublic:// Internal tree node.struct Node{// Node traversal.Node *parent = NULL;QList<Node*> children;// Node data.QObject *object = NULL;QByteArray propertyName;Node(Node *parent = NULL) : parent(parent) {}~Node() { qDeleteAll(children); }void setObject(QObject *object, int maxChildDepth = -1, const QList<QByteArray> &propertyNames = QList<QByteArray>());};QtPropertyTreeModel(QObject *parent = NULL) : QtAbstractPropertyModel(parent) {}// Getters.QObject* object() const { return _root.object; }int maxDepth() const { return _maxTreeDepth; }// Setters.void setObject(QObject *object) { beginResetModel(); _root.setObject(object, _maxTreeDepth, propertyNames); endResetModel(); }void setMaxDepth(int i) { beginResetModel(); _maxTreeDepth = i; reset(); endResetModel(); }void setProperties(const QString &str) { beginResetModel(); QtAbstractPropertyModel::setProperties(str); reset(); endResetModel(); }void addProperty(const QString &str) { beginResetModel(); QtAbstractPropertyModel::addProperty(str); reset(); endResetModel(); }// Model interface.Node* nodeAtIndex(const QModelIndex &index) const;QObject* objectAtIndex(const QModelIndex &index) const;QByteArray propertyNameAtIndex(const QModelIndex &index) const;QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;QModelIndex parent(const QModelIndex &index) const;int rowCount(const QModelIndex &parent = QModelIndex()) const;int columnCount(const QModelIndex &parent = QModelIndex()) const;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);Qt::ItemFlags flags(const QModelIndex &index) const;QVariant headerData(int section, Qt::Orientation orientation, int role) const;public slots:void reset() { setObject(object()); }protected:Node _root;int _maxTreeDepth = -1;};/* --------------------------------------------------------------------------------* Property table model for a list of QObjects (rows are objects, columns are properties).* -------------------------------------------------------------------------------- */class QtPropertyTableModel : public QtAbstractPropertyModel{Q_OBJECTpublic:typedef std::function<QObject*()> ObjectCreatorFunction;QtPropertyTableModel(QObject *parent = NULL) : QtAbstractPropertyModel(parent) {}// Getters.QObjectList objects() const { return _objects; }ObjectCreatorFunction objectCreator() const { return _objectCreator; }// Setters.void setObjects(const QObjectList &objects) { beginResetModel(); _objects = objects; endResetModel(); }template <class T>void setObjects(const QList<T*> &objects);template <class T>void setChildObjects(QObject *parent);void setObjectCreator(ObjectCreatorFunction creator) { _objectCreator = creator; }void setProperties(const QString &str) { beginResetModel(); QtAbstractPropertyModel::setProperties(str); endResetModel(); }void addProperty(const QString &str) { beginResetModel(); QtAbstractPropertyModel::addProperty(str); endResetModel(); }// Model interface.QObject* objectAtIndex(const QModelIndex &index) const;QByteArray propertyNameAtIndex(const QModelIndex &index) const;QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;QModelIndex parent(const QModelIndex &index) const;int rowCount(const QModelIndex &parent = QModelIndex()) const;int columnCount(const QModelIndex &parent = QModelIndex()) const;QVariant headerData(int section, Qt::Orientation orientation, int role) const;bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationRow);void reorderChildObjectsToMatchRowOrder(int firstRow = 0);// Default creator functions for convenience.// Requires template class T to implement a default constructor T().template <class T>static QObject* defaultCreator() { return new T(); }template <class T>static QObject* defaultChildCreator(QObject *parent) { T *object = new T(); object->setParent(parent); return object; }signals:void rowCountChanged();void rowOrderChanged();protected:QObjectList _objects;ObjectCreatorFunction _objectCreator = NULL;};template <class T>void QtPropertyTableModel::setObjects(const QList<T*> &objects){beginResetModel();_objects.clear();foreach(T *object, objects) {if(QObject *obj = qobject_cast<QObject*>(object))_objects.append(obj);}endResetModel();}template <class T>void QtPropertyTableModel::setChildObjects(QObject *parent){beginResetModel();_objects.clear();foreach(T *derivedObject, parent->findChildren<T*>(QString(), Qt::FindDirectChildrenOnly)) {if(QObject *object = qobject_cast<QObject*>(derivedObject))_objects.append(object);}_objectCreator = std::bind(&QtPropertyTableModel::defaultChildCreator<T>, parent);endResetModel();}/* --------------------------------------------------------------------------------* Property editor delegate.* -------------------------------------------------------------------------------- */class QtPropertyDelegate: public QStyledItemDelegate{public:QtPropertyDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {}QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const Q_DECL_OVERRIDE;QString displayText(const QVariant &value, const QLocale &locale) const Q_DECL_OVERRIDE;void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;protected:bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) Q_DECL_OVERRIDE;};/* --------------------------------------------------------------------------------* User types for QVariant that will be handled by QtPropertyDelegate.* User types need to be declared via Q_DECLARE_METATYPE (see below outside of namespace)*   and also registered via qRegisterMetaType (see static instantiation in .cpp file)* -------------------------------------------------------------------------------- */// For static registration of user types (see static instantiation in QtPropertyEditor.cpp).template <typename Type> class MetaTypeRegistration{public:inline MetaTypeRegistration(){qRegisterMetaType<Type>();}};// For push buttons.// See Q_DECLARE_METATYPE below and qRegisterMetaType in .cpp file.class QtPushButtonActionWrapper{public:QtPushButtonActionWrapper(QAction *action = NULL) : action(action) {}QtPushButtonActionWrapper(const QtPushButtonActionWrapper &other) { action = other.action; }~QtPushButtonActionWrapper() {}QAction *action = NULL;};/* --------------------------------------------------------------------------------* Tree editor for properties in a QObject tree.* -------------------------------------------------------------------------------- */class QtPropertyTreeEditor : public QTreeView{Q_OBJECTpublic:QtPropertyTreeEditor(QWidget *parent = NULL);// Owns its own tree model for convenience. This means model will be deleted along with editor.// However, you're not forced to use this model.QtPropertyTreeModel treeModel;public slots:void resizeColumnsToContents();protected:QtPropertyDelegate _delegate;};/* --------------------------------------------------------------------------------* Table editor for properties in a list of QObjects.* -------------------------------------------------------------------------------- */class QtPropertyTableEditor : public QTableView{Q_OBJECTpublic:QtPropertyTableEditor(QWidget *parent = NULL);// Owns its own table model for convenience. This means model will be deleted along with editor.// However, you're not forced to use this model.QtPropertyTableModel tableModel;bool isDynamic() const { return _isDynamic; }void setIsDynamic(bool b);QSize sizeHint() const Q_DECL_OVERRIDE { return getTableSize(this); }public slots:void horizontalHeaderContextMenu(QPoint pos);void verticalHeaderContextMenu(QPoint pos);void appendRow();void insertSelectedRows();void removeSelectedRows();void handleSectionMove(int logicalIndex, int oldVisualIndex, int newVisualIndex);protected:QtPropertyDelegate _delegate;bool _isDynamic = true;void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE;bool eventFilter(QObject* o, QEvent* e) Q_DECL_OVERRIDE;};} // QtPropertyEditorQ_DECLARE_METATYPE(QtPropertyEditor::QtPushButtonActionWrapper);#endif // __QtPropertyEditor_H__
/* --------------------------------------------------------------------------------* Author: Marcel Paz Goldschen-Ohm* Email: marcel.goldschen@gmail.com* -------------------------------------------------------------------------------- */#include "QtPropertyEditor.h"#include <QAbstractButton>
#include <QApplication>
#include <QComboBox>
#include <QEvent>
#include <QHeaderView>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QMetaObject>
#include <QMetaType>
#include <QMouseEvent>
#include <QPushButton>
#include <QRegularExpression>
#include <QScrollBar>
#include <QStylePainter>
#include <QToolButton>
#include <QDebug>namespace QtPropertyEditor
{static MetaTypeRegistration<QtPushButtonActionWrapper> thisInstantiationRegistersQtPushButtonActionWrapperWithQt;QList<QByteArray> getPropertyNames(QObject *object){QList<QByteArray> propertyNames = getMetaPropertyNames(*object->metaObject());foreach(const QByteArray &dynamicPropertyName, object->dynamicPropertyNames()) {propertyNames << dynamicPropertyName;}return propertyNames;}QList<QByteArray> getMetaPropertyNames(const QMetaObject &metaObject){QList<QByteArray> propertyNames;int numProperties = metaObject.propertyCount();for(int i = 0; i < numProperties; ++i) {const QMetaProperty metaProperty = metaObject.property(i);propertyNames << QByteArray(metaProperty.name());}return propertyNames;}QList<QByteArray> getNoninheritedPropertyNames(QObject *object){QList<QByteArray> propertyNames = getPropertyNames(object);QList<QByteArray> superPropertyNames = getMetaPropertyNames(*object->metaObject()->superClass());foreach(const QByteArray &superPropertyName, superPropertyNames) {propertyNames.removeOne(superPropertyName);}return propertyNames;}QObject* descendant(QObject *object, const QByteArray &pathToDescendantObject){// Get descendent object specified by "path.to.descendant", where "path", "to" and "descendant"// are the object names of objects with the parent->child relationship object->path->to->descendant.if(!object || pathToDescendantObject.isEmpty())return 0;if(pathToDescendantObject.contains('.')) {QList<QByteArray> descendantObjectNames = pathToDescendantObject.split('.');foreach(QByteArray name, descendantObjectNames) {object = object->findChild<QObject*>(QString(name));if(!object)return 0; // Invalid path to descendant object.}return object;}return object->findChild<QObject*>(QString(pathToDescendantObject));}QSize getTableSize(const QTableView *table){int w = table->verticalHeader()->width() + 4; // +4 seems to be neededint h = table->horizontalHeader()->height() + 4;for(int i = 0; i < table->model()->columnCount(); i++)w += table->columnWidth(i);for(int i = 0; i < table->model()->rowCount(); i++)h += table->rowHeight(i);return QSize(w, h);}void QtAbstractPropertyModel::setProperties(const QString &str){// str = "name0: header0, name1, name2, name3: header3 ..."propertyNames.clear();propertyHeaders.clear();QStringList fields = str.split(",", QString::SkipEmptyParts);foreach(const QString &field, fields) {if(!field.trimmed().isEmpty())addProperty(field);}}void QtAbstractPropertyModel::addProperty(const QString &str){// "name" OR "name: header"if(str.contains(":")) {int pos = str.indexOf(":");QByteArray propertyName = str.left(pos).trimmed().toUtf8();QString propertyHeader = str.mid(pos+1).trimmed();propertyNames.push_back(propertyName);propertyHeaders[propertyName] = propertyHeader;} else {QByteArray propertyName = str.trimmed().toUtf8();propertyNames.push_back(propertyName);}}const QMetaProperty QtAbstractPropertyModel::metaPropertyAtIndex(const QModelIndex &index) const{QObject *object = objectAtIndex(index);if(!object)return QMetaProperty();QByteArray propertyName = propertyNameAtIndex(index);if(propertyName.isEmpty())return QMetaProperty();// Return metaObject with same name.const QMetaObject *metaObject = object->metaObject();int numProperties = metaObject->propertyCount();for(int i = 0; i < numProperties; ++i) {const QMetaProperty metaProperty = metaObject->property(i);if(QByteArray(metaProperty.name()) == propertyName)return metaProperty;}return QMetaProperty();}QVariant QtAbstractPropertyModel::data(const QModelIndex &index, int role) const{if(!index.isValid())return QVariant();if(role == Qt::DisplayRole || role == Qt::EditRole) {QObject *object = objectAtIndex(index);if(!object)return QVariant();QByteArray propertyName = propertyNameAtIndex(index);if(propertyName.isEmpty())return QVariant();return object->property(propertyName.constData());}return QVariant();}bool QtAbstractPropertyModel::setData(const QModelIndex &index, const QVariant &value, int role){if(!index.isValid())return false;if(role == Qt::EditRole) {QObject *object = objectAtIndex(index);if(!object)return false;QByteArray propertyName = propertyNameAtIndex(index);if(propertyName.isEmpty())return false;bool result = object->setProperty(propertyName.constData(), value);// Result will be FALSE for dynamic properties, which causes the tree view to lag.// So make sure we still return TRUE in this case.if(!result && object->dynamicPropertyNames().contains(propertyName))return true;return result;}return false;}Qt::ItemFlags QtAbstractPropertyModel::flags(const QModelIndex &index) const{Qt::ItemFlags flags = QAbstractItemModel::flags(index);if(!index.isValid())return flags;QObject *object = objectAtIndex(index);if(!object)return flags;flags |= Qt::ItemIsEnabled;flags |= Qt::ItemIsSelectable;QByteArray propertyName = propertyNameAtIndex(index);const QMetaProperty metaProperty = metaPropertyAtIndex(index);if(metaProperty.isWritable() || object->dynamicPropertyNames().contains(propertyName))flags |= Qt::ItemIsEditable;return flags;}void QtPropertyTreeModel::Node::setObject(QObject *object, int maxChildDepth, const QList<QByteArray> &propertyNames){this->object = object;propertyName.clear();qDeleteAll(children);children.clear();if(!object) return;// Compiled properties (but exclude objectName as this is displayed for the object node itself).const QMetaObject *metaObject = object->metaObject();int numProperties = metaObject->propertyCount();for(int i = 0; i < numProperties; ++i) {const QMetaProperty metaProperty = metaObject->property(i);QByteArray propertyName = QByteArray(metaProperty.name());if(propertyNames.isEmpty() || propertyNames.contains(propertyName)) {Node *node = new Node(this);node->propertyName = propertyName;children.append(node);}}// Dynamic properties.QList<QByteArray> dynamicPropertyNames = object->dynamicPropertyNames();foreach(const QByteArray &propertyName, dynamicPropertyNames) {if(propertyNames.isEmpty() || propertyNames.contains(propertyName)) {Node *node = new Node(this);node->propertyName = propertyName;children.append(node);}}// Child objects.if(maxChildDepth > 0 || maxChildDepth == -1) {if(maxChildDepth > 0)--maxChildDepth;QMap<QByteArray, QObjectList> childMap;foreach(QObject *child, object->children()) {childMap[QByteArray(child->metaObject()->className())].append(child);}for(auto it = childMap.begin(); it != childMap.end(); ++it) {foreach(QObject *child, it.value()) {Node *node = new Node(this);node->setObject(child, maxChildDepth, propertyNames);children.append(node);}}}}QtPropertyTreeModel::Node* QtPropertyTreeModel::nodeAtIndex(const QModelIndex &index) const{try {return static_cast<Node*>(index.internalPointer());} catch(...) {return NULL;}}QObject* QtPropertyTreeModel::objectAtIndex(const QModelIndex &index) const{// If node is an object, return the node's object.// Else if node is a property, return the parent node's object.Node *node = nodeAtIndex(index);if(!node) return NULL;if(node->object) return node->object;if(node->parent) return node->parent->object;return NULL;}QByteArray QtPropertyTreeModel::propertyNameAtIndex(const QModelIndex &index) const{// If node is a property, return the node's property name.// Else if node is an object, return "objectName".Node *node = nodeAtIndex(index);if(!node) return QByteArray();if(!node->propertyName.isEmpty()) return node->propertyName;return QByteArray();}QModelIndex QtPropertyTreeModel::index(int row, int column, const QModelIndex &parent) const{// Return a model index whose internal pointer references the appropriate tree node.if(column < 0 || column >= 2 || !hasIndex(row, column, parent))return QModelIndex();const Node *parentNode = parent.isValid() ? nodeAtIndex(parent) : &_root;if(!parentNode || row < 0 || row >= parentNode->children.size())return QModelIndex();Node *node = parentNode->children.at(row);return node ? createIndex(row, column, node) : QModelIndex();}QModelIndex QtPropertyTreeModel::parent(const QModelIndex &index) const{// Return a model index for parent node (column must be 0).if(!index.isValid())return QModelIndex();Node *node = nodeAtIndex(index);if(!node)return QModelIndex();Node *parentNode = node->parent;if(!parentNode || parentNode == &_root)return QModelIndex();int row = 0;Node *grandparentNode = parentNode->parent;if(grandparentNode)row = grandparentNode->children.indexOf(parentNode);return createIndex(row, 0, parentNode);}int QtPropertyTreeModel::rowCount(const QModelIndex &parent) const{// Return number of child nodes.const Node *parentNode = parent.isValid() ? nodeAtIndex(parent) : &_root;return parentNode ? parentNode->children.size() : 0;}int QtPropertyTreeModel::columnCount(const QModelIndex &parent) const{// Return 2 for name/value columns.const Node *parentNode = parent.isValid() ? nodeAtIndex(parent) : &_root;return (parentNode ? 2 : 0);}QVariant QtPropertyTreeModel::data(const QModelIndex &index, int role) const{if(!index.isValid())return QVariant();if(role == Qt::DisplayRole || role == Qt::EditRole) {QObject *object = objectAtIndex(index);if(!object)return QVariant();QByteArray propertyName = propertyNameAtIndex(index);if(index.column() == 0) {// Object's class name or else the property name.if(propertyName.isEmpty())return QVariant(object->metaObject()->className());else if(propertyHeaders.contains(propertyName))return QVariant(propertyHeaders[propertyName]);elsereturn QVariant(propertyName);} else if(index.column() == 1) {// Object's objectName or else the property value.if(propertyName.isEmpty())return QVariant(object->objectName());elsereturn object->property(propertyName.constData());}}return QVariant();}bool QtPropertyTreeModel::setData(const QModelIndex &index, const QVariant &value, int role){if(!index.isValid())return false;if(role == Qt::EditRole) {QObject *object = objectAtIndex(index);if(!object)return false;QByteArray propertyName = propertyNameAtIndex(index);if(index.column() == 0) {// Object class name or property name.return false;} else if(index.column() == 1) {// Object's objectName or else the property value.if(propertyName.isEmpty()) {object->setObjectName(value.toString());return true;} else {bool result = object->setProperty(propertyName.constData(), value);// Result will be FALSE for dynamic properties, which causes the tree view to lag.// So make sure we still return TRUE in this case.if(!result && object->dynamicPropertyNames().contains(propertyName))return true;return result;}}}return false;}Qt::ItemFlags QtPropertyTreeModel::flags(const QModelIndex &index) const{Qt::ItemFlags flags = QAbstractItemModel::flags(index);if(!index.isValid())return flags;QObject *object = objectAtIndex(index);if(!object)return flags;flags |= Qt::ItemIsEnabled;flags |= Qt::ItemIsSelectable;if(index.column() == 1) {QByteArray propertyName = propertyNameAtIndex(index);const QMetaProperty metaProperty = metaPropertyAtIndex(index);if(metaProperty.isWritable() || object->dynamicPropertyNames().contains(propertyName) || objectAtIndex(index))flags |= Qt::ItemIsEditable;}return flags;}QVariant QtPropertyTreeModel::headerData(int section, Qt::Orientation orientation, int role) const{if(role == Qt::DisplayRole) {if(orientation == Qt::Horizontal) {if(section == 0)return QVariant("Name");else if(section == 1)return QVariant("Value");else if(section == 3){return QVariant("type");}}}return QVariant();}QObject* QtPropertyTableModel::objectAtIndex(const QModelIndex &index) const{if(_objects.size() <= index.row())return 0;QObject *object = _objects.at(index.row());// If property names are specified, check if name at column is a path to a child object property.if(!propertyNames.isEmpty()) {if(propertyNames.size() > index.column()) {QByteArray propertyName = propertyNames.at(index.column());if(propertyName.contains('.')) {int pos = propertyName.lastIndexOf('.');return descendant(object, propertyName.left(pos));}}}return object;}QByteArray QtPropertyTableModel::propertyNameAtIndex(const QModelIndex &index) const{// If property names are specified, return the name at column.if(!propertyNames.isEmpty()) {if(propertyNames.size() > index.column()) {QByteArray propertyName = propertyNames.at(index.column());if(propertyName.contains('.')) {int pos = propertyName.lastIndexOf('.');return propertyName.mid(pos + 1);}return propertyName;}return QByteArray();}// If property names are NOT specified, return the metaObject's property name at column.QObject *object = objectAtIndex(index);if(!object)return QByteArray();const QMetaObject *metaObject = object->metaObject();int numProperties = metaObject->propertyCount();if(numProperties > index.column())return QByteArray(metaObject->property(index.column()).name());// If column is greater than the number of metaObject properties, check for dynamic properties.const QList<QByteArray> &dynamicPropertyNames = object->dynamicPropertyNames();if(numProperties + dynamicPropertyNames.size() > index.column())return dynamicPropertyNames.at(index.column() - numProperties);return QByteArray();}QModelIndex QtPropertyTableModel::index(int row, int column, const QModelIndex &/* parent */) const{return createIndex(row, column);}QModelIndex QtPropertyTableModel::parent(const QModelIndex &/* index */) const{return QModelIndex();}int QtPropertyTableModel::rowCount(const QModelIndex &/* parent */) const{return _objects.size();}int QtPropertyTableModel::columnCount(const QModelIndex &/* parent */) const{// Number of properties.if(!propertyNames.isEmpty())return propertyNames.size();if(_objects.isEmpty())return 0;QObject *object = _objects.at(0);const QMetaObject *metaObject = object->metaObject();return metaObject->propertyCount() + object->dynamicPropertyNames().size();}QVariant QtPropertyTableModel::headerData(int section, Qt::Orientation orientation, int role) const{if(role == Qt::DisplayRole) {if(orientation == Qt::Vertical) {return QVariant(section);} else if(orientation == Qt::Horizontal) {QByteArray propertyName = propertyNameAtIndex(createIndex(0, section));QByteArray childPath;if(propertyNames.size() > section) {QByteArray pathToPropertyName = propertyNames.at(section);if(pathToPropertyName.contains('.')) {int pos = pathToPropertyName.lastIndexOf('.');childPath = pathToPropertyName.left(pos + 1);}}if(propertyHeaders.contains(propertyName))return QVariant(childPath + propertyHeaders.value(propertyName));return QVariant(childPath + propertyName);}}return QVariant();}bool QtPropertyTableModel::insertRows(int row, int count, const QModelIndex &parent){// Only valid if we have an object creator method.if(!_objectCreator)return false;bool columnCountWillAlsoChange = _objects.isEmpty() && propertyNames.isEmpty();beginInsertRows(parent, row, row + count - 1);for(int i = row; i < row + count; ++i) {QObject *object = _objectCreator();_objects.insert(i, object);}endInsertRows();if(row + count < _objects.size())reorderChildObjectsToMatchRowOrder(row + count);if(columnCountWillAlsoChange) {beginResetModel();endResetModel();}emit rowCountChanged();return true;}bool QtPropertyTableModel::removeRows(int row, int count, const QModelIndex &parent){beginRemoveRows(parent, row, row + count - 1);for(int i = row; i < row + count; ++i)delete _objects.at(i);QObjectList::iterator begin = _objects.begin() + row;_objects.erase(begin, begin + count);endRemoveRows();emit rowCountChanged();return true;}bool QtPropertyTableModel::moveRows(const QModelIndex &/*sourceParent*/, int sourceRow, int count, const QModelIndex &/*destinationParent*/, int destinationRow){beginResetModel();QObjectList objectsToMove;for(int i = sourceRow; i < sourceRow + count; ++i)objectsToMove.append(_objects.takeAt(sourceRow));for(int i = 0; i < objectsToMove.size(); ++i) {if(destinationRow + i >= _objects.size())_objects.append(objectsToMove.at(i));else_objects.insert(destinationRow + i, objectsToMove.at(i));}endResetModel();reorderChildObjectsToMatchRowOrder(sourceRow <= destinationRow ? sourceRow : destinationRow);emit rowOrderChanged();return true;}void QtPropertyTableModel::reorderChildObjectsToMatchRowOrder(int firstRow){for(int i = firstRow; i < rowCount(); ++i) {QObject *object = objectAtIndex(createIndex(i, 0));if(object) {QObject *parent = object->parent();if(parent) {object->setParent(NULL);object->setParent(parent);}}}}QWidget* QtPropertyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const{QVariant value = index.data(Qt::DisplayRole);if(value.isValid()) {if(value.type() == QVariant::Bool) {// We want a check box, but instead of creating an editor widget we'll just directly// draw the check box in paint() and handle mouse clicks in editorEvent().// Here, we'll just return NULL to make sure that no editor is created when this cell is double clicked.return NULL;} else if(value.type() == QVariant::Double) {// Return a QLineEdit to enter double values with arbitrary precision and scientific notation.QLineEdit *editor = new QLineEdit(parent);editor->setText(value.toString());return editor;} else if(value.type() == QVariant::Int) {// We don't need to do anything special for an integer, we'll just use the default QSpinBox.// However, we do need to check if it is an enum. If so, we'll use a QComboBox editor.const QtAbstractPropertyModel *propertyModel = qobject_cast<const QtAbstractPropertyModel*>(index.model());if(propertyModel) {const QMetaProperty metaProperty = propertyModel->metaPropertyAtIndex(index);if(metaProperty.isValid() && metaProperty.isEnumType()) {const QMetaEnum metaEnum = metaProperty.enumerator();int numKeys = metaEnum.keyCount();if(numKeys > 0) {QComboBox *editor = new QComboBox(parent);for(int j = 0; j < numKeys; ++j) {QByteArray key = QByteArray(metaEnum.key(j));editor->addItem(QString(key));}QByteArray currentKey = QByteArray(metaEnum.valueToKey(value.toInt()));editor->setCurrentText(QString(currentKey));return editor;}}}} else if(value.type() == QVariant::Size || value.type() == QVariant::SizeF ||value.type() == QVariant::Point || value.type() == QVariant::PointF ||value.type() == QVariant::Rect || value.type() == QVariant::RectF) {// Return a QLineEdit. Parsing will be done in displayText() and setEditorData().QLineEdit *editor = new QLineEdit(parent);editor->setText(displayText(value, QLocale()));return editor;} else if(value.type() == QVariant::UserType) {if(value.canConvert<QtPushButtonActionWrapper>()) {// We want a push button, but instead of creating an editor widget we'll just directly// draw the button in paint() and handle mouse clicks in editorEvent().// Here, we'll just return NULL to make sure that no editor is created when this cell is double clicked.return NULL;}}}return QStyledItemDelegate::createEditor(parent, option, index);}void QtPropertyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const{QStyledItemDelegate::setEditorData(editor, index);}void QtPropertyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const{QVariant value = index.data(Qt::DisplayRole);if(value.isValid()) {if(value.type() == QVariant::Double) {// Set model's double value data to numeric representation in QLineEdit editor.// Conversion from text to number handled by QVariant.QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {QVariant value = QVariant(lineEditor->text());bool ok;double dval = value.toDouble(&ok);if(ok)model->setData(index, QVariant(dval), Qt::EditRole);return;}} else if(value.type() == QVariant::Int) {// We don't need to do anything special for an integer.// However, if it's an enum we'll set the data based on the QComboBox editor.QComboBox *comboBoxEditor = qobject_cast<QComboBox*>(editor);if(comboBoxEditor) {QString selectedKey = comboBoxEditor->currentText();const QtAbstractPropertyModel *propertyModel = qobject_cast<const QtAbstractPropertyModel*>(model);if(propertyModel) {const QMetaProperty metaProperty = propertyModel->metaPropertyAtIndex(index);if(metaProperty.isValid() && metaProperty.isEnumType()) {const QMetaEnum metaEnum = metaProperty.enumerator();bool ok;int selectedValue = metaEnum.keyToValue(selectedKey.toLatin1().constData(), &ok);if(ok)model->setData(index, QVariant(selectedValue), Qt::EditRole);return;}}// If we got here, we have a QComboBox editor but the property at index is not an enum.}} else if(value.type() == QVariant::Size) {QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {// Parse formats: (w x h) or (w,h) or (w h) <== () are optionalQRegularExpression regex("\\s*\\(?\\s*(\\d+)\\s*[x,\\s]\\s*(\\d+)\\s*\\)?\\s*");QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());if(match.hasMatch() && match.capturedTexts().size() == 3) {bool wok, hok;int w = match.captured(1).toInt(&wok);int h = match.captured(2).toInt(&hok);if(wok && hok)model->setData(index, QVariant(QSize(w, h)), Qt::EditRole);}}} else if(value.type() == QVariant::SizeF) {QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {// Parse formats: (w x h) or (w,h) or (w h) <== () are optionalQRegularExpression regex("\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[x,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*");QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());if(match.hasMatch() && match.capturedTexts().size() == 3) {bool wok, hok;double w = match.captured(1).toDouble(&wok);double h = match.captured(2).toDouble(&hok);if(wok && hok)model->setData(index, QVariant(QSizeF(w, h)), Qt::EditRole);}}} else if(value.type() == QVariant::Point) {QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {// Parse formats: (x,y) or (x y) <== () are optionalQRegularExpression regex("\\s*\\(?\\s*(\\d+)\\s*[x,\\s]\\s*(\\d+)\\s*\\)?\\s*");QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());if(match.hasMatch() && match.capturedTexts().size() == 3) {bool xok, yok;int x = match.captured(1).toInt(&xok);int y = match.captured(2).toInt(&yok);if(xok && yok)model->setData(index, QVariant(QPoint(x, y)), Qt::EditRole);}}} else if(value.type() == QVariant::PointF) {QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {// Parse formats: (x,y) or (x y) <== () are optionalQRegularExpression regex("\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[x,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*");QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());if(match.hasMatch() && match.capturedTexts().size() == 3) {bool xok, yok;double x = match.captured(1).toDouble(&xok);double y = match.captured(2).toDouble(&yok);if(xok && yok)model->setData(index, QVariant(QPointF(x, y)), Qt::EditRole);}}} else if(value.type() == QVariant::Rect) {QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {// Parse formats: [Point,Size] or [Point Size] <== [] are optional// Point formats: (x,y) or (x y) <== () are optional// Size formats: (w x h) or (w,h) or (w h) <== () are optionalQRegularExpression regex("\\s*\\[?""\\s*\\(?\\s*(\\d+)\\s*[,\\s]\\s*(\\d+)\\s*\\)?\\s*""[,\\s]""\\s*\\(?\\s*(\\d+)\\s*[x,\\s]\\s*(\\d+)\\s*\\)?\\s*""\\]?\\s*");QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());if(match.hasMatch() && match.capturedTexts().size() == 5) {bool xok, yok, wok, hok;int x = match.captured(1).toInt(&xok);int y = match.captured(2).toInt(&yok);int w = match.captured(3).toInt(&wok);int h = match.captured(4).toInt(&hok);if(xok && yok && wok && hok)model->setData(index, QVariant(QRect(x, y, w, h)), Qt::EditRole);}}} else if(value.type() == QVariant::RectF) {QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);if(lineEditor) {// Parse formats: [Point,Size] or [Point Size] <== [] are optional// Point formats: (x,y) or (x y) <== () are optional// Size formats: (w x h) or (w,h) or (w h) <== () are optionalQRegularExpression regex("\\s*\\[?""\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*""[,\\s]""\\s*\\(?\\s*([0-9\\+\\-\\.eE]+)\\s*[x,\\s]\\s*([0-9\\+\\-\\.eE]+)\\s*\\)?\\s*""\\]?\\s*");QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());if(match.hasMatch() && match.capturedTexts().size() == 5) {bool xok, yok, wok, hok;double x = match.captured(1).toDouble(&xok);double y = match.captured(2).toDouble(&yok);double w = match.captured(3).toDouble(&wok);double h = match.captured(4).toDouble(&hok);if(xok && yok && wok && hok)model->setData(index, QVariant(QRectF(x, y, w, h)), Qt::EditRole);}}//        } else if(value.type() == QVariant::Color) {//            QLineEdit *lineEditor = qobject_cast<QLineEdit*>(editor);//            if(lineEditor) {//                // Parse formats: (r,g,b) or (r g b) or (r,g,b,a) or (r g b a) <== () are optional//                QRegularExpression regex("\\s*\\(?"//                                         "\\s*(\\d+)\\s*"//                                         "[,\\s]\\s*(\\d+)\\s*"//                                         "[,\\s]\\s*(\\d+)\\s*"//                                         "([,\\s]\\s*(\\d+)\\s*)?"//                                         "\\)?\\s*");//                QRegularExpressionMatch match = regex.match(lineEditor->text().trimmed());//                if(match.hasMatch() && (match.capturedTexts().size() == 4 || match.capturedTexts().size() == 5)) {//                    bool rok, gok, bok, aok;//                    int r = match.captured(1).toInt(&rok);//                    int g = match.captured(2).toInt(&gok);//                    int b = match.captured(3).toInt(&bok);//                    if(match.capturedTexts().size() == 4) {//                        if(rok && gok && bok)//                            model->setData(index, QColor(r, g, b), Qt::EditRole);//                    } else if(match.capturedTexts().size() == 5) {//                        int a = match.captured(4).toInt(&aok);//                        if(rok && gok && bok && aok)//                            model->setData(index, QColor(r, g, b, a), Qt::EditRole);//                    }//                }//            }}}QStyledItemDelegate::setModelData(editor, model, index);}QString QtPropertyDelegate::displayText(const QVariant &value, const QLocale &locale) const{if(value.isValid()) {if(value.type() == QVariant::Size) {// w x hQSize size = value.toSize();return QString::number(size.width()) + QString(" x ") + QString::number(size.height());} else if(value.type() == QVariant::SizeF) {// w x hQSizeF size = value.toSizeF();return QString::number(size.width()) + QString(" x ") + QString::number(size.height());} else if(value.type() == QVariant::Point) {// (x, y)QPoint point = value.toPoint();return QString("(")+ QString::number(point.x()) + QString(", ") + QString::number(point.y())+ QString(")");} else if(value.type() == QVariant::PointF) {// (x, y)QPointF point = value.toPointF();return QString("(")+ QString::number(point.x()) + QString(", ") + QString::number(point.y())+ QString(")");} else if(value.type() == QVariant::Rect) {// [(x, y), w x h]QRect rect = value.toRect();return QString("[(")+ QString::number(rect.x()) + QString(", ") + QString::number(rect.y())+ QString("), ")+ QString::number(rect.width()) + QString(" x ") + QString::number(rect.height())+ QString("]");} else if(value.type() == QVariant::RectF) {// [(x, y), w x h]QRectF rect = value.toRectF();return QString("[(")+ QString::number(rect.x()) + QString(", ") + QString::number(rect.y())+ QString("), ")+ QString::number(rect.width()) + QString(" x ") + QString::number(rect.height())+ QString("]");//        } else if(value.type() == QVariant::Color) {//            // (r, g, b, a)//            QColor color = value.value<QColor>();//            return QString("(")//                    + QString::number(color.red()) + QString(", ") + QString::number(color.green()) + QString(", ")//                    + QString::number(color.blue()) + QString(", ") + QString::number(color.alpha())//                    + QString(")");}}return QStyledItemDelegate::displayText(value, locale);}void QtPropertyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{QVariant value = index.data(Qt::DisplayRole);if(value.isValid()) {if(value.type() == QVariant::Bool) {bool checked = value.toBool();QStyleOptionButton buttonOption;buttonOption.state |= QStyle::State_Active; // Required!buttonOption.state |= ((index.flags() & Qt::ItemIsEditable) ? QStyle::State_Enabled : QStyle::State_ReadOnly);buttonOption.state |= (checked ? QStyle::State_On : QStyle::State_Off);QRect checkBoxRect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &buttonOption); // Only used to get size of native checkbox widget.buttonOption.rect = QStyle::alignedRect(option.direction, Qt::AlignLeft, checkBoxRect.size(), option.rect); // Our checkbox rect.QApplication::style()->drawControl(QStyle::CE_CheckBox, &buttonOption, painter);return;} else if(value.type() == QVariant::Int) {// We don't need to do anything special for an integer.// However, if it's an enum want to render the key name instead of the value.// This cannot be done in displayText() because we need the model index to get the key name.const QtAbstractPropertyModel *propertyModel = qobject_cast<const QtAbstractPropertyModel*>(index.model());if(propertyModel) {const QMetaProperty metaProperty = propertyModel->metaPropertyAtIndex(index);if(metaProperty.isValid() && metaProperty.isEnumType()) {const QMetaEnum metaEnum = metaProperty.enumerator();QByteArray currentKey = QByteArray(metaEnum.valueToKey(value.toInt()));QStyleOptionViewItem itemOption(option);initStyleOption(&itemOption, index);itemOption.text = QString(currentKey);QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &itemOption, painter);return;}}} else if(value.type() == QVariant::UserType) {if(value.canConvert<QtPushButtonActionWrapper>()) {QAction *action = value.value<QtPushButtonActionWrapper>().action;QStyleOptionButton buttonOption;buttonOption.state = QStyle::State_Active | QStyle::State_Raised;//buttonOption.features = QStyleOptionButton::DefaultButton;if(action) buttonOption.text = action->text();buttonOption.rect = option.rect;//buttonOption.rect = QRect(option.rect.x() + 5, option.rect.y() + 5, option.rect.width() - 10, option.rect.height() - 10);QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);return;}}}QStyledItemDelegate::paint(painter, option, index);}bool QtPropertyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index){QVariant value = index.data(Qt::DisplayRole);if(value.isValid()) {if(value.type() == QVariant::Bool) {if(event->type() == QEvent::MouseButtonDblClick)return false;if(event->type() != QEvent::MouseButtonRelease)return false;QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);if(mouseEvent->button() != Qt::LeftButton)return false;//QStyleOptionButton buttonOption;//QRect checkBoxRect = QApplication::style()->subElementRect(QStyle::SE_CheckBoxIndicator, &buttonOption); // Only used to get size of native checkbox widget.//buttonOption.rect = QStyle::alignedRect(option.direction, Qt::AlignLeft, checkBoxRect.size(), option.rect); // Our checkbox rect.// option.rect ==> cell// buttonOption.rect ==> check box// Here, we choose to allow clicks anywhere in the cell to toggle the checkbox.if(!option.rect.contains(mouseEvent->pos()))return false;bool checked = value.toBool();QVariant newValue(!checked); // Toggle model's bool value.bool success = model->setData(index, newValue, Qt::EditRole);// Update entire table row just in case some other cell also refers to the same bool value.// Otherwise, that other cell will not reflect the current state of the bool set via this cell.if(success)model->dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), model->columnCount()));return success;} else if(value.type() == QVariant::UserType) {if(value.canConvert<QtPushButtonActionWrapper>()) {QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);if(mouseEvent->button() != Qt::LeftButton)return false;if(!option.rect.contains(mouseEvent->pos()))return false;QAction *action = value.value<QtPushButtonActionWrapper>().action;if(action) action->trigger();return true;}}}return QStyledItemDelegate::editorEvent(event, model, option, index);}QtPropertyTreeEditor::QtPropertyTreeEditor(QWidget *parent) : QTreeView(parent){setItemDelegate(&_delegate);setAlternatingRowColors(true);setModel(&treeModel);}void QtPropertyTreeEditor::resizeColumnsToContents(){resizeColumnToContents(0);resizeColumnToContents(1);}QtPropertyTableEditor::QtPropertyTableEditor(QWidget *parent) : QTableView(parent){setItemDelegate(&_delegate);setAlternatingRowColors(true);setModel(&tableModel);verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);setIsDynamic(_isDynamic);// Draggable rows.verticalHeader()->setSectionsMovable(_isDynamic);connect(verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(handleSectionMove(int, int, int)));// Header context menus.horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);verticalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);connect(horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(horizontalHeaderContextMenu(QPoint)));connect(verticalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(verticalHeaderContextMenu(QPoint)));// Custom corner button.if(QAbstractButton *cornerButton = findChild<QAbstractButton*>()) {cornerButton->installEventFilter(this);}}void QtPropertyTableEditor::setIsDynamic(bool b){_isDynamic = b;// Dragging rows.verticalHeader()->setSectionsMovable(_isDynamic);// Corner button.if(QAbstractButton *cornerButton = findChild<QAbstractButton*>()) {if(_isDynamic) {cornerButton->disconnect(SIGNAL(clicked()));connect(cornerButton, SIGNAL(clicked()), this, SLOT(appendRow()));cornerButton->setText("+");cornerButton->setToolTip("Append row");} else {cornerButton->disconnect(SIGNAL(clicked()));connect(cornerButton, SIGNAL(clicked()), this, SLOT(selectAll()));cornerButton->setText("");cornerButton->setToolTip("Select all");}// adjust the width of the vertical header to match the preferred corner button width// (unfortunately QAbstractButton doesn't implement any size hinting functionality)QStyleOptionHeader opt;opt.text = cornerButton->text();//opt.icon = cornerButton->icon();QSize s = (cornerButton->style()->sizeFromContents(QStyle::CT_HeaderSection, &opt, QSize(), cornerButton).expandedTo(QApplication::globalStrut()));if(s.isValid()) {verticalHeader()->setMinimumWidth(s.width());}}}void QtPropertyTableEditor::horizontalHeaderContextMenu(QPoint pos){QModelIndexList indexes = selectionModel()->selectedColumns();QMenu *menu = new QMenu;menu->addAction("Resize Columns To Contents", this, SLOT(resizeColumnsToContents()));menu->popup(horizontalHeader()->viewport()->mapToGlobal(pos));}void QtPropertyTableEditor::verticalHeaderContextMenu(QPoint pos){QModelIndexList indexes = selectionModel()->selectedRows();QMenu *menu = new QMenu;if(_isDynamic) {QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());if(propertyTableModel->objectCreator()) {menu->addAction("Append Row", this, SLOT(appendRow()));}if(indexes.size()) {if(propertyTableModel->objectCreator()) {menu->addSeparator();menu->addAction("Insert Rows", this, SLOT(insertSelectedRows()));menu->addSeparator();}menu->addAction("Delete Rows", this, SLOT(removeSelectedRows()));}}menu->popup(verticalHeader()->viewport()->mapToGlobal(pos));}void QtPropertyTableEditor::appendRow(){if(!_isDynamic)return;QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());if(!propertyTableModel || !propertyTableModel->objectCreator())return;model()->insertRows(model()->rowCount(), 1);}void QtPropertyTableEditor::insertSelectedRows(){if(!_isDynamic)return;QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());if(!propertyTableModel || !propertyTableModel->objectCreator())return;QModelIndexList indexes = selectionModel()->selectedRows();if(indexes.size() == 0)return;QList<int> rows;foreach(const QModelIndex &index, indexes) {rows.append(index.row());}qSort(rows);model()->insertRows(rows.at(0), rows.size());}void QtPropertyTableEditor::removeSelectedRows(){if(!_isDynamic)return;QModelIndexList indexes = selectionModel()->selectedRows();if(indexes.size() == 0)return;QList<int> rows;foreach(const QModelIndex &index, indexes) {rows.append(index.row());}qSort(rows);for(int i = rows.size() - 1; i >= 0; --i) {model()->removeRows(rows.at(i), 1);}}void QtPropertyTableEditor::handleSectionMove(int /* logicalIndex */, int oldVisualIndex, int newVisualIndex){if(!_isDynamic)return;QtPropertyTableModel *propertyTableModel = qobject_cast<QtPropertyTableModel*>(model());if(!propertyTableModel)return;// Move objects in the model, and then move the sections back to maintain logicalIndex order.propertyTableModel->moveRows(QModelIndex(), oldVisualIndex, 1, QModelIndex(), newVisualIndex);disconnect(verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(handleSectionMove(int, int, int)));verticalHeader()->moveSection(newVisualIndex, oldVisualIndex);connect(verticalHeader(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(handleSectionMove(int, int, int)));}void QtPropertyTableEditor::keyPressEvent(QKeyEvent *event){switch(event->key()) {case Qt::Key_Backspace:case Qt::Key_Delete:if(_isDynamic && QMessageBox::question(this, "Delete Rows?", "Delete selected rows?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {removeSelectedRows();}break;case Qt::Key_Plus:appendRow();break;default:break;}}bool QtPropertyTableEditor::eventFilter(QObject* o, QEvent* e){if (e->type() == QEvent::Paint) {if(QAbstractButton *btn = qobject_cast<QAbstractButton*>(o)) {// paint by hand (borrowed from QTableCornerButton)QStyleOptionHeader opt;opt.init(btn);QStyle::State styleState = QStyle::State_None;if (btn->isEnabled())styleState |= QStyle::State_Enabled;if (btn->isActiveWindow())styleState |= QStyle::State_Active;if (btn->isDown())styleState |= QStyle::State_Sunken;opt.state = styleState;opt.rect = btn->rect();opt.text = btn->text(); // this line is the only difference to QTableCornerButton//opt.icon = btn->icon(); // this line is the only difference to QTableCornerButtonopt.position = QStyleOptionHeader::OnlyOneSection;QStylePainter painter(btn);painter.drawControl(QStyle::CE_Header, opt);return true; // eat event}}return false;}} // QtPropertyEditor

三 、说说用途

这些年来,大家肯定听多了什么 组态虚幻引擎低代码平台拖拽式编程啊,听起来都好DIAO啊,本质是啥呢,说到底还是一堆的属性配置,与大量的相关业务逻辑做的映射。以HMI为例,目前说得上名的企业基本都是使用组态这一套思想,例如威纶通、凡易、WINCC,亿维自动化,matlab也提供组态,甚至于我们所说的大型绘图软件,如cad、solidworks 也有组态的使用,不过人家叫做块,或者说是标准件、模板,大家都是对对象支持了属性编辑,都是可以模块化的复用和自定义,在这点上都是好兄弟。

既然每个自定义的对象或者组件都有大量的属性需要展示、或者暴漏给用户进行交互,是为每一个控件写一个窗口去支持属性的修改,还是使用一套统一的属性系统,使用通用的模板去完成这一重要的功能模块,这不难得出结论。

没有用Qt的,我可以放心的告诉你,基本都自己实现了一套属性系统,内部使用了大量的反射机制;用了Qt的,当然也有一部分没有使用Qt原生的属性系统,而是苦哈哈的维护这陈年旧代码,随着组件的增加,不断的写对象,写对象属性编辑对话框,子子孙孙,无穷无尽,这有一点好处,提供了长期的需求和就业岗位,也算是造福程序员啦。那么,用了Qt属性系统的那些项目呢,代码清清爽爽,赏心悦目,当然缺点就是你boss好像认为你很闲,一查项目,代码没几行😂 。所以呢,这么好的东西,还是慎用。但是慎用不表示你不需要深层次的掌握它。

四、自定义使用

楼已经太高了,下篇讲吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/294580.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL的安装及如何连接到Navicat和IntelliJ IDEA

MySQL的安装及如何连接到Navicat和IntelliJ IDEA 文章目录 MySQL的安装及如何连接到Navicat和IntelliJ IDEA1 MySQL安装1.1 下载1.2 安装(解压)1.3 配置1.3.1 添加环境变量1.3.2 新建配置文件1.3.3 初始化MySQL1.3.4 注册MySQL服务1.3.5 启动MySQL服务1.3.6 修改默认账户密码 1…

【前端】前后端通信方法与差异(未完待续)

系列文章 【Vue】vue增加导航标签 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/134965353 【Vue】Element开发笔记 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/133947977 【Vue】vue&#xff0c;在Windows IIS平台…

jar混淆,防止反编译,Allatori工具混淆jar包

文章目录 Allatori工具简介下载解压配置config.xml注意事项 Allatori工具简介 官网地址&#xff1a;https://allatori.com/ Allatori不仅混淆了代码&#xff0c;还最大限度地减小了应用程序的大小&#xff0c;提高了速度&#xff0c;同时除了你和你的团队之外&#xff0c;任何人…

基于ssm图书管理系统的设计与实现论文

摘 要 随着科技的快速的发展和网络信息的普及&#xff0c;信息化管理已经融入到了人们的日常生活中&#xff0c;各行各业都开始采用信息化管理系统&#xff0c;通过计算机信息化管理&#xff0c;首先可以减轻人们工作量&#xff0c;而且采用信息化管理数据信息更加的严谨&…

es、MySQL 深度分页问题

文章目录 es 深度分页MySQL 深度分页 es 深度分页 es 深度分页问题&#xff0c;有点忘记了&#xff0c;这里记录一下 当索引库中有10w条数据&#xff0c;比如是商品数据&#xff1b;用户就是要查在1w到后10条数据&#xff0c;怎么查询。 es查询是从各个分片中取出前1w到后10条数…

音画欣赏|《同杯万古尘》

《同杯万古尘》 尺寸&#xff1a;69x35cm 陈可之2023年绘 《拟古十二首-其九》 李白 生者为过客&#xff0c;死者为归人。 天地一逆旅&#xff0c;同悲万古尘。 月兔空捣药&#xff0c;扶桑已成薪。 白骨寂无言&#xff0c;青松岂知春。 前后更叹息&#xff0c;浮荣安足珍&am…

SpringMVC基础知识(持续更新中~)

笔记&#xff1a; https://gitee.com/zhengguangqq/ssm-md/blob/master/ssm%20md%E6%A0%BC%E5%BC%8F%E7%AC%94%E8%AE%B0/%E4%B8%89%E3%80%81SpringMVC.md 细节补充&#xff1a; ​​​​​​​

Linux 一键部署二进制Gitea

gitea 前言 Gitea 是一个轻量级的 DevOps 平台软件。从开发计划到产品成型的整个软件生命周期,他都能够高效而轻松的帮助团队和开发者。包括 Git 托管、代码审查、团队协作、软件包注册和 CI/CD。它与 GitHub、Bitbucket 和 GitLab 等比较类似。 Gitea 最初是从 Gogs 分支而来…

Python编程 圣诞树教程 (附代码)专属于程序员的浪漫

文章目录 1.turtle库2.python函数的定义规则3.引入库4.定义画彩灯函数5.定义画圣诞树的函数6.定义树下面小装饰的函数7.定义一个画雪花的函数8.画五角星9.写文字10.全部源代码11.html圣诞树代码实现&#xff08;动态音乐&#xff09; 1.turtle库 turtle库是Python语言中一个很…

力扣面试经典题之二叉树

104. 二叉树的最大深度 简单 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xf…

linux分辨率添加

手动添加分辨率 注&#xff1a;添加分辨率需要显卡驱动支持&#xff0c;若显卡驱动有问题&#xff0c;则不能添加 可通过 xrandr 结果判断 # xrandr 若图中第二行” eDP“ 显示为 ” default “ &#xff0c;则显卡驱动加载失败&#xff0c;不能添加分辨率 1. 添加分辨率 # …

高级数据结构 <二叉搜索树>

本文已收录至《数据结构(C/C语言)》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言正文二叉搜索树的概念二叉搜索树的基本功能实现二叉搜索树的基本框架插入节点删除节点查找函数中序遍历函数析构函数和销毁函数(后序遍历销毁)拷贝构造和赋值重载(前序遍历创建)其他函数…