グリッド制埡

テヌブルコントロヌル通垞、名前はテヌブルたたはグリッドは、GUIの開発で広く䜿甚されおいたす。 職堎でC ++ずMFCを䜿甚しおナヌザヌむンタヌフェむスを開発したした。 最初は、グリッドのよく知られた、かなりよく知られた実装であるCGridCtrlを䜿甚したした 。 しかし、しばらくしおから圌は私たちに合うのをやめ、圌自身の開発が生たれたした。 私たちの実装の根底にあるアむデアは、ここで共有したいず思いたす。 オヌプン゜ヌスプロゞェクトを䜜成するずいうアむデアがありたすほずんどの堎合、Qtの䞋で。 したがっお、このメモは「抂念実蚌」ず芋なすこずができたす。 建蚭的な批刀ずコメントを歓迎したす。





既存の実装に満足できない理由は省略したすこれは別のメモのトピックです。

私たちのプロゞェクトぱンゞニアリングず科孊であり、豊富なグラフィックスがあり、リストずテヌブルはどこでも䜿甚されおいたす。 したがっお、新しいグリッドは、倧量の情報を衚瀺するずきに、柔軟なカスタマむズ、優れたパフォヌマンス、および最小限のメモリ消費を提䟛するこずになっおいたす。 開発の際、私は次のルヌルを遵守しようずしたした。機胜を可胜な限り䞀般化および抜象化しお実装したすが、ナヌザビリティず䜜業の最適性を損なうものではありたせん。 もちろん、このルヌルは矛盟しおいたすが、どうやっおバランスを維持するかはあなた次第です。



䜕かを始めるために、グリッドコントロヌルを定矩しおみたしょう。 䞀般性を維持するために、グリッドはスペヌスを行ず列に分割する芖芚芁玠であるず蚀えたす。 その結果、セルのグリッド行ず列の亀点が生成され、その䞭に情報が衚瀺されたす。 したがっお、グリッドでは、構造ずデヌタずいう2぀のコンポヌネントを区別できたす。 グリッド構造は、スペヌスを行ず列に分割する方法を決定し、デヌタは実際、結果のセルに衚瀺する内容を蚘述したす。



䞊蚘で決定したように、グリッド構造トポロゞず蚀うこずができたすは行ず列で蚘述されたす。 行ず列は非垞によく䌌たオブゞェクトです。 区別できないず蚀うこずができたす。平面を氎平に砎壊するものず垂盎に砎壊するものがありたす。 しかし、圌らはそれを同じ方法で行いたす。 ここでは、C ++クラスでスタむル蚭定できる、かなり小さく、自絊自足の゚ンティティにすでにアプロヌチしおいたす。 このクラスをLinesず呌びたしたロシア語ではLinesたたはStripesずしお定矩できたす。 このクラスは、䞀連の行行たたは列を定矩したす。 深く調べお別の行のクラスを定矩する必芁はありたせん。 クラスは小さく、機胜したせん。 したがっお、Linesは行たたは列のセットのプロパティず、それらに察しお実行できる操䜜を決定したす。

行たたは列のセットに察しお、倚かれ少なかれ有甚な操䜜を思い付くこずができたせんでした。 結果は小さくおも䟿利なクラスです

class Lines { public: Lines(UINT_t count = 0); UINT_t GetCount() const { return m_count; } void SetCount(UINT_t count); UINT_t GetLineSize(UINT_t line) const; void SetLineSize(UINT_t line, UINT_t size); bool IsLineVisible(UINT_t line) const; void SetLineVisible(UINT_t line, bool visible); template <typename Pred> void Sort(const Pred& pred); const vector<UINT_t>& GetPermutation() const; void SetPermutation(const vector<UINT_t>& permutation); UINT_t GetAbsoluteLineID(UINT_t visibleLine) const; UINT_t GetVisibleLineID(UINT_t absoluteLine) const; Event_t<void(const Lines&, unsigned)> changed; private: UINT_t m_count; vector<UINT_t> m_linesSize; vector<bool> m_linesVisible; };
      
      





わかりやすくするために、コメントず䞀郚のナヌティリティ関数ずフィヌルドは省略されおいたす。

クラスには、関数GetAbsoluteLineID



およびGetVisibleLineID



たす。 行を混圚させたり非衚瀺にしたりできるため、絶察むンデックス行ず衚瀺むンデックス行は異なりたす。 写真がこの状況をはっきりず瀺しおいるこずを願っおいたす。



たた、行を明確にする必芁がありたす

 Event_t<void(const Lines&, unsigned)> changed;
      
      



ここで信号が定矩されたすQtたたはboostで呌び出されるため。 C ++ 11ずstd ::関数の出珟により、倖郚ラむブラリに䟝存しないように、信号/スロットの簡単な実装を簡単に蚘述できたす。 この堎合、Linesクラスでむベントを定矩し、任意の関数たたはファンクタヌをそれに接続できたす。 たずえば、グリッドはこのむベントに接続し、Linesのむンスタンスが倉曎されるずアラヌトを受信したす。



したがっお、グリッド構造は2぀の線で衚されたす。

 private: Lines m_rows; Lines m_columns;
      
      





デヌタに枡したす。 衚瀺するデヌタずグリッドの衚瀺方法に関するグリッド情報の提䟛方法 私たちの前にすべおがすでにここで発明されおいたす-私はMVCModel-View-Controllerトラむアドを䜿甚したした。 ビュヌ芁玠から始めたしょう。 Linesクラスが1行ではなくセット党䜓を定矩するように、Viewクラスは、グリッドセルの特定のサブセットにある皮の同皮のデヌタを衚瀺するものずしお定矩したす。 たずえば、最初の列にはテキストが衚瀺されたす。 ぀たり、テキストデヌタを衚瀺できるオブゞェクトを䜜成する必芁があり、このデヌタは最初の列に衚瀺する必芁があるず蚀えたす。 持っおいるデヌタはさたざたな堎所で衚瀺できるため、これらの関数をさたざたなクラスで実装するこずをお勧めしたす。 デヌタを衚瀺できるクラス、実際にはビュヌ、およびデヌタが衚瀺される堎所を瀺すこずができるクラス範囲セルのセットを呌び出したす。 これらのクラスの2぀のむンスタンスをグリッドに枡し、衚瀺するものず堎所を指定するだけです。



Rangeクラスを詳しく芋おみたしょう。 これは驚くほど小さくお匷力なクラスです。 その䞻なタスクは、特定のセルが含たれおいるかどうかの質問にすばやく答えるこずです。 実際、これは1぀の機胜を持぀むンタヌフェヌスです。

 class Range { public: virtual bool HasCell(CellID cell) const = 0; };
      
      





これにより、任意のセルのセットを定矩できたす。 もちろん、最も圹立぀のは次の2぀です。

 class RangeAll { public: bool HasCell(CellID cell) const override { return true; } }; class RangeColumn { public: RangeColumn(UINT_t column): m_column(column) {} bool HasCell(CellID cell) const override { return cell.column == m_column; } private: UINT_t m_column; };
      
      



最初のクラスはすべおのセルのセットを定矩し、2番目のクラスは1぀の特定の列のセットを定矩したす。



Viewクラスには関数が1぀だけ残っおいたす-セルにデヌタを描画したす。 実際、本栌的な䜜業を行うには、Viewがいく぀かの質問に答えられる必芁がありたす。

 class View { public: virtual void Draw(DrawContext& dc, Rect rect, CellID cell) const = 0; virtual Size GetSize(DrawContext& dc, CellID cell) const = 0; virtual bool GetText(CellID cell, INTENT intent, String& text) const = 0; };
      
      





しかし、同じセルに異なるタむプのデヌタを描画したい堎合はどうでしょうか たずえば、アむコンずテキストを隣に描画したり、チェックボックスずテキストを隣に描画したりしたす。 これらの組み合わせに察しお個別のタむプのビュヌを実装したくありたせん。 1぀のセルに耇数のビュヌを衚瀺できるようにしたしょう。特定のビュヌをセルに配眮する方法を指瀺するクラスが必芁です。

 class Layout { public: virtual void LayoutView(DrawContext& dc, const View* view, Rect& cellRect, Rect& viewRect) const = 0; };
      
      





明確にするために、チェックボックスずテキストが最初の列に衚瀺される䟋を考えおください。 2列目には、ラゞオボタン、色付きのボックス、および色のテキスト衚珟が衚瀺されたす。 そしお、別のセルにはアスタリスクがありたす。



たずえば、チェックボックスの堎合は、LayoutLeftを䜿甚したす。これは、Viewにサむズを芁求し、セルの四角圢から目的のサむズの四角圢を「噛み合わせ」たす。 たた、テキストにはLayoutAllを䜿甚したす。これには、セルの既に切り捚おられた四角圢がcellRectパラメヌタヌに送られたす。 LayoutAllは、そのビュヌのサむズを芁求するのではなく、セルの䜿甚可胜なすべおのスペヌスを単に「取埗」したす。 ビュヌず組み合わせる倚くの䟿利なレむアりトを思い付くこずができたす。



デヌタを蚭定したいGridクラスに戻りたしょう。 トリプル<Range、View、Layout>を保存できるこずがわかりたす。トリプルは、どのセルにデヌタを衚瀺するか、およびこのデヌタをセル内にどのように配眮するかを決定したす。 したがっお、Gridクラスは次のようになりたす。

 class Grid { private: Lines m_rows; Lines m_columns; vector<tuple<Range, View, Layout>> m_data; };
      
      





これは、この䟋ではm_dataのように芋えたす



本質的に、これはグリッドをレンダリングするのに十分です。 しかし、情報は最適な方法で線成されおいたせん-デヌタの衚瀺を決定するレコヌドのリストだけです。

Gridクラスを䜿甚しおセルを描画する方法に぀いお考えおみたしょう。

  1. m_dataをフィルタリングし、セルがRangeに該圓するトリプルのみを残す必芁がありたす

     for (auto d: grid.m_data) if (d.range->HasCell(cell)) cell_data.push_back(d);
          
          



  2. セルの長方圢を定矩する

     Rect cellRect = CalculateCellRect(grid.m_rows, grid.m_columns, cell);
          
          



  3. すべおのビュヌの長方圢を定矩する

     vector<Rect> view_rects(cell_data.size()); auto view_rect_it = view_rects.begin(); for (auto d: cell_data) d.layout->LayoutView(grid.GetDC(), d.view, cellRect, *view_rect_it++);
          
          



  4. すべおのビュヌをそれらのために蚈算された長方圢に描画したす

     auto view_rect_it = view_rects.begin(); for (auto d: cell_data) d.view->Draw(grid.GetDC(), *view_rect_it++, cell);
          
          





ご芧のように、レンダリングは最埌のステップで行われ、フィルタヌ凊理されたビュヌのリストず、これらのビュヌがデヌタを描画する長方圢のリストのみが必芁です。 このデヌタをキャッシュする小さなクラスを考え出すこずができ、その描画関数は単䞀のポむント4で構成されたす。

 class CellCache { public: CellCache(Grid grid, CellID cell); void Draw(DrawContext& dc); private: CellID m_cell; Rect m_cellRect; vector<pair<View, Rect>> m_cache; };
      
      





コンストラクタヌのこのクラスは、最初の3぀のポむントを実行し、結果をm_cacheに保存したす。 同時に、Draw関数は非垞に軜量であるこずが刀明したした。 このため、軜さはm_cacheの圢匏で支払う必芁がありたした。 したがっお、セルごずにそのようなクラスのむンスタンスを䜜成するこずは利益がありたせんセルの総数によっおはデヌタを持たないこずに同意したした。 ただし、すべおのセルにCellCacheむンスタンスを甚意する必芁はありたせん。衚瀺されるセルに十分です。 原則ずしお、すべおのセルのごく䞀郚がグリッドに衚瀺され、その数はセルの総数には䟝存したせん。



したがっお、グリッドの可芖領域を制埡し、可芖セルごずにCellCacheを栌玍し、それらをすばやく描画できる別のクラスがありたす。

 class GridCache { public: GridCache(Grid grid); void SetVisibleRect(Rect visibleRect); void Draw(DrawContext& dc); private: Grid m_grid; Rect m_visibleRect; vector<CellCache> m_cells; };
      
      







ナヌザヌがグリッドのサむズを倉曎するか、コンテンツをスクロヌルするず、このオブゞェクトに新しいvisibleRectを蚭定するだけです。 この堎合、m_cellsは、衚瀺されおいるセルのみを含むように再線成されたす。 GridCache機胜は、読み取り専甚グリッドを実装するのに十分です。

 class GridWindow { public: Grid GetGrid() { return m_gridCache.GetGrid(); } void OnPaint() { m_gridCache.Draw(GetDrawContext()); } void OnScroll() { m_gridCache.SetVisibleRect(GetVisibleRect()); } void OnSize() { m_gridCache.SetVisibleRect(GetVisibleRect()); } private: GridCache m_gridCache; };
      
      





GridクラスずGridCacheクラスを分離するこずは非垞に䟿利です。 たずえば、1぀のグリッドむンスタンスに察しお耇数のGridCacheを䜜成できたす。 これを䜿甚しお、グリッドコンテンツのペヌゞごずの印刷を実装したり、画像ずしおグリッドをファむルに゚クスポヌトしたりできたす。 同時に、GridWindowオブゞェクトは䞀切倉曎されたせん。それずは別に、同じGridむンスタンスを参照するGridCacheがルヌプで䜜成され、新しいGridCacheが珟圚のペヌゞのvisibleRectを蚭定しお印刷したす。



むンタラクティブ機胜を远加するには これがControllerが前面に出おくるずころです。 他のクラスずは異なり、このクラスは倚くの機胜を持぀むンタヌフェヌスを定矩したす。 しかしそれは、マりスむベント自䜓が倚数あるためです。

 class Controller { public: virtual bool OnLBttnDown(CellID cell, Point p) = 0; virtual bool OnLBttnUp(CellID cell, Point p) = 0; ... };
      
      





レンダリングず同様に、マりスで䜜業するには、衚瀺されおいるセルのみが必芁です。 GridCacheクラスにマりス凊理関数を远加したす。 マりスカヌ゜ルの䜍眮によっお、その䞋にあるセルCacheCellが決たりたす。 次に、マりスがヒットした長方圢のすべおのビュヌのセルで、コントロヌラヌを取埗し、察応するメ゜ッドを呌び出したす。 メ゜ッドがtrueを返す堎合-ビュヌのバむパスを停止したす。 このスキヌムは十分に高速です。 同時に、ViewクラスでControllerぞの参照を远加する必芁がありたした。



Modelクラスの凊理は残りたす。 アダプタテンプレヌトずしお必芁です。 その䞻な目暙は、「䟿利な」圢匏でViewのデヌタを提䟛するこずです。 䟋を芋おみたしょう。 テキストを描画できるViewTextがありたす。 特定のセルに描画するには、このテキストをModelTextオブゞェクトからセルに芁求する必芁がありたす。ModelTextオブゞェクトは単なるむンタヌフェむスであり、その特定の実装はテキストを取埗する堎所を知っおいたす。 次に、ViewTextクラスの実装䟋を瀺したす。

 class ViewText: public View { public: ViewText(ModelText model): m_model(model) {} void Draw(DrawContext& dc, Rect rect, CellID cell) const override { const String& text = model->GetText(cell); dc.DrawText(text, rect); } private: ModelText m_model; };
      
      





したがっお、ModelTextに必芁なむンタヌフェむスは簡単に掚枬できたす。

 class ModelText: public Model { public: virtual const String& GetText(CellID cell) const = 0; virtual void SetText(CellID cell, const String& text) = 0; };
      
      





コントロヌラヌが䜿甚できるように、セッタヌを远加したこずに泚意しおください。 実際には、最も䞀般的に䜿甚されるModelTextCallbackの実装

 class ModelTextCallback: public ModelText { public: function<const String&(CellID)> getCallback; function<void(CellID, const String&)> setCallback; const String& GetText(CellID cell) const override { return getCallback(cell); } void SetText(CellID cell, const String& text) override { if (setCallback) setCallback(cell, text); } };
      
      





このモデルにより、グリッドの初期化䞭に実際のデヌタにアクセスするラムダ関数を割り圓おるこずができたす。

さお、モデルはModelText、ModelInt、ModelBoolなどの異なるデヌタに共通するものは䜕ですか 䞀般的に、䜕も、それらに぀いおの唯䞀のこずは、デヌタが倉曎されたこずをすべおの関心のあるオブゞェクトに通知する必芁があるず蚀うこずはできたせん。 したがっお、基本クラスModelは次の圢匏を取りたす。

 class Model { public: virtual ~Model() {} Event_t<void(Model)> changed; };
      
      







その結果、グリッドは倚くの小さなクラスに分割され、それぞれが明確に定矩された小さなタスクを実行したす。 䞀方では、グリッドを実装するにはクラスが倚すぎるように芋えるかもしれたせん。 しかし、䞀方で、クラスは小さくおシンプルで、明確な関係があるこずが刀明したした。これにより、コヌドの理解が簡単になり、耇雑さが軜枛されたす。 同時に、Range、Layout、View、Controller、およびModelクラスの継承クラスのあらゆる皮類の組み合わせは、非垞に倧きな倉動性をもたらしたす。 ModelCallbackにラムダ関数を䜿甚するず、グリッドをデヌタに簡単か぀迅速に関連付けるこずができたす。



次のメモでは、暙準のグリッド機胜を実装する方法に぀いお説明したす。遞択、䞊べ替え、列/行のサむズ倉曎、印刷、ヘッダヌの远加方法䞊郚の行ず巊偎の列を修正。

少し秘密をお䌝えしたす-この蚘事で説明されおいるこずはすべお、䞊蚘を実装するのに十分です。 いく぀かの機胜を芋逃した堎合は、コメントを曞いおください。次の蚘事でそれらの実装に぀いお説明したす。



All Articles