編集可能なツリーモデルの例
この例では、モデル/ビューフレームワーク内の他のクラスと併用できる、シンプルなアイテムベースのツリーモデルを実装する方法を示します。

このモデルは、編集可能な項目、カスタムヘッダー、および行や列の挿入・削除機能をサポートしています。これらの機能により、新しい子項目の挿入も可能であり、その方法は付属のサンプルコードで示されています。
概要
「モデルのサブクラス化リファレンス」で説明されているように、モデルは、flags()、data()、headerData()、columnCount()、およびrowCount() という一連の標準的なモデル関数の実装を提供する必要があります。さらに、このモデルのような階層型モデルでは、index() およびparent() の実装も提供する必要があります。
編集可能なモデルは、setData() およびsetHeaderData() の実装を提供する必要があり、flags() 関数から適切なフラグの組み合わせを返さなければなりません。
この例ではモデルの次元を変更できるため、insertRows()、insertColumns()、removeRows()、およびremoveColumns() も実装する必要があります。
設計
「単純なツリーモデル」の例と同様に、このモデルはTreeItem クラスのインスタンスの集合を単にラップする役割を果たします。各TreeItem は、ツリービュー内の1行分の項目データを保持するように設計されているため、各列に表示されるデータに対応する値のリストが含まれています。
QTreeView はモデルに対する行指向のビューを提供するため、モデルを介してこの種のビューにデータを供給するデータ構造には、行指向の設計を採用するのが自然です。これによりツリーモデルの柔軟性は低下し、より高度なビューでの使用には不向きになる可能性もありますが、設計の複雑さが軽減され、実装が容易になります。
![]() | 内部アイテム間の関係 カスタムモデルで使用するためのデータ構造を設計する際、TreeItem::parent()のような関数を通じて各項目の親を公開しておくと便利です。これにより、モデル自身の `parent()` 関数の記述が容易になるからです。 同様に、TreeItem::child()のような関数は、モデルのindex() 関数を実装する際に役立ちます。その結果、各 この図は、 示された例では、ルート項目からそのchild()関数を呼び出すことで、2つの最上位項目である AとB を取得できます。また、これらの各項目はparent()関数からルートノードを返しますが、図では項目Aについてのみ示されています。 |
各TreeItem は、itemData というプライベートメンバー(QVariant オブジェクトのリスト)に、それが表す行の各列のデータを格納します。ビューの各列とリストの各エントリの間には1対1の対応関係があるため、itemData リストのエントリを読み取るための単純なdata()関数と、それらを変更するためのsetData()関数を用意しています。 このアイテムの他の関数と同様に、これにより、モデルのdata()およびsetData()関数の実装が簡略化されます。
アイテムのツリーのルートにアイテムを配置します。 このルートアイテムは、モデルインデックスQModelIndex() に対応しており、モデルインデックスの処理において、最上位アイテムの親を表すために使用されます。ルートアイテムはどの標準ビューにも可視的に表示されませんが、その内部のQVariant オブジェクトのリストを使用して、水平ヘッダータイトルとしてビューに渡される文字列のリストを格納します。
![]() | モデルを介したデータへのアクセス 図に示されているケースでは、aで表される情報は、標準のモデル/ビュー API を使用して取得できます: 各`item`は、特定の行の各列に対応するデータを保持しているため、同じ b と同じ行にある他のモデルインデックスの情報を取得する場合も、同じ基になる |
モデルクラス `TreeModel` では、`index()`および`parent()` の実装内で `QAbstractItemModel::createIndex()` を使用して対応するモデルインデックスを作成する際、各アイテムのポインタを渡すことで、`TreeItem ` オブジェクトをモデルインデックスに関連付けます。 この方法で格納されたポインタは、関連するモデルインデックスに対してinternalPointer()関数を呼び出すことで取得できます。この処理を行うために独自のgetItem()関数を作成し、data() およびparent()の実装からそれを呼び出します。
アイテムへのポインタを格納することは、internalPointer() から取得したアドレスが有効なポインタであると仮定できるため、アイテムの作成や破棄の方法を制御できる場合に便利です。しかし、一部のモデルでは、システム内の他のコンポーネントから取得したアイテムを処理する必要があり、多くの場合、アイテムの作成や破棄の方法を完全に制御することはできません。 そのような状況では、モデルが削除済みのアイテムにアクセスしようとしないことを保証するための安全策を講じ、純粋なポインタベースのアプローチを補完する必要があります。
| 基になるデータ構造への情報の格納 いくつかのデータは、各 ` この図は、前の2つの図でラベルa、b、cで表された情報が、基盤となるデータ構造内の項目A、B、Cにどのように格納されているかを示しています。 なお、モデル内の同じ行に属する情報は、すべて同じ項目から取得されることに注意してください。リスト内の各要素は、モデル内の特定の行の各列によって公開される情報に対応しています。 | ![]() |
TreeModel の実装はQTreeView での使用を想定して設計されているため、TreeItem インスタンスの使用方法に制限を追加しました。各アイテムは、同じ数のデータ列を公開する必要があります。 これにより、モデルの表示に一貫性が生まれ、ルートアイテムを用いて任意の行の列数を決定できるようになります。また、列の総数に見合う十分なデータを含むアイテムを作成するという要件が追加されるだけです。その結果、列の挿入や削除は、すべてのアイテムを変更するためにツリー全体を走査する必要があるため、時間がかかる操作となります。
別のアプローチとしては、データが変更されるたびに、個々のTreeItem インスタンス内のデータリストを短縮または拡張するようにTreeModel クラスを設計する方法があります。しかし、この「遅延」リサイズ方式では、各行の末尾でのみ列の挿入や削除が可能となり、各行内の任意の位置で列を挿入または削除することはできません。
![]() | モデルインデックスを用いた項目の関連付け 「Simple Tree Model」の例と同様に、 図では、前の図に示された項目を用いて、モデルのparent()実装が、呼び出し元から渡された項目の親に対応するモデルインデックスをどのように取得するかを示しています。 QModelIndex::internalPointer() 関数を使用して、対応するモデルインデックスから項目Cへのポインタを取得します。 このポインタは、インデックスの作成時に内部的にインデックスに格納されていました。子には親へのポインタが含まれているため、そのparent()関数を使用して、項目B へのポインタを取得します。親のモデルインデックスは、QAbstractItemModel::createIndex() 関数を使用して、項目Bへのポインタを内部ポインタとして渡すことで作成されます。 |
TreeItem クラスの定義
TreeItem クラスは、親や子アイテムに関する情報を含む、いくつかのデータを持つ単純なアイテムを提供します。
class TreeItem
{
public:
explicit TreeItem(QVariantList data, TreeItem *parent = nullptr);
TreeItem *child(int number);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
bool insertChildren(int position, int count, int columns);
bool insertColumns(int position, int columns);
TreeItem *parent();
bool removeChildren(int position, int count);
bool removeColumns(int position, int columns);
int row() const;
bool setData(int column, const QVariant &value);
private:
std::vector<std::unique_ptr<TreeItem>> m_childItems;
QVariantList itemData;
TreeItem *m_parentItem;
};各アイテムに、情報の列数の取得、データの読み書き、および列の追加・削除を行う関数を用意することで、QAbstractItemModel が提供するAPIと同様の設計としています。ただし、「行」ではなく「子」を扱う関数を提供することで、アイテム間の関係を明示的にしています。
各アイテムには、子アイテムへのポインタのリスト、親アイテムへのポインタ、およびモデル内の特定の行の列に格納されている情報に対応するQVariant オブジェクトのリストが含まれています。
TreeItem クラスの実装
各TreeItem は、データのリストと、オプションで親アイテムを指定して生成されます:
TreeItem::TreeItem(QVariantList data, TreeItem *parent)
: itemData(std::move(data)), m_parentItem(parent)
{}初期状態では、各アイテムには子アイテムがありません。これらは、後述するinsertChildren() 関数を使用して、アイテムの内部メンバーであるchildItems に追加されます。
子要素は std::unique_ptr に格納され、アイテム自体が削除された際に、そのアイテムに追加された各子要素も確実に削除されるようにしています。
各アイテムは親へのポインタを保持しているため、parent() 関数は単純なものです:
TreeItem *TreeItem::parent()
{
return m_parentItem;
}3つの関数が、項目の子要素に関する情報を提供します。child() は、内部の子要素リストから特定の子要素を返します:
TreeItem *TreeItem::child(int number)
{
return (number >= 0 && number < childCount())
? m_childItems.at(number).get() : nullptr;
}childCount() 関数は、子要素の総数を返します:
int TreeItem::childCount() const
{
return int(m_childItems.size());
}row() 関数は、親の子供リストにおける子供のインデックスを特定するために使用されます。この関数は、親のchildItems メンバに直接アクセスして、この情報を取得します:
int TreeItem::row() const
{
if (!m_parentItem)
return 0;
const auto it = std::find_if(m_parentItem->m_childItems.cbegin(), m_parentItem->m_childItems.cend(),
[this](const std::unique_ptr<TreeItem> &treeItem) {
return treeItem.get() == this;
});
if (it != m_parentItem->m_childItems.cend())
return std::distance(m_parentItem->m_childItems.cbegin(), it);
Q_ASSERT(false); // should not happen
return -1;
}ルート項目には親項目がありません。この項目については、他の項目との整合性を保つため、0を返します。
columnCount() 関数は、QVariant オブジェクトの内部itemData リストに含まれる要素数を単純に返すだけです:
int TreeItem::columnCount() const
{
return int(itemData.count());
}データは、data() 関数を使用して取得されます。この関数は、itemData リスト内の適切な要素にアクセスします:
QVariant TreeItem::data(int column) const
{
return itemData.value(column);
}データの設定は、setData() 関数を使用して行われます。この関数は、モデル内の列値に対応する有効なリストインデックスについてのみ、itemData リストに値を格納します:
bool TreeItem::setData(int column, const QVariant &value)
{
if (column < 0 || column >= itemData.size())
return false;
itemData[column] = value;
return true;
}モデルの実装を容易にするため、データが正常に設定されたことを示すためにtrueを返します。
編集可能なモデルでは、行や列の挿入・削除が可能となるよう、サイズ変更が可能な必要があることがよくあります。モデル内の特定のモデルインデックスの下に 行を挿入すると、対応するアイテムに新しい子アイテムが挿入されますが、これはinsertChildren() 関数によって処理されます:
bool TreeItem::insertChildren(int position, int count, int columns)
{
if (position < 0 || position > qsizetype(m_childItems.size()))
return false;
for (int row = 0; row < count; ++row) {
QVariantList data(columns);
m_childItems.insert(m_childItems.cbegin() + position,
std::make_unique<TreeItem>(data, this));
}
return true;
}これにより、新しいアイテムが所定の列数で作成され、内部のchildItems リスト内の適切な位置に挿入されることが保証されます。アイテムの削除は、removeChildren() 関数で行います:
bool TreeItem::removeChildren(int position, int count)
{
if (position < 0 || position + count > qsizetype(m_childItems.size()))
return false;
for (int row = 0; row < count; ++row)
m_childItems.erase(m_childItems.cbegin() + position);
return true;
}前述の通り、列の挿入および削除を行う関数は、ツリー内のすべての項目に対して呼び出されることが想定されているため、子項目の挿入および削除を行う関数とは異なる方法で使用されます。これを実現するために、項目の各子項目に対してこの関数を再帰的に呼び出します:
bool TreeItem::insertColumns(int position, int columns)
{
if (position < 0 || position > itemData.size())
return false;
for (int column = 0; column < columns; ++column)
itemData.insert(position, QVariant());
for (auto &child : std::as_const(m_childItems))
child->insertColumns(position, columns);
return true;
}TreeModel クラスの定義
TreeModel クラスは、QAbstractItemModel クラスの実装を提供し、編集やサイズ変更が可能なモデルに必要なインターフェースを公開しています。
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_DISABLE_COPY_MOVE(TreeModel)
TreeModel(const QStringList &headers, const QString &data,
QObject *parent = nullptr);
~TreeModel() override;コンストラクタとデストラクタは、このモデルに固有のものです。
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;読み取り専用のツリーモデルでは、上記の関数のみを提供すれば十分です。以下のパブリック関数は、編集やサイズ変更の機能をサポートしています:
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
bool setHeaderData(int section, Qt::Orientation orientation,
const QVariant &value, int role = Qt::EditRole) override;
bool insertColumns(int position, int columns,
const QModelIndex &parent = {}) override;
bool removeColumns(int position, int columns,
const QModelIndex &parent = {}) override;
bool insertRows(int position, int rows,
const QModelIndex &parent = {}) override;
bool removeRows(int position, int rows,
const QModelIndex &parent = {}) override;
private:
void setupModelData(const QList<QStringView> &lines);
TreeItem *getItem(const QModelIndex &index) const;
std::unique_ptr<TreeItem> rootItem;
};この例を簡略化するため、モデルによって公開されるデータは、モデルのsetupModelData()関数によってデータ構造に整理されています。実際の多くのモデルでは、生データを一切処理せず、既存のデータ構造やライブラリの API を利用して動作します。
TreeModel クラスの実装
コンストラクタはルート項目を作成し、指定されたヘッダーデータを用いて初期化します:
TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent)
: QAbstractItemModel(parent)
{
QVariantList rootData;
for (const QString &header : headers)
rootData << header;
rootItem = std::make_unique<TreeItem>(rootData);
setupModelData(QStringView{data}.split(u'\n'));
}内部関数setupModelData()を呼び出し、渡されたテキストデータを、モデルで使用可能なデータ構造に変換します。他のモデルでは、既成のデータ構造を使用して初期化したり、独自のデータを管理するライブラリの API を利用したりする場合もあります。
TreeModel::~TreeModel() = default;デストラクタでは、ルート項目を削除するだけでよく、これによりすべての子項目が再帰的に削除されます。ルート項目は unique_ptr 内に格納されているため、これはデフォルトのデストラクタによって自動的に行われます。
モデルの他のモデル/ビューコンポーネントとのインターフェースはモデルインデックスに基づいており、内部データ構造はアイテムベースであるため、モデルによって実装される関数の多くは、任意のモデルインデックスを対応するアイテムに変換できる必要があります。利便性と一貫性を確保するため、この反復的なタスクを実行するgetItem() 関数を定義しました:
TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
if (index.isValid()) {
if (auto *item = static_cast<TreeItem*>(index.internalPointer()))
return item;
}
return rootItem.get();
}この関数に渡される各モデルインデックスは、メモリ内の有効なアイテムに対応している必要があります。インデックスが無効である場合、またはその内部ポインタが有効なアイテムを指していない場合は、代わりにルートアイテムが返されます。
モデルのrowCount() の実装は単純です。まずgetItem() 関数を使用して関連するアイテムを取得し、次にそのアイテムが持つ子アイテムの数を返します:
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() > 0)
return 0;
const TreeItem *parentItem = getItem(parent);
return parentItem ? parentItem->childCount() : 0;
}対照的に、columnCount() の実装では、すべての項目に割り当てられた列の数が同じと定義されているため、特定の項目を探す必要はありません。
int TreeModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return rootItem->columnCount();
}その結果、列数はルート項目から直接取得することができます。
項目の編集や選択を可能にするには、flags() 関数を実装し、Qt::ItemIsEditable フラグとQt::ItemIsSelectable フラグに加え、Qt::ItemIsEnabled フラグを含むフラグの組み合わせを返すようにする必要があります:
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}モデルは、他のコンポーネントがその構造に関するデータや情報を要求できるように、モデルインデックスを生成できる必要があります。このタスクはindex() 関数によって実行され、この関数は、指定された親項目の子項目に対応するモデルインデックスを取得するために使用されます:
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() != 0)
return {};このモデルでは、親インデックスが無効(ルート項目に対応)である場合、または列番号がゼロである場合にのみ、子項目のモデルインデックスを返します。
カスタム関数 `getItem()`を使用して、指定されたモデルインデックスに対応する `TreeItem ` インスタンスを取得し、指定された行に対応する子アイテムを要求します。
TreeItem *parentItem = getItem(parent);
if (!parentItem)
return {};
if (auto *childItem = parentItem->child(row))
return createIndex(row, column, childItem);
return {};
}各アイテムにはデータ行全体の情報が含まれているため、行番号、列番号、およびアイテムへのポインタを引数としてcreateIndex()を呼び出し、それを一意に識別するためのモデルインデックスを作成します。data()関数では、アイテムポインタと列番号を使用して、モデルインデックスに関連付けられたデータにアクセスします。このモデルでは、データを識別するために行番号は必要ありません。
parent() 関数は、指定されたモデルインデックスに対応するアイテムを検索し、そのparent()関数を使用して親アイテムを取得した後、親を表すモデルインデックスを作成することで、アイテムの親に対するモデルインデックスを提供します(上の図を参照)。
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
TreeItem *childItem = getItem(index);
TreeItem *parentItem = childItem ? childItem->parent() : nullptr;
return (parentItem != rootItem.get() && parentItem != nullptr)
? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}ルートアイテムを含む親のないアイテムについては、nullのモデルインデックスを返すことで処理されます。それ以外の場合は、index()関数と同様にモデルインデックスが作成され、適切な行番号とともに返されますが、index()の実装で使用されている方式と整合性を保つため、列番号は0に設定されます。
モデルのテスト
QAbstractItemModelTester アイテムモデルを正しく実装するのは難しい場合があります。 Qt Test モジュールに含まれるxml-ph-0000@deepl.internalクラスは、モデルインデックスの作成や親子関係など、モデルの整合性をチェックします。
例えば、Qt Testの単体テストの一環として、モデルインスタンスをクラスのコンストラクタに渡すだけで、モデルをテストすることができます:
classTestEditableTreeModel :publicQObject
{
Q_OBJECT
private slots:
voidtestTreeModel();
};
voidTestEditableTreeModel::testTreeModel()
{
constexprautofileName= ":/default.txt"_L1;
QFile file(fileName);
QVERIFY2(file.open(QIODevice::ReadOnly|QIODevice::Text),
qPrintable(fileName + " cannot be opened: "_L1 + file.errorString()));
constQStringList headers{"column1"_L1, "column2"_L1};
TreeModel model(headers,QString::fromUtf8(file.readAll()));
QAbstractItemModelTester tester(&model);
}
QTEST_APPLESS_MAIN(TestEditableTreeModel)
#include "test.moc"ctest 実行ファイルを使用して実行できるテストを作成するには、add_test() を使用します:
# Unit Test
include(CTest)
qt_add_executable(editabletreemodel_tester
test.cpp
treeitem.cpp treeitem.h
treemodel.cpp treemodel.h)
target_link_libraries(editabletreemodel_tester PRIVATE
Qt6::Core
Qt6::Test
)
if(ANDROID)
target_link_libraries(editabletreemodel_tester PRIVATE
Qt6::Gui
)
endif()
qt_add_resources(editabletreemodel_tester "editabletreemodel_tester"
PREFIX
"/"
FILES
${editabletreemodel_resource_files}
)
add_test(NAME editabletreemodel_tester
COMMAND editabletreemodel_tester)© 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.



