ショートカットエディタの例
「ショートカットエディタ」の例では、Qtの標準ビューおよびQKeySequenceEdit クラスで使用するための、基本的な読み書き可能な階層モデルを作成する方法を示しています。モデル/ビュープログラミングの詳細については、「モデル/ビュープログラミングの概要」を参照してください。

Qtのモデル/ビューアーキテクチャは、データの抽象モデルを使用してデータソース内の情報を操作する標準的な方法をビューに提供し、データへのアクセス方法を簡素化および標準化します。ショートカットエディタのモデルは、アクションを項目のツリーとして表現し、ビューがインデックスベースのシステムを介してこのデータにアクセスできるようにします。 より一般的には、各項目が子項目のテーブルの親として機能できるようにすることで、モデルを使用してデータをツリー構造の形式で表現することができます。
設計と概念
データの構造を表現するために使用するデータ構造は、ShortcutEditorModelItem オブジェクトから構成されるツリーの形式をとります。各 ShortcutEditorModelItem はツリービュー内の 1 つの項目を表し、2 列のデータを含んでいます。
| ショートカットエディタの構造 データは、ポインタベースのツリー構造で相互にリンクされた ShortcutEditorModelItem オブジェクトを使用して、モデル内部に格納されます。一般的に、各 ShortcutEditorModelItem には親アイテムがあり、複数の子アイテムを持つことができます。ただし、ツリー構造のルートアイテムには親アイテムが存在せず、モデルの外部からは参照されることはありません。 各 ShortcutEditorModelItem は、ツリー構造内での自身の位置に関する情報を保持しており、親アイテムや行番号を取得することができます。この情報をすぐに利用できるようにすることで、モデルの実装が容易になります。 ツリービューの各項目には通常、複数の列のデータ(この例では名前とショートカット)が含まれるため、この情報を各項目に格納するのが自然です。簡略化のため、項目の各列のデータを格納するには、QVariant オブジェクトのリストを使用します。 |
ポインタベースのツリー構造を使用することで、ビューにモデルインデックスを渡す際に、そのインデックスに対応する項目のアドレスを記録し(QAbstractItemModel::createIndex() を参照)、後でQModelIndex::internalPointer() を使用してそれを取得することができます。これにより、モデルの記述が容易になり、同じ項目を参照するすべてのモデルインデックスが、同じ内部データポインタを持つことが保証されます。
適切なデータ構造が整っていれば、他のコンポーネントにモデルインデックスやデータを提供するための追加コードを最小限に抑えて、ツリーモデルを作成できます。
ShortcutEditorModelItem クラスの定義
ShortcutEditorModelItem クラスは次のように定義されます。
このクラスは基本的な C++ クラスです。QObject を継承しておらず、シグナルやスロットも提供していません。このクラスは、列データを含む QVariant のリストと、ツリー構造内でのその位置に関する情報を保持するために使用されます。関数は以下の機能を提供します:
appendChildItem()は、モデルが最初に構築される際にデータを追加するために使用され、通常の使用時には使用されません。child()およびchildCount()関数を使用すると、モデルは任意の子アイテムに関する情報を取得できます。columnCount()関数により、その項目に関連付けられた列数に関する情報が提供され、各列のデータは data() 関数を使用して取得できます。row()およびparent()関数は、項目の行番号と親項目を取得するために使用されます。
親アイテムと列のデータは、parentItem およびitemData というプライベートメンバー変数に格納されます。childItems 変数には、そのアイテム自身の子アイテムへのポインタのリストが含まれます。
ShortcutEditorModel クラスの定義
ShortcutEditorModel クラスは次のように定義されます。
class ShortcutEditorModel : public QAbstractItemModel
{
Q_OBJECT
class ShortcutEditorModelItem
{
public:
explicit ShortcutEditorModelItem(const QList<QVariant> &data,
ShortcutEditorModelItem *parentItem = nullptr);
~ShortcutEditorModelItem();
void appendChild(ShortcutEditorModelItem *child);
ShortcutEditorModelItem *child(int row) const;
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
int row() const;
ShortcutEditorModelItem *parentItem() const;
QAction *action() const;
private:
QList<ShortcutEditorModelItem *> m_childItems;
QList<QVariant> m_itemData;
ShortcutEditorModelItem *m_parentItem;
};
public:
explicit ShortcutEditorModel(QObject *parent = nullptr);
~ShortcutEditorModel() override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &index = QModelIndex()) const override;
int columnCount(const QModelIndex &index = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
void setActions();
private:
void setupModelData(ShortcutEditorModelItem *parent);
ShortcutEditorModelItem *m_rootItem;
};このクラスは、読み書き可能なモデルを提供する他のほとんどの `QAbstractItemModel ` サブクラスと似ています。このモデルに固有なのは、コンストラクタと `setupModelData() ` 関数の形式のみです。さらに、モデルが破棄される際にクリーンアップを行うためのデストラクタも用意しています。
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を指定するという規約に従います。モデルインデックスは、index() 関数と同様にcreateIndex()を使用して作成されます。
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 のインスタンスを作成するだけで十分です。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.