Qt 是目前最先进、最完整的跨平台C++开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。
快捷编辑器示例展示了如何创建一个基本的读写层次模型,来与Qt的标准视图和QKeySequenceEdit类一起使用。
点击获取Qt Widget组件下载(Q技术交流:166830288)
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_cast<ShortcutEditorModelItem*>(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_cast<ShortcutEditorModelItem*>(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_cast<ShortcutEditorModelItem*>(parent.internalPointer());
return parentItem->childCount();
}
数据通过Data()从模型中获得,由于项目管理它自己的列,我们需要使用列号来使用TreeItem::data()函数检索数据:
int ShortcutEditorModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return static_cast<ShortcutEditorModelItem*>(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_cast<ShortcutEditorModelItem*>(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_cast<int>(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();
}
TODO
void ShortcutEditorModel::setupModelData(ShortcutEditorModelItem *parent)
{
ActionsMap actionsMap;
Application *application = static_cast<Application *>(QCoreApplication::instance());
ActionManager *actionManager = application->actionManager();
const QList<QAction *> 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_cast<void *>(action))}, categoryLevelItem);
categoryLevelItem->appendChild(actionLevelItem);
}
}
}
}
TODO
bool ShortcutEditorModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole && index.column() == static_cast<int>(Column::Shortcut)) {
QString keySequenceString = value.toString();
ShortcutEditorModelItem *item = static_cast<ShortcutEditorModelItem *>(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实例。
Qt Widget组件推荐
- QtitanRibbon - Ribbon UI组件:是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件,QtitanRibbon致力于为Windows、Linux和Mac OS X提供功能完整的Ribbon组件。
- QtitanChart - Qt类图表组件:是一个C ++库,代表一组控件,这些控件使您可以快速地为应用程序提供漂亮而丰富的图表。
- QtitanDataGrid - Qt网格组件:提供了一套完整的标准 QTableView 函数和传统组件无法实现的独特功能。使您能够将不同来源的各类数据加载到一个快速、灵活且功能强大的可编辑网格中,支持排序、分组、报告、创建带状列、拖放按钮和许多其他方便的功能。
- QtitanDocking:允许您像 Visual Studio 一样为您的伟大应用程序配备可停靠面板和可停靠工具栏。黑色、白色、蓝色调色板完全支持 Visual Studio 2019 主题!