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.rhkb.cn/news/223203.html

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

相关文章

攻防世界——game 游戏

下载下来是一个exe文件&#xff0c;可以用IDA打开 我们先运行一下 这是属于第二种类型&#xff0c;完成一个操作后给你flag 这种题我更倾向于动调直接得到flag 我们查壳 没有保护壳&#xff0c;直接32打开 进入字符串界面&#xff0c;找到显示的那部分 int __cdecl main_0(…

汽车级EEPROM 存储器 M24C64-DRMN3TP/K是电可擦除可编程只读存储器?它的功能特性有哪些?

M24C64-DRMN3TP/K是一款64 Kbit串行EEPROM汽车级设备&#xff0c;工作温度高达125C。符合汽车标准AEC-Q100 1级规定的极高可靠性。 该设备可通过一个高达1MHz的简单串行I2C兼容接口访问。 存储器阵列基于先进的真EEPROM技术&#xff08;电可擦除可编程存储器&#xff09;。M2…

【三维生成与重建】ZeroRF:Zero Pretraining的快速稀疏视图360°重建

系列文章目录 题目&#xff1a;ZeroRF: Fast Sparse View 360◦ Reconstruction with Zero Pretraining 任务&#xff1a;稀疏重建&#xff1b;拓展&#xff1a;Image to 3D、文本到3D 作者&#xff1a;Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su &#xff0c;来自UC San Dieg…

【接口测试】如何定位BUG的产生原因

我们从在日常功能测试过程中对UI的每一次操作说白了就是对一个或者多个接口的一次调用&#xff0c;接口的返回的内容(移动端一般为json)经过前端代码的处理最终展示在页面上。http接口是离我们最近的一层接口&#xff0c;web端和移动端所展示的数据就来自于这层&#xff0c;那么…

深入理解 Rust 中的容器类型及其应用

Rust 作为一种系统编程语言&#xff0c;提供了丰富的容器类型来处理各种数据结构和算法。这些容器类型不仅支持基本的数据存储和访问&#xff0c;还提供了高效的内存管理和安全性保障。本文将详细介绍 Rust 中的几种主要容器类型&#xff0c;包括它们的用法、特点和适用场景&am…

【数据结构一】初始Java集合框架(前置知识)

Java中的数据结构 Java语言在设计之初有一个非常重要的理念便是&#xff1a;write once&#xff0c;run anywhere&#xff01;所以Java中的数据结构是已经被设计者封装好的了&#xff0c;我们只需要实例化出想使用的对象&#xff0c;便可以操作相应的数据结构了&#xff0c;本篇…

【C++进阶02】多态

一、多态的概念及定义 1.1 多态的概念 多态简单来说就是多种形态 同一个行为&#xff0c;不同对象去完成时 会产生出不同的状态 多态分为静态多态和动态多态 静态多态指的是编译时 在程序编译期间确定了程序的行为 比如&#xff1a;函数重载 动态多态指的是运行时 在程序运行…

【Amazon 实验②】使用Amazon WAF做基础 Web Service 防护之自定义规则

文章目录 1. 自定义规则1.1 介绍 2. 实验步骤2.1 测试2.2 输出 上一篇章介绍了使用Amazon WAF做基础 Web Service 防护中的Web ACLs 配置 & AWS 托管规则的介绍和演示操作 【Amazon 实验①】使用Amazon WAF做基础 Web Service 防护&#xff0c;本篇章将继续介绍关于自定义…

操作系统--磁盘存储器的管理

目录 8.1 外存的组织方式 常用的外存分配方法&#xff1a; 8.1.1 连续组织方式 连续组织方式的主要优点有&#xff1a; 连续组织方式的主要缺点如下&#xff1a; 8.1.2 链接组织方式 链接组织方式的主要优点是&#xff1a; 1. 隐式链接 2. 显式链接 8.1.3 FAT和NTFS技术 文…

大创项目推荐 深度学习+python+opencv实现动物识别 - 图像识别

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 inception_v3网络5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

基于Java (spring-boot)的在线考试管理系统

一、项目介绍 系统功能说明 1、系统共有管理员、老师、学生三个角色&#xff0c;管理员拥有系统最高权限。 2、老师拥有考试管理、题库管理、成绩管理、学生管理四个模块。 3、学生可以参与考试、查看成绩、试题练习、留言等功能 二、作品包含 三、项目技术 后端语言&#xff1…

【二】【C语言\动态规划】解码方法、不同路径、不同路径II,三道题目深度解析

动态规划 动态规划就像是解决问题的一种策略&#xff0c;它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题&#xff0c;并将每个小问题的解保存起来。这样&#xff0c;当我们需要解决原始问题的时候&#xff0c;我们就可以直接利…

CSS3多列分页属性

CSS3多列 Firefox浏览器支持该属性的形式是-moz-column-count&#xff0c;而基于Webkit的浏览器&#xff0c;例如Safari和Chrome&#xff0c;支持该属性的形式是-webkit-column-count column-count&#xff1a;该属性定义多列文本流中的栏数 语法&#xff1a;column-count:int…

本地websocket服务端结合cpolar内网穿透实现公网访问

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

「数据结构」二叉树2

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;初阶数据结构 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;前言&#x1f349;链式结构&#x1f349;遍历二叉树&#x1f34c;前序遍历&#x1f34c;中序遍历&#x…

Qt 多线程用法

文章目录 开发平台QThread 类 moveToThreadQtConcurrent::run QFutureWatcherQThreadPool QRunnable 开发平台 项目说明OSwin10 x64Qt6.6compilermsvc2022构建工具cmake QThread 类 moveToThread 写一个简单的例子吧,比较容易理解,方便入门. 也可以看出这种方式,对于线程…

Polygon zkEVM Spearbit审计报告解读(2022年12月版本)

1. 引言 前序博客&#xff1a; Polygon zkEVM Hexens审计报告解读&#xff08;2022年12月至2023年2月版本&#xff09; 主要见&#xff1a; Polygon zkEVM Security Review: December 2022 Engagement Polygon zkEVM为提供&#xff08;opcode层面兼容的&#xff09;EVM等价…

Linux学习小结

目录结构 tree -L 1 / # /root #root用户的家目录 /home #存储普通用户家目录 lostfound #这个目录平时是空的&#xff0c;存储系统非正常关机而留下“无家可归”的文件 /usr #系统文件&#xff0c;相当于C:\Windows /usr/local #软件安装的目录&#xff0c;相当于C:\Progra…

Ubuntu-20.04.2 mate 上安装、配置、测试 qtcreator

一、从repo中安装 Ubuntu-20.04.2的repo中&#xff0c;qtcreator安装包挺全乎的&#xff0c;敲完 sudo apt install qtcreator 看一下同时安装和新软件包将被安装列表&#xff0c;压缩包252MB&#xff0c;解压安装后933MB&#xff0c;集大成的一包。 sudo apt install qtcrea…

使用Java语言解决古典猴子分桃问题

一、主要思想 五只猴子分桃 第一只猴子呀 平均分成五分 挤出来多一个 多的扔入海中 拿了其中一份 来了五只猴子 均是如此操作 第五只猴子呀 还存有多少只 二、基本代码 public class MonkeyPeach {public static void main(String[] args){int n 1;int m 0;int flag1;int…