「Qt Widget中文示例指南」如何实现一个快捷编辑器(二)
Qt是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写所有平台无差别运行更提供了几乎所有开发过程中需要用到的工具。如今Qt已被运用于超过70个行业、数千家企业支持数百万设备及应用。快捷编辑器示例展示了如何创建一个基本的读写层次模型来与Qt的标准视图和QKeySequenceEdit类一起使用。Qt的模型/视图架构为视图提供了一种标准的方式来操作数据源中的信息使用数据的抽象模型来简化和标准化访问数据的方式。快捷编辑器模型将操作表示为项目树并允许视图通过基于索引的系统访问此数据。更一般地说可以使用模型以树结构的形式表示数据方法是允许每个项作为子项表的父项。在上文中我们为大家介绍了快捷编辑器的设计理念及结构等本文将继续介绍一些具体的实现类。ShortcutEditorModel类实现构造函数接受一个参数其中包含模型将与视图和委托共享的数据ShortcutEditorModel::ShortcutEditorModel(QObject *parent) : QAbstractItemModel(parent) { m_rootItem new ShortcutEditorModelItem({tr(Name), tr(Shortcut)}); }由构造函数来为模型创建根项为方便起见此项仅包含垂直标题数据。我们还使用它来引用包含模型数据的内部数据结构并使用它来表示模型中顶级项的假想父项。模型的内部数据结构由setupModelData()函数填充我们将在本文末尾单独研究这个函数。析构函数确保在模型被销毁时删除根项及其所有子类ShortcutEditorModel::~ShortcutEditorModel() { delete m_rootItem; }由于在构造和设置模型之后我们不能向模型中添加数据因此这简化了管理内部项目树的方式。模型必须实现index()函数来为视图和委托提供索引以便在访问数据时使用。当其他组件被它们的行号、列号以及它们的父模型索引引用时为它们创建索引。如果将无效的模型索引指定为父索引则由模型返回与模型中的顶级项对应的索引。当提供模型索引时我们首先检查它是否有效。如果不是假定引用的是顶级项否则我们使用模型索引的internalPointer() 函数从模型索引中获取数据指针并使用它来引用TreeItem对象。注意我们构造的所有模型索引都将包含一个指向现有TreeItem的指针因此可以保证接收到的任何有效模型索引都将包含一个有效的数据指针。void ShortcutEditorModel::setActions() { beginResetModel(); setupModelData(m_rootItem); endResetModel(); }由于此函数的行和列参数引用相应父项的子项因此我们使用TreeItem::child()函数获得该项createIndex()函数用于创建要返回的模型索引。我们指定行号和列号以及指向项本身的指针稍后可以使用模型索引来获取项目的数据。TreeItem对象的定义方式使得parent()函数的编写变得简单QModelIndex ShortcutEditorModel::index(int row, int column, const QModelIndex parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); ShortcutEditorModelItem *parentItem; if (!parent.isValid()) parentItem m_rootItem; else parentItem static_castShortcutEditorModelItem*(parent.internalPointer()); ShortcutEditorModelItem *childItem parentItem-child(row); if (childItem) return createIndex(row, column, childItem); return QModelIndex(); }我们只需要确保永远不会返回与根项对应的模型索引为了与index()函数的实现方式保持一致我们为模型中任何顶级项的父项返回一个无效的模型索引。当创建要返回的模型索引时我们必须在父项中指定父项的行号和列号。我们可以很容易地使用TreeItem::row()函数发现行号但是我们遵循指定0作为父列号的约定。模型索引是用createIndex()创建的方法与index()函数相同。rowCount()函数只是返回对应于给定模型索引的TreeItem的子条目的数量或者如果指定了无效索引则返回顶级条目的数量QModelIndex ShortcutEditorModel::parent(const QModelIndex index) const { if (!index.isValid()) return QModelIndex(); ShortcutEditorModelItem *childItem static_castShortcutEditorModelItem*(index.internalPointer()); ShortcutEditorModelItem *parentItem childItem-parentItem(); if (parentItem m_rootItem) return QModelIndex(); return createIndex(parentItem-row(), 0, parentItem); }由于每个项目都管理自己的列数据因此columnCount()函数必须调用项目自己的columnCount()函数来确定给定模型索引有多少列。与rowCount()函数一样如果指定了无效的模型索引则返回的列数将从根项确定int ShortcutEditorModel::rowCount(const QModelIndex parent) const { ShortcutEditorModelItem *parentItem; if (parent.column() 0) return 0; if (!parent.isValid()) parentItem m_rootItem; else parentItem static_castShortcutEditorModelItem*(parent.internalPointer()); return parentItem-childCount(); }数据通过Data()从模型中获得由于项目管理它自己的列我们需要使用列号来使用TreeItem::data()函数检索数据int ShortcutEditorModel::columnCount(const QModelIndex parent) const { if (parent.isValid()) return static_castShortcutEditorModelItem*(parent.internalPointer())-columnCount(); return m_rootItem-columnCount(); }注意在这个实现中我们只支持DisplayRole并且还为无效的模型索引返回无效的QVariant对象。我们使用flags()函数来确保视图知道模型是只读的QVariant ShortcutEditorModel::data(const QModelIndex index, int role) const { if (!index.isValid()) return QVariant(); if (role ! Qt::DisplayRole role ! Qt::EditRole) return QVariant(); ShortcutEditorModelItem *item static_castShortcutEditorModelItem*(index.internalPointer()); return item-data(index.column()); }headerData()函数返回我们方便地存储在根项中的数据Qt::ItemFlags ShortcutEditorModel::flags(const QModelIndex index) const { if (!index.isValid()) return Qt::NoItemFlags; Qt::ItemFlags modelFlags QAbstractItemModel::flags(index); if (index.column() static_castint(Column::Shortcut)) modelFlags | Qt::ItemIsEditable; return modelFlags; }这些信息可以以不同的方式提供在构造函数中指定或者硬编码到headerData()函数中。QVariant ShortcutEditorModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation Qt::Horizontal role Qt::DisplayRole) { return m_rootItem-data(section); } return QVariant(); }TODOvoid ShortcutEditorModel::setupModelData(ShortcutEditorModelItem *parent) { ActionsMap actionsMap; Application *application static_castApplication *(QCoreApplication::instance()); ActionManager *actionManager application-actionManager(); const QListQAction * registeredActions actionManager-registeredActions(); for (QAction *action : registeredActions) { QString context actionManager-contextForAction(action); QString category actionManager-categoryForAction(action); actionsMap[context][category].append(action); } QAction *nullAction nullptr; const QString contextIdPrefix root; // Go through each context, one context - many categories each iteration for (const auto contextLevel : actionsMap.keys()) { ShortcutEditorModelItem *contextLevelItem new ShortcutEditorModelItem({contextLevel, QVariant::fromValue(nullAction)}, parent); parent-appendChild(contextLevelItem); // Go through each category, one category - many actions each iteration for (const auto categoryLevel : actionsMap[contextLevel].keys()) { ShortcutEditorModelItem *categoryLevelItem new ShortcutEditorModelItem({categoryLevel, QVariant::fromValue(nullAction)}, contextLevelItem); contextLevelItem-appendChild(categoryLevelItem); for (QAction *action : actionsMap[contextLevel][categoryLevel]) { QString name action-text(); if (name.isEmpty() || !action) continue; ShortcutEditorModelItem *actionLevelItem new ShortcutEditorModelItem({name, QVariant::fromValue(reinterpret_castvoid *(action))}, categoryLevelItem); categoryLevelItem-appendChild(actionLevelItem); } } } }TODObool ShortcutEditorModel::setData(const QModelIndex index, const QVariant value, int role) { if (role Qt::EditRole index.column() static_castint(Column::Shortcut)) { QString keySequenceString value.toString(); ShortcutEditorModelItem *item static_castShortcutEditorModelItem *(index.internalPointer()); QAction *itemAction item-action(); if (itemAction) { if (keySequenceString itemAction-shortcut().toString(QKeySequence::NativeText)) return true; itemAction-setShortcut(keySequenceString); } Q_EMIT dataChanged(index, index); if (keySequenceString.isEmpty()) return true; } return QAbstractItemModel::setData(index, value, role); }TODO在模型中设置数据我们使用setupModelData()函数在模型中设置初始数据该函数检索已注册的操作文本并创建记录数据和整体模型结构的项目对象。当然这个函数的工作方式是非常特定于这个模型的。为了确保模型正确工作只需要创建具有正确数据和父项的ShortcutEditorModelItem实例。