Qtでツリーモデルを更新する

すべての人に良い一日を! この記事では、QTreeViewとQAbstractItemModelを使用してツリー構造を表示および更新する際に発生する問題について説明します。 これらの困難を回避するために作成した自転車も提供します。



データを表示するために、QtはModelViewパラダイムを使用します。このパラダイムでは、QAbstractItemModelの子孫によってモデルを実装する必要があります。 このクラスは便利に作られていますが、階層のサポートは、私には思えたように、横のどこかに縫い付けられており、あまり便利ではありません。 開発者がドキュメントで認めているように、適切なツリーモデルを構築するのは簡単な作業ではなく、デバッグに役立つように設計されたModelTestでさえ、モデルのエラーを特定するのに必ずしも役立ちません。



私のプロジェクトでは、もう1つの困難に直面しました。外部からの更新です。 実際、QAbstractItemModelでは、要素を含むアクションの前に、どの要素が具体的に削除、追加、移動されたかを明示的に示す必要があります。 私が理解しているように、モデルはView-sまたはQAbstractItemModelメソッドを介してのみ編集されると想定されています。 ただし、変更を正しく「通知」する方法がわからないライブラリの外部モデルを使用する場合、またはモデルが集中的に編集されてその変更に関するメッセージの送信が不採算になる場合、モデル構造の更新は複雑になります。



このような更新の問題を解決し、QAbstractItemModel実装の作成を簡素化するため。 次のアプローチを使用することにしました。ツリーの構造を照会するための単純なインターフェースを作成します。



class VirtualModelAdapter { public: //   virtual int getItemsCount(void *parent) = 0; virtual void * getItem(void *parent, int index) = 0; virtual QVariant data(void *item, int role) = 0; //   void beginUpdate(); void endUpdate(); }
      
      





QAbstractItemModelを実装します。このモデルでは、必要に応じて構造がキャッシュされ、遅延ロードされます。 VirtualModelAdapterを使用して、モデルの更新を単純なsihanizatsiiキャッシュ構造にします。



したがって、beginInsertRows / endInsertRowsおよびbeginRemoveRows / endRemoveRowsの呼び出しのヒープの代わりに、モデルの更新を括弧beginUpdate()endUpdate()で囲み、更新の最後に同期することができます。 同時に、(データではなく)構造のみがキャッシュされ、ユーザーによって表示される部分のみがキャッシュされることに注意してください。 すぐに言ってやった。 ツリーをキャッシュするために、次の構造を使用しました。



 class InternalNode { InternalNode *parent; void *item; size_t parentIndex; std::vector<std::unique_ptr<InternalNode>> children; }
      
      





モデルの構造を更新するには、要素のリストを比較する関数を使用し、不一致がある場合は、新しい要素を挿入して不要な要素を削除します。



 void VirtualTreeModel::syncNodeList(InternalNode &node, void *parent) { InternalChildren &nodes = node.children; int srcStart = 0; int srcCur = srcStart; int destStart = 0; auto index = getIndex(node); while (srcCur <= static_cast<int>(nodes.size())) { bool finishing = srcCur >= static_cast<int>(nodes.size()); int destCur = 0; InternalNode *curNode = nullptr; if (!finishing) { curNode = nodes[srcCur].get(); destCur = m_adapter->indexOf(parent, curNode->item, destStart); } if (destCur >= 0) { // remove skipped source nodes if (srcCur > srcStart) { beginRemoveRows(index, srcStart, srcCur-1); node.eraseChildren(nodes.begin() + srcStart, nodes.begin() + srcCur); if (!finishing) srcCur = srcStart; endRemoveRows(); } srcStart = srcCur + 1; if (finishing) destCur = m_adapter->getItemsCount(parent); // insert skipped new nodes if (destCur > destStart) { int insertCount = destCur - destStart; beginInsertRows(index, srcCur, srcCur + insertCount - 1); for (int i = 0, cur = srcCur; i < insertCount; i++, cur++) { void *obj = m_adapter->getItem(parent, destStart + i); auto newNode = new InternalNode(&node, obj, cur); nodes.emplace(nodes.begin() + cur, newNode); } node.insertedChildren(srcCur + insertCount); endInsertRows(); srcCur += insertCount; destStart += insertCount; } destStart = destCur + 1; if (curNode && curNode->isInitialized(m_adapter)) { syncNodeList(*curNode, curNode->item); srcStart = srcCur + 1; } } srcCur++; } node.childInitialized = true; }
      
      





基本的に、次のシステムが取得されます。BeginUpdate()を呼び出した後にデータ構造が変更し始めると、Viewへのすべての呼び出しはインデックス()、親()などにアドレス指定されます。 キャッシュに変換され、data()は空のQVariant()を返します。 構造の更新が完了したら、endUpdate()を呼び出すと、すべての挿入と削除で同期が行われ、ビューが再描画されます。



例として、次のセクション構造を作成しました。



 class Part { Part *parent; QString name; std::vector<std::unique_ptr<Part>> subParts; }
      
      





これで、表示のために次のクラスを実装するだけで十分です。



 lass VirtualPartAdapter : public VirtualModelAdapter { int getItemsCount(void *parent) override; void * getItem(void *parent, int index) override; QVariant data(void *item, int role) override; void * getItemParent(void *item) override; Part *getValue(void * data); };
      
      







また、外部からの変更には、次のアプローチを使用します。



  m_adapter->beginUpdate(); Part* cur = currentPart(); auto g1 = cur->add("NewType"); g1->add("my class"); g1->add("my struct"); m_adapter->endUpdate();
      
      





さらに簡単な代替方法として、データを変更する前にQueuedUpdate()を呼び出すと、Qt :: QueuedConnectionを介して送信された信号を処理した後に構造が自動的に更新されます。



  m_adapter-> QueuedUpdate(); Part* cur = currentPart(); auto g1 = cur->add("NewType"); g1->add("my class"); g1->add("my struct");
      
      







おわりに



C ++とQtでの私の経験はあまり良くなく、問題は簡単に解決できると感じています。 いずれにせよ、この方法が誰かに役立つことを願っています。 全文と例はgithubにあります。



コメントと批評は大歓迎です。



All Articles