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好像认为你很闲,一查项目,代码没几行😂 。所以呢,这么好的东西,还是慎用。但是慎用不表示你不需要深层次的掌握它。
四、自定义使用
楼已经太高了,下篇讲吧
相关文章:

Qt通用属性工具:随心定义,随时可见(一)
一、开胃菜,没图我说个DIAO 先不BB,给大家上个效果图展示下: 上图我们也没干啥,几行代码: #include "widget.h" #include <QApplication> #include <QObject> #include "QtPropertyEdit…...

Python中json模块的使用与pyecharts绘图的基本介绍
文章目录 json模块json与Python数据的相互转化 pyecharts模块pyecharts基本操作基础折线图配置选项全局配置选项 json模块的数据处理折线图示例示例代码 json模块 json实际上是一种数据存储格式,是一种轻量级的数据交互格式,可以把他理解成一个特定格式…...

nodejs+vue+微信小程序+python+PHP医院挂号系统-计算机毕业设计推荐
当前社会各行业领域竞争压力非常大,随着当前时代的信息化,科学化发展,让社会各行业领域都争相使用新的信息技术, 本医院挂号系统也是紧跟科学技术的发展,运用当今一流的软件技术实现软件系统的开发,让家具销…...

数据大模型与低代码开发:赋能技术创新的黄金组合
在当今技术领域,数据大模型和低代码开发已经成为两个重要的趋势。数据大模型借助庞大的数据集和强大的计算能力,助力我们从海量数据中挖掘出有价值的洞见和预测能力。与此同时,低代码开发通过简化开发流程和降低编码需求,使得更多…...

Redis BitMap(位图)
这里是小咸鱼的技术窝(CSDN板块),我又开卷了 之前经手的项目运行了10多年,基于重构,里面有要实现一些诸如签到的需求,以及日历图的展示,可以用将签到信息存到传统的关系型数据库(MyS…...
使用eclipse创建一个java文件并运行
启动 Eclipse 并创建一个新的 Java 项目: 打开 Eclipse。 选择 “File” > “New” > “Java Project”(文件 > 新建 > Java 项目)。 在弹出的窗口中,为你的项目命名,比如 MyJavaProject。 点击 “Finish”ÿ…...

C#上位机与欧姆龙PLC的通信05---- HostLink协议
1、介绍 Hostlink协议是欧姆龙PLC与上位机链接的公开协议。上位机通过发送Hostlink命令,可以对PLC进行I/O读写、可以对PLC进行I/O读写、改变操作模式、强制置位/复位等操作。由于是公开协议,即便是非欧姆龙的上位设备(软件)&…...

Uniapp 开发 BLE
BLE 低功耗蓝牙(Bluetooth Low Energy,或称Bluetooth LE、BLE,旧商标Bluetooth Smart),用于医疗保健、运动健身、安防、工业控制、家庭娱乐等领域。在如今的物联网时代下大放异彩,扮演者重要一环ÿ…...
c语言排序算法
C语言代码示例: 冒泡排序(Bubble Sort): void bubbleSort(int arr[], int n) {for (int i 0; i < n-1; i) {for (int j 0; j < n-i-1; j) {if (arr[j] > arr[j1]) {int temp arr[j];arr[j] arr[j1];arr[j1] temp;…...

【机器学习】模式识别
1 概述 模式识别,简单来讲,就是分类问题。 模式识别应用:医学影像分析、人脸识别、车牌识别、遥感图像 2 模式分类器 分类器的分类:线性分类器、非线性分类器、最近邻分类器 2.1 分类器的训练(学习)过…...

【Prometheus|报错】Out of bounds
【背景】进入Prometheus地址的9090端口,pushgateway(0/1)error : out of bounds 【排查分析】 1、out of bounds报错,是由于Prometheus向tsdb存数据出错,与最新存数据的时间序列有问题,有可能当前时间与最…...

【音视频】Mesh、Mcu、SFU三种框架的总结
目录 三种网络场景介绍 【Mesh】 【MCU】(MultiPoint Control Unit) 【SFU】(Selective Forwarding Unit) 三种网络架构的优缺点 Mesh架构 MCU架构(MultiPoint Control Unit) SFU架构(Selective Forwarding Unit) 总结 参考文章 三种网络场景介绍 【Mesh】 Mesh架构…...

高级算法设计与分析(四) -- 贪心算法
系列文章目录 高级算法设计与分析(一) -- 算法引论 高级算法设计与分析(二) -- 递归与分治策略 高级算法设计与分析(三) -- 动态规划 高级算法设计与分析(四) -- 贪心算法 高级…...

MATLAB - 机器人逆运动学设计器(Inverse Kinematics Designer APP)
系列文章目录 前言 一、简介 通过逆运动学设计器,您可以为 URDF 机器人模型设计逆运动学求解器。您可以调整逆运动学求解器并添加约束条件,以实现所需的行为。使用该程序,您可以 从 URDF 文件或 MATLAB 工作区导入 URDF 机器人模型。调整逆…...

使用OpenCV DNN模块进行人脸检测
内容的一部分来源于贾志刚的《opencv4应用开发、入门、进阶与工程化实践》。这本书我大概看了一下,也就后面几章比较感兴趣,但是内容很少,并没有想像的那种充实。不过学习还是要学习的。 在实际工程项目中,并不是说我们将神经网络…...
C#中使用OpenCV的常用函数
以下是一些C#中使用OpenCV的常用函数例子: 1. 加载图像: using OpenCvSharp;Mat image Cv2.ImRead("path_to_your_image.jpg", ImreadModes.Color); 2. 显示图像: Cv2.NamedWindow("Image Window", WindowFlags.Nor…...

使用Swift Package Manager (SPM)实现xcframework分发
Swift Package Manager (SPM) 是苹果官方提供的用于管理 Swift 项目的依赖关系和构建过程的工具。它是一个集成在 Swift 编程语言中的包管理器,用于解决在开发过程中管理和构建包依赖项的需求。 1、上传xcframework.zip到服务端 压缩xcframeworks成一个zip包&…...

非阻塞 IO(NIO)
文章目录 非阻塞 IO(NIO)模型驱动程序应用程序模块使用 非阻塞 IO(NIO) 上一节中 https://blog.csdn.net/tyustli/article/details/135140523,使用等待队列头实现了阻塞 IO 程序使用时,阻塞 IO 和非阻塞 IO 的区别在于文件打开的时候是否使用了 O_NONB…...

Android应用-flutter使用Positioned将控件定位到底部中间
文章目录 场景描述示例解释 场景描述 要将Positioned定位到屏幕底部中间的位置,你可以使用MediaQuery来获取屏幕的高度,然后设置Positioned的bottom属性和left或right属性,一般我们left和right都会设置一个值让控制置于合适的位置࿰…...

Django 简单图书管理系统
一、图书需求 1. 书籍book_index.html中有超链接:查看所有的书籍列表book_list.html页面 2. 书籍book_list.html中显示所有的书名,有超链接:查看本书籍详情book_detail.html(通过书籍ID)页面 3. 书籍book_detail.html中书的作者和出版社&…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...