グラフィカルインタフェースVIII: ツリービューコントロール(チャプター2)
コンテンツ
- はじめに
- ツリービュ要素
- ツリービュ要素作成に使われるCTreeItemクラスの開発
- マウスポインタ作成に使われるCPointerクラスの開発
- ツリービュ作成に使われるCTreeViewクラスの開発
- ツリービュ要素の検証
- おわりに
はじめに
このライブラリの目的についてのより詳しい情報は一番初めの記事であるグラフィカルインタフェース I:ライブラリストラクチャの準備(チャプター 1)にあります。リンクを含むチャプターのリストは各部の記事の終わりに設けられています。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
前のグラフィカルインターフェイス第八部では静的およびドロップダウンカレンダー要素に焦点が当てられました。この第2章は、グラフィカルインタフェースを作成するために使用されるすべての完全なライブラリーに含まれているツリービューという均等に複雑な要素に焦点を当てます。本稿で実装されるツリービューは複数の柔軟な設定とモードを含み、ニーズに合わせてコントロール要素を調整することができます。
本稿では、また、マウスポインタを作成するために使用されるあと1つの有用なクラスの実装が説明されます。それに加えて、ツリービュー内でのこのタイプの要素の使用例も与えられます。
ツリービュ要素
単純なリストとは異なり、ツリービューは含まれる要素をカテゴリ別に無制限のネストレベルで配置することができます。他の(1以上の)要素のリストが含まれている各要素(項目またはノード)は、ローカルリストを展開/崩壊することを可能にするコントロールで装備されています。各項目は、テキスト記述に加えて、ユーザの経験を簡素化するためのラベル(ピクトグラム)を有することもできます。(ローカルリストを持っていない)エンドポイントは、グラフィカルインタフェースでアプリケーションを表示したり特定の機能を実行するためのコントロールのグループを含むことができます。
ツリービューは、確立された階層を有する要素とカタログを作成するために使用することができます。例えば、これは、ウィンドウズエクスプローラで行われるのと同様のファイルナビゲータの作成に使用することができます。MetaTraderとMetaEditorにもツリービューが適用されるナビゲーションウィンドウがあります。
MetaTraderでは「Ctrl+N」を押すことによって「ナビゲータ」ウィンドウの表示/非表示が可能です(下記のスクリーンショットを参照)。
図1 MetaTraderの「ナビゲータ」ウィンドウ
MetaEditorでは 「Ctrl+D」を押すことによって「ナビゲータ」ウィンドウの表示/非表示が可能です(下記のスクリーンショットを参照)。
図2 MetaEditorの「ナビゲータ」ウィンドウ
ツリービュ要素作成に使われるCTreeItemクラスの開発
ツリービュークラスを書く前に、さらに2つの要素の作成が必要です。ツリービュー項目は重要なものです。その構造体はグラフィカルインタフェース II:メニュー項目要素(チャプター1)稿で見られたコンテキストメニュー項目(CMenuItem)に似ていますが、ユニークなプロパティを保持するため、作成には別のクラスが必要です。
2番目の要素はマウスポインタです。それはリストビュー領域の幅を変更する準備の状態を評価するための指標として使用されます。このタイプの要素を作成するためには、後で他の要素で使用することができるCPointer と呼ばれる別のクラスを記述します。次のセクションではCPointerクラスが考察されます。
初めから、ツリービュー項目がどのオブジェクトから収集されるかを確認する必要があります。
ツリービュー項目のコンポーネントは下記で一覧できます。.
- 背景
- 項目のローカルリストのサインステータスの表示には矢印とプラス/マイナス絵文字が使用されています(展開されている/折りたたまれている)。
- 項目ラベルこれは、例えば視覚的に特定のカテゴリに項目を分類するために必要とされ得ます。
- テキストでの項目の説明
図3 ツリービュー項目のコンポーネント
TreeItem.mqhというファイル名のファイルを作成しライブラリ(WndContainer.mqh)に接続します。
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "TreeItem.mqh"
TreeItem.mqhファイルではすべてのライブラリコントロールに標準的な方法でCTreeItem クラスを作成します(下記のコードを参照)。
//+------------------------------------------------------------------+ //| TreeItem.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" #include "Window.mqh" //+------------------------------------------------------------------+ //| ツリービュ項目作成クラス | //+------------------------------------------------------------------+ class CTreeItem : public CElement { private: //--- 要素が取り付けられるフォームへのポインタ CWindow *m_wnd; //--- public: CTreeItem (void); ~CTreeItem (void); //--- フォームへのポインタを格納する void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } //--- public: //--- チャートイベントの処理 virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- タイマー virtual void OnEventTimer(void) {} //--- 要素の移動 virtual void Moving(const int x,const int y); //--- (1)表示 (2)非表示 (3)リセット (4)削除 virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); //--- マウスの左クリックの優先順位の(1)設定と(2)リセット virtual void SetZorders(void); virtual void ResetZorders(void); //--- 色をリセットする virtual void ResetColors(void); };
要素を作成する前にはその外観を設定するために以下が利用できます。
- さまざまな状態の項目の背景色
- 項目ラベル
- ローカルリストの現在のステータス(展開されている/折りたたまれている)を表示する矢印アイコン
- テキストラベルオフセット
- 異なる状態でのテキストの色
class CTreeItem : public CElement { private: //--- 異なる状態での背景色 color m_item_back_color; color m_item_back_color_hover; color m_item_back_color_selected; //--- 項目矢印のアイコン string m_item_arrow_file_on; string m_item_arrow_file_off; string m_item_arrow_selected_file_on; string m_item_arrow_selected_file_off; //--- 項目ラベル string m_icon_file; //--- テキストラベルオフセット int m_label_x_gap; int m_label_y_gap; //--- 異なる項目の状態でのテキストの色 color m_item_text_color; color m_item_text_color_hover; color m_item_text_color_selected; //--- public: //--- 項目の背景色 void ItemBackColor(const color clr) { m_item_back_color=clr; } void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorSelected(const color clr) { m_item_back_color_selected=clr; } //--- (1) 項目ラベルを設定する (2) 項目矢印のアイコンを設定する void IconFile(const string file_path) { m_icon_file=file_path; } void ItemArrowFileOn(const string file_path) { m_item_arrow_file_on=file_path; } void ItemArrowFileOff(const string file_path) { m_item_arrow_file_off=file_path; } void ItemArrowSelectedFileOn(const string file_path) { m_item_arrow_selected_file_on=file_path; } void ItemArrowSelectedFileOff(const string file_path) { m_item_arrow_selected_file_off=file_path; } //--- (1) 項目テキストを返す (2) テキストラベルのオフセットを設定する string LabelText(void) const { return(m_label.Description()); } void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; } void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; } //--- 異なる状態でのテキストの色 void ItemTextColor(const color clr) { m_item_text_color=clr; } void ItemTextColorHover(const color clr) { m_item_text_color_hover=clr; } void ItemTextColorSelected(const color clr) { m_item_text_color_selected=clr; } };
ツリータイプリストの作成には4つのprivateメソッドと1つのpublic メソッドが必要です。テキストラベルとしてはCEditのようなグラフィックオブジェクトが使われることにご注意ください。まもなく、この目的のために何が必要とされるかを指定します。
class CTreeItem : public CElement { private: //--- ツリータイプビュー作成のためのオブジェクト CRectLabel m_area; CBmpLabel m_arrow; CBmpLabel m_icon; CEdit m_label; //--- public: //--- ツリータイプ項目作成メソッド bool CreateTreeItem(const long chart_id,const int subwin,const int x,const int y,const ENUM_TYPE_TREE_ITEM type, const int list_index,const int node_level,const string item_text,const bool item_state); //--- private: bool CreateArea(void); bool CreateArrow(void); bool CreateIcon(void); bool CreateLabel(void); };
他の項目に関してリスト内の項目を配置するために使用されるパラメータの値はCTreeItem::CreateTreeItem()パブリックメソッドに送信される必要があります。それらのいくつかは要素を収集するために使用されるグラフィックオブジェクトの名前の形成に参加します。要素オブジェクトを作成するためにプライベートメソッドで使用される送信パラメータの値でクラスの関連フィールドが初期化されています。
主な特徴は次の通りです。
- 項目のタイプは (1) ファイナルノードであって他の項目を含まないシンプル項目(TI_SIMPLE) または (2) 項目を含むもの(TI_HAS_ITEMS)の2つの選択肢から選ぶことができます。よってENUM_TYPE_TREE_ITEM列挙がEnums.mqhに追加されます。
//+------------------------------------------------------------------+ //| ツリービュー項目の型の列挙 | //+------------------------------------------------------------------+ enum ENUM_TYPE_TREE_ITEM { TI_SIMPLE =0, TI_HAS_ITEMS =1 };
- リストでの総合インデックス
- ノードレベル(番号)
- 表示されたテキスト(説明)
- 項目の状態
矢印のオフセットの計算方法(項目のローカルリストのサイン)にご注意ください。最終的には、ツリービューの各ノードは、1つ前のノードからこの文字列で計算した値に移動させられます。
class CTreeItem : public CElement { private: //--- 矢印オフセット(サイン) int m_arrow_x_offset; //--- 項目タイプ ENUM_TYPE_TREE_ITEM m_item_type; //--- 総合リストでの項目のインデックス int m_list_index; //--- ノードのレベル int m_node_level; //--- 表示された項目テキスト string m_item_text; //--- 項目リストの状態(展開された/折りたたまれた) bool m_item_state; }; //+------------------------------------------------------------------+ //| ツリー型の項目を作成する | //+------------------------------------------------------------------+ bool CTreeItem::CreateTreeItem(const long chart_id,const int subwin,const int x,const int y,const ENUM_TYPE_TREE_ITEM type, const int list_index,const int node_level,const string item_text,const bool item_state) { //--- フォームへのポインタがない場合終了する if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a list item, we need to send " "a pointer to the form CTreeItem::WindowPointer(CWindow &object)") to the class list; return(false); } //--- 変数の初期化 m_id =m_wnd.LastId()+1; m_chart_id =chart_id; m_subwin =subwin; m_x =x; m_y =y; m_item_type =type; m_list_index =list_index; m_node_level =node_level; m_item_text =item_text; m_item_state =item_state; m_arrow_x_offset =(m_node_level>0)?(12*m_node_level)+5 : 5; //--- 端点からのオフセット CElement::XGap(CElement::X()-m_wnd.X()); CElement::YGap(CElement::Y()-m_wnd.Y()); //--- メニュー項目の作成 if(!CreateArea()) return(false); if(!CreateArrow()) return(false); if(!CreateIcon()) return(false); if(!CreateLabel()) return(false); //--- ダイアログウィンドウか最小化されたウィンドウのコントロールを非表示にする if(m_wnd.WindowType()==W_DIALOG || m_wnd.IsMinimized()) Hide(); //--- return(true); }
要素オブジェクトの名前を形成するためには以下が使用されます(コードは以下を参照)。
- プログラム名
- 要素インデックス
- 要素に属するというサイン(«treeitem»)
- 要素部分に属するというサイン
- ツリー型項目の総合インデックス
- 要素の識別子
ここでは、例としてCTreeItem::CreateArrow()をコンパウンドするオブジェクトを作成するためのプライベートメソッドを使用します。ライブラリーには、デフォルトではドロップダウンリストのアイコンがあります。 本稿の末尾にあるリンクからのダウンロードが可能です。必要に応じて、それらは要素を作成する前に再決定することができます。
このオブジェクトの要素の左側からのオフセットは、要素作成のメインメソッドで 要素オブジェクト作成前にm_node_levelパラメータ(ノードレベル)で計算されます。それがシンプルな項目タイプ(TI_SIMPLE)の場合、プログラムはメソッドを終了します。このオブジェクトの座標は項目タイプの確認のために保存されなければならない(メソッド終了前に)のでご注意ください。 これは後程要素オブジェクト作成の際に座標を計算するために使われます。同じ原則は項目ラベルを作成するCTreeItem::CreateIcon() メソッドにも当てはまります。
ここでは、オブジェクトは作成直後にitem_state値によってメインメソッドに送信される状態が決められ、ツリービューの作成後にどちらの項目が開けられるかをコントロールするのに使われます。
//+------------------------------------------------------------------+ //| 矢印を作成する(ドロップダウンリストのサイン) | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_white.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_black.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_white.bmp" //--- bool CTreeItem::CreateArrow(void) { //--- 座標を計算する int x =CElement::X()+m_arrow_x_offset; int y =CElement::Y()+2; //--- 要素オブジェクト作成キューでの座標計算のために座標を保存する m_arrow.X(x); m_arrow.Y(y); //--- ポイントにドロップダウンリストがない場合は終了する if(m_item_type!=TI_HAS_ITEMS) return(true); //--- オブジェクト名の形成 string name=CElement::ProgramName()+"_"+(string)CElement::Index()+"_treeitem_arrow_"+(string)m_list_index+"__"+(string)CElement::Id(); //--- デフォルトのアイコンを設定する if(m_item_arrow_file_on=="") m_item_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_black.bmp"; if(m_item_arrow_file_off=="") m_item_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\RightTransp_black.bmp"; if(m_item_arrow_selected_file_on=="") m_item_arrow_selected_file_on="Images\\EasyAndFastGUI\\Controls\\RightTransp_rotate_white.bmp"; if(m_item_arrow_selected_file_off=="") m_item_arrow_selected_file_off="Images\\EasyAndFastGUI\\Controls\\RightTransp_white.bmp"; //--- オブジェクトを設定する if(!m_arrow.Create(m_chart_id,name,m_subwin,x,y)) return(false); //--- 優先順位の設定 m_arrow.BmpFileOn("::"+m_item_arrow_file_on); m_arrow.BmpFileOff("::"+m_item_arrow_file_off); m_arrow.State(m_item_state); m_arrow.Corner(m_corner); m_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor); m_arrow.Selectable(false); m_arrow.Z_Order(m_arrow_zorder); m_arrow.Tooltip("\n"); //--- 端点からのオフセット m_arrow.XGap(x-m_wnd.X()); m_arrow.YGap(y-m_wnd.Y()); //--- オブジェクトポインタを格納する CElement::AddToArray(m_arrow); return(true); }
作成後にはツリービューの色とサイズ(幅)を管理するためのメソッドが必要になります。ツリービューの設定は、階層リスト以外の領域では、ツリービューの右側に選択された項目の内容を持つ領域を表示するモードを有効にできるように実装されています。 言い換えれば、この領域のツリービューリストの選択項目に含まれる項目のリストがあります。これらの領域の境界の上でマウスをホバーしてマウスの左ボタンを長押しすると、ツリービューエリアの幅およびコンテンツリストを(同時に)変更することができます。
領域の幅を変更する場合(1)<B0>と</ B0>コンテンツリスト項目オブジェクトのX 座標と(2)両方のリストの項目オブジェクトの幅が更新されます。よって、項目テキストにはCLabel型の代わりにCEdit型が使われます。CLabel型のオブジェクトが使われると、リスト領域のサイズが変更される際に、テキストラベルがフォーム(ウィンドウ)の境界を越えてしまうという問題があるかもしれません。
X座標の更新のためにはCTreeItem::UpdateX() メソッドを書きます。X座標の設定は、このメソッドに送信されるべきです。この場合、すべての計算はツリービュークラス内で行われます。これについてはもうすぐお話しします。
class CTreeItem : public CElement { public: void UpdateX(const int x); }; //+------------------------------------------------------------------+ //| X座標を更新する | //+------------------------------------------------------------------+ void CTreeItem::UpdateX(const int x) { //--- 共通座標と端点からのオフセットを更新する CElement::X(x); CElement::XGap(CElement::X()-m_wnd.X()); //--- 座標と背景オフセット m_area.X_Distance(CElement::X()); m_area.XGap(CElement::X()-m_wnd.X()); //--- 座標と矢印オフセット int l_x=CElement::X()+m_arrow_x_offset; m_arrow.X(l_x); m_arrow.X_Distance(l_x); m_arrow.XGap(l_x-m_wnd.X()); //--- 座標とアイコンオフセット l_x=m_arrow.X()+17; m_icon.X(l_x); m_icon.X_Distance(l_x); m_icon.XGap(l_x-m_wnd.X()); //--- 座標とテキストラベルオフセット l_x=(m_icon_file=="")?m_icon.X() : m_icon.X()+m_label_x_gap; m_label.X(l_x); m_label.X_Distance(l_x); m_label.XGap(l_x-m_wnd.X()); }
リスト項目の幅の変更にはCTreeItem::UpdateWidth() メソッドが使われるべきです。
class CTreeItem : public CElement { public: void UpdateWidth(const int width); }; //+------------------------------------------------------------------+ //| 幅の更新 | //+------------------------------------------------------------------+ void CTreeItem::UpdateWidth(const int width) { //--- 背景の幅 CElement::XSize(width); m_area.XSize(width); m_area.X_Size(width); //--- テキストラベルの幅 int w=CElement::X2()-m_label.X()-1; m_label.XSize(w); m_label.X_Size(w); }
X座標と項目幅の更新に加えてY座標更新のメソッドも必要になります。要素の両方のリストの実装は、このシリーズで以前に考察されたCListView、CLabelsTable及びCTable型のように項目の数を表示するだけではなく、リスト項目がCTreeView要素の実装時に即時作成されることを前提としています。ここでは技術が適用され、リスト領域に収まらない項目は表示されなくなります。ここでは、リストが下向きにスクロールされたり項目のローカルリストが展開される/折りたたまれる際には、オブジェクトのいくつかのパラメータの定期的な更新の代わりに、項目の可視性のみが管理されます。現時点で必要とされていない項目を非表示にするので十分である場合にはY座標は表示される時点で更新される必要があります。このためにはCTreeItem::UpdateY() メソッドが書かれます。下記がコードです。
class CTreeItem : public CElement { public: void UpdateY(const int y); }; //+------------------------------------------------------------------+ //| Y座標を更新する | //+------------------------------------------------------------------+ void CTreeItem::UpdateY(const int y) { //--- 共通座標と端点からのオフセットを更新する CElement::Y(y); CElement::YGap(CElement::Y()-m_wnd.Y()); //--- 座標と背景オフセット m_area.Y_Distance(CElement::Y()); m_area.YGap(CElement::Y()-m_wnd.Y()); //--- 座標と矢印オフセット int l_y=CElement::Y()+2; m_arrow.Y(l_y); m_arrow.Y_Distance(l_y); m_arrow.YGap(l_y-m_wnd.Y()); //--- 座標とアイコンオフセット l_y=CElement::Y()+2; m_icon.Y(l_y); m_icon.Y_Distance(l_y); m_icon.YGap(l_y-m_wnd.Y()); //--- 座標とテキストラベルオフセット l_y=CElement::Y()+m_label_y_gap; m_label.Y(l_y); m_label.Y_Distance(l_y); m_label.YGap(l_y-m_wnd.Y()); }
項目の色の管理はツリービュークラス(CTreeView)で行われ、ここでは下記のメソッドが必要です。
- 示された状態に関する項目の色の変更
- マウスのホバ-時の色の変更
これらのメソッドのコードは下記です。
class CTreeItem : public CElement { public: //--- 項目が強調表示されているかどうかに応じて色を変更する void HighlightItemState(const bool state); //--- マウスのホバ-時に色を変更する void ChangeObjectsColor(void); }; //+------------------------------------------------------------------+ //| 項目が強調表示されているかどうかに応じて色を変更する | //+------------------------------------------------------------------+ void CTreeItem::HighlightItemState(const bool state) { m_area.BackColor((state)?m_item_back_color_selected : m_item_back_color); m_label.BackColor((state)?m_item_back_color_selected : m_item_back_color); m_label.BorderColor((state)?m_item_back_color_selected : m_item_back_color); m_label.Color((state)?m_item_text_color_selected : m_item_text_color); m_arrow.BmpFileOn((state)?"::"+m_item_arrow_selected_file_on : "::"+m_item_arrow_file_on); m_arrow.BmpFileOff((state)?"::"+m_item_arrow_selected_file_off : "::"+m_item_arrow_file_off); } //+------------------------------------------------------------------+ //| マウスのホバ-時にオブジェクトの色を変更する | //+------------------------------------------------------------------+ void CTreeItem::ChangeObjectsColor(void) { if(CElement::MouseFocus()) { m_area.BackColor(m_item_back_color_hover); m_label.BackColor(m_item_back_color_hover); m_label.BorderColor(m_item_back_color_hover); m_label.Color(m_item_text_color_hover); } else { m_area.BackColor(m_item_back_color); m_label.BackColor(m_item_back_color); m_label.BorderColor(m_item_back_color); m_label.Color(m_item_text_color); } }
さらに、グラフィックインターフェースでマウスカーソルのポインタを作成することを目的とするクラスも検討します。
マウスポインタ作成に使われるCPointerクラスの開発
ツリービューの要素は2つの領域で構成されています。階層リストは、左側の領域に配置されます。右側には項目のコンテンツがツリービューで強調表示されています。詳細は以下に提供されており、ここでは、これらの領域の大きさ(幅)は、例えばウィンドウズエクスプローラで行われているように変更できるという事実に集中する必要があります。マウスをツリービューとコンテンツ領域の境界線上でホバーすると、ポインタのラベルは両側矢印に変更されます。開発中のライブラリのマウスカーソルのポインタを管理するためのCPointerクラスを作成しましょう。 現在の端末バージョンでは、マウスのシステムカーソルはMQLで置き換えることはできませんが、ユーザーのアイコンを追加することはでき、グラフィックインターフェイスをよりユーザーフレンドリーにできます。
ライブラリの他の要素と同様に、CPointerクラスはCElementクラスから継承されます。使用される要素のみに接続すればいいです。このタイプのオブジェクトのはどこにもリンクされていないので、フォームへのポインタは必要でありません。要素のベースへの入力も不必要で、この要素のコントロールは接続されているクラスに完全に受け持たれます。
他のファイルとライブラリが配置されているディレクトリに、CPointerクラスを持つPointer.mqhファイルを作成します。ポインタ作成にはCBmpLabel型のオブジェクトを使います。CBmpLabelクラスは、その内容がこのシリーズの初めの方で考察されたObjects.mqhファイルに位置します。
//+------------------------------------------------------------------+ //| Pointer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "Element.mqh" //+------------------------------------------------------------------+ //| マウスカーソルのポインタを作成するクラス | //+------------------------------------------------------------------+ class CPointer : public CElement { private: //--- 要素作成のためのオブジェクト CBmpLabel m_pointer_bmp; /--- public: CPointer(void); ~CPointer(void); //--- public: //--- 要素の移動 virtual void Moving(const int x,const int y); //--- (1)表示 (2)非表示 (3)リセット (4)削除 virtual void Show(void); virtual void Hide(void); virtual void Reset(void); virtual void Delete(void); }; //+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CPointer::CPointer(void) { } //+------------------------------------------------------------------+ //| デストラクタ | //+------------------------------------------------------------------+ CPointer::~CPointer(void) { }
ライブラリの現バージョンでは、マウスカーソルのポインタには4つのうち1つのタイプを設定することができます。より便利なようにEnums.mqhファイルにENUM_MOUSE_POINTER列挙を追加します( 下のコードを参照)。4つの事前定義された型に加えて、カスタムタイプ(MP_CUSTOM)の選択も可能です。
//+------------------------------------------------------------------+ //| 指標型の列挙 | //+------------------------------------------------------------------+ enum ENUM_MOUSE_POINTER { MP_CUSTOM =0, MP_X_RESIZE =1, MP_Y_RESIZE =2, MP_XY1_RESIZE =3, MP_XY2_RESIZE =4 };
上記の列挙の説明は下記です。
- MP_CUSTOM — カスタムタイプ
- MP_X_RESIZE — 横サイズの変更
- MP_Y_RESIZE — 縦サイズの変更
- MP_XY1_RESIZE — 対角線1上のサイズの変更
- MP_XY2_RESIZE — 対角線2上のサイズの変更
マウスカーソルのポインタを作成する前に、その型を設定するのに十分であり、対応するアイコンが要素のリソースで予め指定されたセットから自動的にグラフィックオブジェクトに設定されます。 これらのアイコンの入ったアーカイブは本稿末尾に添付されており、PCにダウンロードすることができます。
//--- リソース #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_x_resize_blue.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_y_resize_blue.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize_blue.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize_blue.bmp"
ポインタにアイコンを設定したい場合はENUM_MOUSE_POINTER列挙からMP_CUSTOMタイプを選んでCPointer::FileOn() とCPointer::FileOff() メソッドを選んでアイコンへのパスを指定します。
class CPointer : public CElement { private: //--- ポインタのアイコン string m_file_on; string m_file_off; //--- ポインタのタイプ ENUM_MOUSE_POINTER m_type; //--- public: //--- ポインタのラベルを設定する void FileOn(const string file_path) { m_file_on=file_path; } void FileOff(const string file_path) { m_file_off=file_path; } //--- ポインタの型を返す/設定する ENUM_MOUSE_POINTER Type(void) const { return(m_type); } void Type(ENUM_MOUSE_POINTER type) { m_type=type; } };
また、座標を更新してポインタの状態を返す/設定するためのメソッドがここで必要とされます。
class CPointer : public CElement { public: //---ポインタの状態を返す/設定する bool State(void) const { return(m_pointer_bmp.State()); } void State(const bool state) { m_pointer_bmp.State(state); } //--- 座標を更新する void UpdateX(const int x) { m_pointer_bmp.X_Distance(x); } void UpdateY(const int y) { m_pointer_bmp.Y_Distance(y); } };
ポインタのクラスコンストラクタはデフォルトでMP_X_RESIZE型です。
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CPointer::CPointer(void) : m_file_on(""), m_file_off(""), m_type(MP_X_RESIZE) { }
ポインタの要素アイコンを作成前にタイプセットに応じて定義するメソッドが必要です。このためにはCPointer::SetPointerBmp() メソッドを書きます。ユーザタイプ(MP_CUSTOM)が選択されてアイコンへのパスが指定されなかった場合、関連したメッセージがログに表示されます。
class CPointer : public CElement { private: //--- マウスカーソルポインタのアイコンを設定する void SetPointerBmp(void); }; //+------------------------------------------------------------------+ //| ポインタの型に応じてポインタのアイコンを設定する | //+------------------------------------------------------------------+ void CPointer::SetPointerBmp(void) { switch(m_type) { case MP_X_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_x_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_x_resize.bmp"; break; case MP_Y_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_y_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_y_resize.bmp"; break; case MP_XY1_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy1_resize.bmp"; break; case MP_XY2_RESIZE : m_file_on ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize_blue.bmp"; m_file_off ="Images\\EasyAndFastGUI\\Controls\\pointer_xy2_resize.bmp"; break; } //--- カスタムタイプ(MP_CUSTOM)が示された場合 if(m_file_on=="" || m_file_off=="") ::Print(__FUNCTION__," > Both icons should be set for a cursor pointer!"); }
要素の作成には1つのパブリックメソッドCPointer::CreatePointer() が必要で、そのコードは下記にみられます。グラフィックオブジェクトの名前の形成には要素インデックスが一部として使われ、その値はCPointerクラスコンストラクタでゼロデフォルトに設定されます。これは、ポインタが接続される要素内に様々な目的のために複数のCPointer 型のオブジェクトを作成するために必要です。
class CPointer : public CElement { public: //--- ポインタラベルを作成する bool CreatePointer(const long chart_id,const int subwin); }; //+------------------------------------------------------------------+ //| ポインタを作成する | //+------------------------------------------------------------------+ bool CPointer::CreatePointer(const long chart_id,const int subwin) { //--- オブジェクト名の形成 string name=CElement::ProgramName()+"_pointer_bmp_"+(string)CElement::Index()+"__"+(string)CElement::Id(); //--- 指標のアイコンを設定する SetPointerBmp(); //--- オブジェクトを作成する if(!m_pointer_bmp.Create(m_chart_id,name,m_subwin,0,0)) return(false); //--- 優先順位の設定 m_pointer_bmp.BmpFileOn("::"+m_file_on); m_pointer_bmp.BmpFileOff("::"+m_file_off); m_pointer_bmp.Corner(m_corner); m_pointer_bmp.Selectable(false); m_pointer_bmp.Z_Order(0); m_pointer_bmp.Tooltip("\n"); //--- オブジェクトを非表示にする m_pointer_bmp.Timeframes(OBJ_NO_PERIODS); return(true); }
これでツリービューの作成に必要な要素はすべてそろいました。次に作成前にCTreeViewクラスについて学びましょう。
ツリービュ作成に使われるCTreeViewクラスの開発
すべてのライブラリ要素で行われたように、TreeView.mqhファイルで標準的なメソッドを持つCTreeViewクラスを作成してWndContainer.mqhファイルと接続します。
//+------------------------------------------------------------------+ //| WndContainer.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #include "TreeView.mqh"
ツリービューのすべてのコンポーネントをリストしてみましょう。
- ツリービューの背景
- ツリービューの項目リスト
- ツリービューの縦スクロールバー
- コンテンツリストの背景
- コンテンツ項目リスト
- コンテンツリストの横スクロールバー
- マウスカーソルへのポインタは、ツリービュー領域とコンテンツ領域の幅がどのように変化するかを追跡します。
図4 ツリービューのコンポーネント
マウスカーソルへのポインタのアイコンは作成後に非表示になります。それは、マウスカーソルがその上に配置された場合にのみリストの連結領域の境界上の狭い領域に表示されます。
ツリービュー要素の作成には7つのprivateメソッドと1つのpublicメソッドが必要です。
class CTreeView : public CElement { private: //--- 要素作成オブジェクト CRectLabel m_area; CRectLabel m_content_area; CTreeItem m_items[]; CTreeItem m_content_items[]; CScrollV m_scrollv; CScrollV m_content_scrollv; CPointer m_x_resize; //--- public: //--- ツリービュー作成メソッド bool CreateTreeView(const long chart_id,const int subwin,const int x,const int y); //--- private: bool CreateArea(void); bool CreateContentArea(void); bool CreateItems(void); bool CreateScrollV(void); bool CreateContentItems(void); bool CreateContentScrollV(void); bool CreateXResizePointer(void); };
例としてそのうちの1つであってマウスカーソルのポインタを作成するCTreeView::CreateXResizePointer() メソッドのコードを表示します(コードは以下を参照)。次の詳細には注意が払われる必要があります。
- 以下のいずれかの条件が満たされた場合にはカーソルへのポインタが作成されません。
- リストの幅を変更できるモードが無効になっている。
- タブ項目のモードが有効になっている。
- ポインタのオフセットはここで(この要素タイプは他の要素のようにウィンドウに接続されていません)はマウスのシステムカーソルから算出されます。
- 要素でいくつかのポインタが必要とされる場合には、それらの各々に独自の要素インデックスが設定されている必要があります。現在のツリービューバージョンで1つのポインタのみが使用されており、このプロパティはポインタコンストラクタでデフォルトで0 に初期化されているのでここでは抜かすことができます。
- ポインタが接続されている要素の識別子を示すことが必要です。これは、カーソルポインタを使用することができる様々な要素からのグラフィックオブジェクトの名前の衝突を回避するために必要とされます。
//+------------------------------------------------------------------+ //| 幅が可変のカーソルのポインタを作成する | //+------------------------------------------------------------------+ bool CTreeView::CreateXResizePointer(void) { //--- コンテンツ領域の幅を変える必要がない場合や // タブ項目のモードが有効になっている場合には終了する if(!m_resize_content_area_mode || m_tab_items_mode) return(true); //--- 優先順位の設定 m_x_resize.XGap(12); m_x_resize.YGap(9); m_x_resize.Id(CElement::Id()); m_x_resize.Type(MP_X_RESIZE); //--- 要素を作成する if(!m_x_resize.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
CTreeViewクラスの他のメソッドを説明する前に、ツリービュー要素に必要な機能を見てみましょう。この要素を作成するためのクラスが実装されるので、最終的には、様々な目的のために使用される階層リストを作ることができます。
例えば、将来的にファイルナビゲーターを作成するためのクラスを開発する場合、いくつかの機能を心に留めておく必要があります。ウィンドウズエクスプローラでファイルナビゲータがどのように配置されているかにご注目ください。ツリービューがウィンドウの左側( Windows 7では「ナビゲーションペイン」と呼ばれています)に表示されている場合、ファイルではなくてフォルダのみが表示されます。すべてのファイルは、ウィンドウズエクスプローラのコンテンツ領域に表示されます(下のスクリーンショットを参照)。
図5。Windows 7でのファイルナビゲータ。左にはツリービュー、右にはコンテンツ領域
しかしながら、ツリービューでは、フォルダ以外のファイルを表示することがしばしば必要です。例えば、ツリービューでファイルを選択するときに、そのコンテンツが右側の領域に表示されるよう手配することができます。CTreeView型の要素クラスがファイルナビゲータの作成に使用されている場合、ライブラリのユーザは、ツリービューで(1)フォルダとファイルの表示 または(2)フォルダのみの表示 の2つのモードから選択できるべきです。したがってENUM_FILE_NAVIGATOR_MODE列挙がEnums.mqhに追加されます(下記のコードを参照)。
//+------------------------------------------------------------------+ //| ナビゲータモードの列挙 | //+------------------------------------------------------------------+ enum ENUM_FILE_NAVIGATOR_MODE { FN_ALL =0, FN_ONLY_FOLDERS =1 };
コンテンツ領域での項目のコンテンツの表示は常に必要ではないので、無効化にするオプションが必要です。例えば、これは、ツリービューが、グラフィカルインタフェースVII: タブコントロール(チャプター2) 稿で示されたようにライブラリコントロール要素のグループがリンクされるタブとして使われる場合に有用です。
さらに、ツリービューの要素のより正確な設定するための追加のモードも提供されます。以下は、現在のバージョンのモードの完全な一覧です。
- ファイルナビゲータモード
- カーソルがホバーされた場合の強調表示モード
- コンテンツ領域で項目の内容を表示するモード
- コンテンツ領域の幅を変更するモード
- タブ項目のモード
要素モードを設定するには、作成前に下記のコードで提供されたメソッドを適用する必要があります。
class CTreeView : public CElement { private: //--- ファイルナビゲータモード ENUM_FILE_NAVIGATOR_MODE m_file_navigator_mode; //--- カーソルでホバーされた場合の強調表示モード bool m_lights_hover; //--- コンテンツ領域で項目の内容を表示するモード bool m_show_item_content; //--- リストの幅を変更するモード bool m_resize_list_area_mode; //--- タブ項目のモード bool m_tab_items_mode; //--- public: //--- (1) ファイルナビゲータモード (2) マウスカーソルでホバーされた場合の強調表示モード // (3) 項目コンテンツを表示するモード (4) リストの幅を変更するモード (5) タブ項目モード void NavigatorMode(const ENUM_FILE_NAVIGATOR_MODE mode) { m_file_navigator_mode=mode; } void LightsHover(const bool state) { m_lights_hover=state; } void ShowItemContent(const bool state) { m_show_item_content=state; } void ResizeListAreaMode(const bool state) { m_resize_list_area_mode=state; } void TabItemsMode(const bool state) { m_tab_items_mode=state; } };
以下のリストには、要素の外観を設定するためのプロパティが含まれています。
- ツリービュー領域の幅
- 背景色
- 背景境界の色
- コンテンツ領域の幅
- リストでの項目の高さ
- さまざまな状態の項目の背景色
- 異なる状態での項目テキストの色
- 項目のコンテンツ存在の表示としてのアイコン(アイコンはデフォルトで右矢印に設定)
class CTreeView : public CElement { private: //--- ツリービュー領域の幅 int m_treeview_area_width; //--- 背景の色と境界 color m_area_color; color m_area_border_color; //--- コンテンツ領域の幅 int m_content_area_width; //--- 項目の高さ int m_item_y_size; //--- 異なる状態での項目の色 color m_item_back_color_hover; color m_item_back_color_selected; //--- 異なる状態でのテキストの色 color m_item_text_color; color m_item_text_color_hover; color m_item_text_color_selected; //--- 矢印のアイコン string m_item_arrow_file_on; string m_item_arrow_file_off; string m_item_arrow_selected_file_on; string m_item_arrow_selected_file_off; //--- public: //--- (1) 項目の高さ (2) ツリービューの幅 (3) コンテンツリスト void ItemYSize(const int y_size) { m_item_y_size=y_size; } void TreeViewAreaWidth(const int x_size) { m_treeview_area_width=x_size; } void ContentAreaWidth(const int x_size) { m_content_area_width=x_size; } //--- 要素背景の境界の色と背景色 void AreaBackColor(const color clr) { m_area_color=clr; } void AreaBorderColor(const color clr) { m_area_border_color=clr; } //--- 異なる状態での項目の色 void ItemBackColorHover(const color clr) { m_item_back_color_hover=clr; } void ItemBackColorSelected(const color clr) { m_item_back_color_selected=clr; } //--- 異なる状態でのテキストの色 void ItemTextColor(const color clr) { m_item_text_color=clr; } void ItemTextColorHover(const color clr) { m_item_text_color_hover=clr; } void ItemTextColorSelected(const color clr) { m_item_text_color_selected=clr; } //--- 項目矢印のアイコン void ItemArrowFileOn(const string file_path) { m_item_arrow_file_on=file_path; } void ItemArrowFileOff(const string file_path) { m_item_arrow_file_off=file_path; } void ItemArrowSelectedFileOn(const string file_path) { m_item_arrow_selected_file_on=file_path; } void ItemArrowSelectedFileOff(const string file_path) { m_item_arrow_selected_file_off=file_path; } };
ツリービューで強調表示された項目のインデックスとコンテンツ領域のリストが格納されるフィールドも必要です。CTreeView::SelectedItemIndex()メソッドでツリービューを作成する前に、作成直後に強調表示されるべき項目を選ぶことができます。
class CTreeView : public CElement { private: //--- リストで選択された項目のインデックス int m_selected_item_index; int m_selected_content_item_index; //--- public: //--- (1) 項目をインデックスで選び (2) 選択された項目のインデックスを返す void SelectedItemIndex(const int index) { m_selected_item_index=index; } int SelectedItemIndex(void) const { return(m_selected_item_index); } };
要素リスト形成パラメータ
CTreeView型の要素の作成前にはツリービューの作成が必要です。リストに特定のパラメータを持つ項目を追加するためのメソッドが必要になります。これらは、ツリービューを使用する過程で項目を配置する順序においてプログラムを支援するために使用されます。階層的シーケンスはいくつかのパラメータを指定せずに達成することができません。これらのパラメータの完全な一覧は以下の通りです。
- リストでの総合インデックス
すべてのツリービュー項目はループで順に設定され、現在の反復インデックスがすべての項目に設定されます。すべての項目のリスト全体でのインデックスは、現在ユーザの目の前にあるツリービューがどんなに展開されているかにはかかわらず、要素を使用している間最後まで初期のままです。
例としてA、B、C、D、E、Fのリスト全体でのインデックスをはそれぞれ0、1、 2、3、4 and 5 です。例えば、ポイントBとCが項目Aに含まれ、項目Eが項目 Dに含まれるとします。例示のために、2つのツリービュー状態オプションが以下に表示されます。画像の左側(1) は、その索引付けの途切れない順序が示されている完全に展開されたリストです。画像の右側 (2) は項目B、C、Eを含む項目AとDのリストが折りたたまれ、それらのツリービュー配列の形成及び初期化の時点で割り当てられた以前のインデックスが保存されたことを示します。
図6。左 1) – 完全に展開されたリストRight 2) – 折りたたまれたリスト
- 以前のノードのリストの総合インデックス
例えば、ルートカタログ内の項目には前のノードがなく-1パラメータを持つことになります。項目がコンテンツ(アイテムのローカルリスト)を持つ場合は、そのすべての要素には、配置されているノードリストの総合インデックスが割り当てられます。
この図ではルートカタログの項目A、D、Hに-1値が割り当てられていることを示します。ポイントBとCには1つ前のノードAの総合インデックス [0] が割り当てられ、ポイントE、FGには1つ前のノードDの総合インデックス [3] が割り当てられています。
図7 ノードのすべての子要素は、割り当てられたリストの総合インデックスを持っています。
- 項目の説明(項目に表示されるテキスト)
例えば、それは(ファイルナビゲーターが作成されている場合)フォルダやファイルの名前、カテゴリとサブカテゴリの名前、(「タブ」要素が作成されている場合)要素グループの名前などであり得ます。
- ノードのレベル数
カウントはゼロから始まります。つまりルートカタログのポイントは0値を持ちます。さらに、ネスティングレベルが増加されると、ネスティング項目の値は1単位だけ増加されます。より良い理解のために以下の画像をご参照ください。上記された水平スケール上の項目欄は、それらのレベルの数が割り当てられています。ノードA、D、Jのレベルは0です。項目B、C、E、I、Kの番号は1を持ち、項目 F、G、Hの番号は2です。
図8 ノード番号は、そのネストのレベルにリンクされています。
- ローカルインデックス
項目にコンテンツ(要素のローカルリスト)がある場合、このリストはゼロから始まるカスタムIDを持つことになります。ローカルリストのインデックスは、以下のスキーム上の青い図に示されています。
図9 ローカルリストの索引付け
- 1つ前のノードのローカルインデックス
前のノードの一般的な指標と同じ原理がここで適用されます。ルートカタログの項目には1つ前のノードがないため、このパラメータの値は -1に等しいです。1つ前のノードを持っている項目にはそのローカル索引が割り当てられています。1つ前のノードのローカルインデックスは、以下の画像で赤くマークされています。
図10 1つ前のノードのローカルインデックス
- 項目数(ノードのローカルリストの配列サイズ)
CTreeItemクラスのフィールドで項目に割り当てられた型( ENUM_TYPE_TREE_ITEM列挙) はこの値に依存します。下記のスキームは、すべてのローカルリスト内で数値形式で提供された項目数を持っています。項目A、D、E、Jにはコンテンツ(それぞれ2、2、3、1)があり、ポイントB、C、F、G、H、I、Kは空 (0)です。
図11 ノードのローカルリストの要素数
- 項目の状態
ローカルリスト(コンテンツ)の場合には、展開/折りたたみされています。これは、ツリービューが作成された直後に展開すべき項目を示すことができます。
例えば、ツリービュー内の項目を定義するためのすべての主要パラメータは、単一の要約表(下図の例を参照)で示すことができます。簡単な解釈のために、すべてのパラメータの値が異なる色でマークされています。
図12 ツリービュー内の項目を定義するキー(重要な)パラメータの要約テーブル
ファイルナビゲーターを作成するためのクラスを開発する場合、これらのすべてのパラメータは、ターミナルの総合およびローカルファイルディレクトリの階層構造を読み取ることによって、自動的に計算されなければなりません。これは、この第八部の次の章で詳細にお話しますが、ファイルナビゲーターを作成するためのツリービュークラス(CTreeView)の適応方法を想像するためには、そのいくつかの側面をすでにカバーすることができます。例えば、ノードのローカルリスト内の項目数に加えて、ファイルナビゲータが作成された場合はフォルダである項目の数も示されなければなりません。また、すべての項目にはそれがフォルダであるかどうかを定義するパラメータが必要です。ファイルナビゲータでは、ローカルリストを持たない項目はファイルであると確定しているわけではありません。
単純なツリービューを作成する場合には、その構造は独立して形成される必要があります。どちらの場合でも、同じCTreeView::AddItem() メソッドが使われます。これは、引数として送信される必要がある指定されたパラメータを使ってツリービューリストに項目を追加することができます。以下のコードは、このメソッドのコードとすべての項目のパラメータが格納されている配列のリストを示しています。各項目で独自のアイコンが設定できることにご注意ください。
class CTreeView : public CElement { private: //--- ツリービュー項目の配列(共通リスト) int m_t_list_index[]; int m_t_prev_node_list_index[]; string m_t_item_text[]; string m_t_path_bmp[]; int m_t_item_index[]; int m_t_node_level[]; int m_t_prev_node_item_index[]; int m_t_items_total[]; int m_t_folders_total[]; bool m_t_item_state[]; bool m_t_is_folder[]; //--- public: //--- ツリービューに項目を追加する void AddItem(const int list_index,const int list_id,const string item_name,const string path_bmp,const int item_index, const int node_number,const int item_number,const int items_total,const int folders_total,const bool item_state,const bool is_folder=true); }; //+------------------------------------------------------------------+ //| ツリービューの共通配列に項目を追加する | //+------------------------------------------------------------------+ void CTreeView::AddItem(const int list_index,const int prev_node_list_index,const string item_text,const string path_bmp,const int item_index, const int node_level,const int prev_node_item_index,const int items_total,const int folders_total,const bool item_state,const bool is_folder) { //--- 配列のサイズを1要素で増加する int array_size =::ArraySize(m_items); m_items_total =array_size+1; ::ArrayResize(m_items,m_items_total); ::ArrayResize(m_t_list_index,m_items_total); ::ArrayResize(m_t_prev_node_list_index,m_items_total); ::ArrayResize(m_t_item_text,m_items_total); ::ArrayResize(m_t_path_bmp,m_items_total); ::ArrayResize(m_t_item_index,m_items_total); ::ArrayResize(m_t_node_level,m_items_total); ::ArrayResize(m_t_prev_node_item_index,m_items_total); ::ArrayResize(m_t_items_total,m_items_total); ::ArrayResize(m_t_folders_total,m_items_total); ::ArrayResize(m_t_item_state,m_items_total); ::ArrayResize(m_t_is_folder,m_items_total); //--- 送信されたパラメータの値を格納する m_t_list_index[array_size] =list_index; m_t_prev_node_list_index[array_size] =prev_node_list_index; m_t_item_text[array_size] =item_text; m_t_path_bmp[array_size] =path_bmp; m_t_item_index[array_size] =item_index; m_t_node_level[array_size] =node_level; m_t_prev_node_item_index[array_size] =prev_node_item_index; m_t_items_total[array_size] =items_total; m_t_folders_total[array_size] =folders_total; m_t_item_state[array_size] =item_state; m_t_is_folder[array_size] =is_folder; }
要素の作成前には、そのパラメータを設定し、ツリービュー領域とコンテンツ領域の初期の幅を指定する機会があります。デフォルトでは、コンテンツ領域の幅が設定されるクラスフィールドの値はWRONG_VALUEで初期化されています(以下のコードを参照)。これは、コンテンツ領域の幅を設定することができない場合には(1)コンテンツ領域の背景(2)この領域の項目の配列(3)この領域のリストのスクロールバー の3つの要素コンポーネントが作成されないことを意味します。これはパラメータでツリービューで強調表示された項目のコンテンツを表示するモードが設定された場合にも起こりません。したがって、背景を維持することによって表示されるリストを無効にすることができますが、その反対はできません。
//+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CTreeView::CTreeView(void) : m_treeview_area_width(180), m_content_area_width(WRONG_VALUE), m_item_y_size(20), m_visible_items_total(13), m_tab_items_mode(false), m_lights_hover(false), m_show_item_content(true) { //--- 要素クラスの名前を基本クラスに格納する CElement::ClassName(CLASS_NAME); //--- マウスの左クリックの優先順位を設定する m_zorder=0; }
コンテンツ領域内の項目のリストとしては別の動的配列のリストが必要とされます。階層順に従うする必要がないので、それはツリービューの場合ほど大きくはありません。特定のリストの作成には3つのパラメータのみが必要となります。
- コンテンツ領域リスト総合インデックス
- ツリービューの総合インデックス
- 項目の説明(表示されるテキスト)
class CTreeView : public CElement { private: //--- ツリービューで選択された項目を含むリストの配列(完全なリスト) int m_c_list_index[]; int m_c_tree_list_index[]; string m_c_item_text[]; };
これらの配列のサイズおよび初期化の設定は、コンテンツリストを作成する CTreeView::CreateContentItems()メソッドで行われます。ルートカタログではツリービューで選択できるその上のノードが存在しないため、すべての項目は ルートカタログに属しているもの以外は配列に追加されます。ここでは、メソッドの初めに設定されたモードの確認 があり、コンテンツ領域の作成はこれに依存します。
//+------------------------------------------------------------------+ //| 選択された項目のコンテンツリストを作成する | //+------------------------------------------------------------------+ bool CTreeView::CreateContentItems(void) { //--- 項目のコンテンツが表示されないでいい場合か // コンテンツ領域が表示されている場合は終了する if(!m_show_item_content || m_content_area_width<0) return(true); //--- 座標と幅 int x =m_content_area.X()+1; int y =CElement::Y()+1; int w =m_content_area.X2()-x-1; //--- 項目カウンタ int c=0; //--- int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- ルートカタログの項目はこのリストに存在できない // よって、ノードレベルが1未満の場合は、次に移る if(m_t_node_level[i]<1) continue; //--- 配列のサイズを1要素で増加する int new_size=c+1; ::ArrayResize(m_content_items,new_size); ::ArrayResize(m_c_item_text,new_size); ::ArrayResize(m_c_tree_list_index,new_size); ::ArrayResize(m_c_list_index,new_size); //--- Y座標の計算 y=(c>0)?y+m_item_y_size-1 : y; //--- パネルオブジェクトを受け渡す m_content_items[c].WindowPointer(m_wnd); //--- 作成前にプロパティを設定する m_content_items[c].Index(1); m_content_items[c].Id(CElement::Id()); m_content_items[c].XSize(w); m_content_items[c].YSize(m_item_y_size); m_content_items[c].IconFile(m_t_path_bmp[i]); m_content_items[c].ItemBackColor(m_area_color); m_content_items[c].ItemBackColorHover(m_item_back_color_hover); m_content_items[c].ItemBackColorSelected(m_item_back_color_selected); m_content_items[c].ItemTextColor(m_item_text_color); m_content_items[c].ItemTextColorHover(m_item_text_color_hover); m_content_items[c].ItemTextColorSelected(m_item_text_color_selected); //--- 座標 m_content_items[c].X(x); m_content_items[c].Y(y); //--- パネルの端点からのオフセット m_content_items[c].XGap(x-m_wnd.X()); m_content_items[c].YGap(y-m_wnd.Y()); //--- オブジェクトを作成する if(!m_content_items[c].CreateTreeItem(m_chart_id,m_subwin,x,y,TI_SIMPLE,c,0,m_t_item_text[i],false)) return(false); //--- 要素を非表示にする m_content_items[c].Hide(); //--- 項目はドロップダウン要素になる m_content_items[c].IsDropdown(true); //--- (1) コンテンツの総合リストインデックス (2) ツリービューインデックス (3) 項目テキストを格納する m_c_list_index[c] =c; m_c_tree_list_index[c] =m_t_list_index[i]; m_c_item_text[c] =m_t_item_text[i]; //--- c++; } //--- リストのサイズを格納する m_content_items_total=::ArraySize(m_content_items); return(true); }
以前に考慮したツリービューの動的配列とコンテンツ領域のリストは完全なリストの格納のためです。しかし、これらのリストのすべての項目が同時に表示されるわけではないので、ツリービューとの相互作用の間に再形成される後2つの動的配列のグループも必要になります。折りたたむまたは展開することができるローカルリストを持つノードの現在の状態が考慮されます。別の具体的な例を見てみましょう。
例えば11項目を持つツリービューがあるとします。
図13 11項目を持つツリービューのモデル
項目A、D、Jはルートカタログリストにあるので、コンテンツ領域では完全な項目リストを持たないことになります。下の図の右側 (1) はツリービュー項目すべてのリストを示します。上記のリストのいずれかで考慮されたこのリストのパラメータを格納するための動的配列の名前は「t」(「tree」の頭文字) で始まります。図の左側(2)はコンテンツ領域の項目のリストを表示します。このリストのパラメータを格納する動的配列の名前は「c」(contentの頭文字)から始まります。
図14 両グループの完全なリスト
例のローカルリストは項目A、D、E、Jにあります。これらは下の図で青でマークされています。
図15 ローカルリストを持つ項目(青でマーク)
コンテンツを持っているツリービューで項目を選択するたびに、コンテンツ領域のリストが再形成されることになります。例えば、Aが選択されると、コンテンツ領域で項目BとCのリストが形成されます(図でのオプション1)。Dが選択されるとEとIの項目リストが形成されます(図でのオプション2)。
図16 コンテンツ領域のリストを形成する例
ツリービューで表示される項目のリストの再構成はノードのローカルリストを折りたたむ/展開する時点で行われます。それはすべて、シンプルでかなり明白です。折りたたまれたリストの項目は表示から除外され、ツリービューの表示された項目を格納するための配列には含まれません。
上記のすべてのものでは、ツリービューの総合インデックスを格納するツリービューで表示される項目のリストのための1つの配列と、(1)コンテンツリストの総合インデックス(2)ツリー型のリストの総合インデックス(3)項目の説明(表示されたテキスト)を格納するコンテンツリストのための3つの配列が必要になります。
class CTreeView : public CElement { private: //--- ツリービューで表示された項目のリストの配列 int m_td_list_index[]; //--- コンテンツリストで表示された項目のリストの配列 int m_cd_list_index[]; int m_cd_tree_list_index[]; string m_cd_item_text[]; };
続いて、上記のアルゴリズムによってリストを形成して管理するために使用するメソッドを考察していきます。
要素リスト管理メソッド
すべてのパラメータがCTreeView クラスに送られて要素が作成された後は、表示される項目のリストが形成されて更新される必要があります。メインメソッドのコードを減らすために、作業に関連した追加的なプライベートフィールドとメソッドを作成します。ノードの最小/大レベルの値とツリービューのルートカタログの項目の数が必要になります。
class CTreeView : public CElement { private: //--- ノードの(1) 最小と (2) 最大レベル int m_min_node_level; int m_max_node_level; //--- ルートカタログの項目数 int m_root_items_total; //--- private: //--- (1) ノードの境界と (2) ルートカタログのサイズを決定して格納する void SetNodeLevelBoundaries(void); void SetRootItemsTotal(void); }; //+------------------------------------------------------------------+ //| ノードの境界を定義して設定する | //+------------------------------------------------------------------+ void CTreeView::SetNodeLevelBoundaries(void) { //--- ノードの最小値と最大値を定義する m_min_node_level =m_t_node_level[::ArrayMinimum(m_t_node_level)]; m_max_node_level =m_t_node_level[::ArrayMaximum(m_t_node_level)]; } //+------------------------------------------------------------------+ //| ルートカタログのサイズを定義して設定する | //+------------------------------------------------------------------+ void CTreeView::SetRootItemsTotal(void) { //--- ルートカタログの項目数を定義する int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- 最小レベルの場合はカウンタを増加する if(m_t_node_level[i]==m_min_node_level) m_root_items_total++; } }
以前には表示された項目のリストを形成するための配列が考察されました。ツリービューの配列に書き込みをするCTreeView::AddDisplayedTreeItem() メソッドが必要になります。このリストに項目を追加するためには、リストの総合インデックスがこのメソッドに送信される必要があります。
class CTreeView : public CElement { private: //--- コンテンツ領域でリストに項目を追加する void AddDisplayedTreeItem(const int list_index); }; //+------------------------------------------------------------------+ //| ツリービューで表示された項目の配列に | //| 項目を追加する | //+------------------------------------------------------------------+ void CTreeView::AddDisplayedTreeItem(const int list_index) { //--- 配列のサイズを1要素で増加する int array_size=::ArraySize(m_td_list_index); ::ArrayResize(m_td_list_index,array_size+1); //--- 送信されたパラメータの値を格納する m_td_list_index[array_size]=list_index; }
配列が形成された後、項目は交換され、要素は最新の変更を表示するために再描画される必要があります。このために後1つの補助的なCTreeView::RedrawTreeList()メソッドを作成します。メソッドの初めでは、要素は隠されています。その後、1番目のポイントの(1) Y座標が計算され (2) スクロールバーのサイズが修正され (3) 項目の幅がスクロールバーの存在/不在を考慮して計算されます。そしてY座標が計算され、ループ内のすべての項目のプロパティが反復して更新されます。 要素はループの終了後に再び表示されます。
class CTreeView : public CElement { private: //--- ツリービューの再描画 void RedrawTreeList(void); }; //+------------------------------------------------------------------+ //| 要素の再描画 | //+------------------------------------------------------------------+ void CTreeView::RedrawTreeList(void) { //--- 要素を非表示にする Hide(); //--- ツリービューの最初の項目のY座標 int y=CElement::Y()+1; //--- 項目数を取得する m_items_total=::ArraySize(m_td_list_index); //--- スクロールサイズを修正する m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total); //--- ツリービュー項目の幅の計算 int w=(m_items_total>m_visible_items_total) ?CElement::XSize()-m_scrollv.ScrollWidth() : CElement::XSize()-2; //--- 新しい値を設定する for(int i=0; i<m_items_total; i++) { //--- 各項目のY座標を計算する y=(i>0)?y+m_item_y_size-1 : y; //--- リスト項目の総合インデックスを取得する int li=m_td_list_index[i]; //--- 座標とサイズを更新する m_items[li].UpdateY(y); m_items[li].UpdateWidth(w); } //--- 要素を表示する Show(); }
上記のフィールドやメソッドはすべて CTreeView::UpdateTreeViewList() で呼び出され、ここではツリービューが形成されて更新されます(コードは下記を参照)。 このメソッドを詳しく見ていきましょう。
ここでは4つのローカル動的配列が必要とされ、リストを形成する過程で項目の順序を制御するために使用されます。以下の配列が含まれます。
- 前のノードのリストの総合インデックス
- 項目のローカルインデックス
- ノードの項目数
- ノードのフォルダ数
配列の初期サイズは、ツリービューのノードの最大数よりも2要素多くなります。これは(ループ内で)現在の項目の値が配列の次の要素に記憶されるのために必要とされ、前の項目の値は、現在のループインデックスによって次の反復でチェックされます。配列は-1で初期化されます。プログラムがこのメソッドを開始するごとに、ツリービューの作成に必要なm_td_list_index[]配列バッファが解放されて配列にはゼロサイズが割り当てられることにご注意ください。追加的な項目カウンタ(ii)およびルートカタログに最後の項目のフラグを設定するための変数(end_list)が必要になります。
CTreeView::UpdateTreeViewList() メソッドのメインループは、すべてのローカル配列や変数が宣言された後にその動作を開始します。動作は次の条件が満たされるまで続きます。
- ノード数カウンタ(nl)が事前定義された最大値を超える
- (挿入されたすべての項目が確認された後に)ルートカタログの最後の項目に達していない
- ユーザがプログラムを削除しなかった
これは二重のループです。ツリービューのノードは1番目のレベルで計算されています。現在のノードのローカルリストのすべての項目は、2番目のレベルで確認されます。2番目のループの本体の始めに、ツリービューがファイルナビゲーションとして使用されているかどうかをチェックするためにファイルナビゲーションモードがチェックされます。「ツリービューでフォルダのみを表示する」モードが有効になっている場合は、現在の項目がフォルダかどうかを確認する必要があり、違った場合には次の項目に進みます。
これらの条件が満たされている場合は、さらに3つのチェックが続きます。プログラムは下記の3例では、次の項目に進みます。
- ノードが一致しない
- 項目のローカルインデックスの順序が従われていない
- 現在ループカタログ内でなく、および、1つ前のノードのリストの総合インデックスがメモリに格納されているものと一致しない
上記のチェックがすべて通過された後、次の項目がローカルリストのサイズを超えた場合、項目のローカル索引を記憶します。
さらに、項目がローカルリストを含んでおりそれが現在展開されている場合はツリービューに表示される項目の配列に項目を追加します。ここでは、メソッドのローカル配列に現在の項目の値を記憶する必要があります。ここでの唯一の例外は、項目のローカルインデックスです。それは現在のノードインデックス(nl)、残りのパラメータ値は次のノードインデックス(n)に保存されるべきです。また、ローカルインデックス項目のカウンタはここでゼロにされ、次のノードに進むための現在の(2番目の)ループはここで停止します。
次のノードへの移動が失敗した場合、それがリストを持たない項目である、またはリストが現在折りたたまれていることを意味します。この場合ポイントは初めにツリービューに表示される項目の配列に追加されます 。そして、項目のローカルインデックスのカウンタが増加されます。
ここで、現在位置がルートカタログ内ですでにリストの最後の項目に達している場合、リストが正常に形成されていることが意味されます。フラグが設定されてループが停止します。ルートカタログの最後の項目に達していない場合は、設定されているモードに応じて、現在のノードの項目数またはフォルダ数を取得します。これがリストの最後の項目でない場合には次に進みます。最後の項目が達せられた場合、1つ前のノードに進み、リストで最後にチェックされた項目からループを継続する必要があります。ルートカタログの最後の項目に到達するまで進みます。
ツリービューの作成後には、メソッドの最終部で要素が再描画されます。
class CTreeView : public CElement { private: //--- ツリービューを更新する void UpdateTreeViewList(void); }; //+------------------------------------------------------------------+ //| ツリービューリストを更新する | //+------------------------------------------------------------------+ void CTreeView::UpdateTreeViewList(void) { //--- 項目の順序を制御する配列 int l_prev_node_list_index[]; // 以前のノードのリストの総合インデックス int l_item_index[]; // 項目のローカルインデックス int l_items_total[]; // ノードの項目数 int l_folders_total[]; // ノードのフォルダ数 //--- 配列の初期サイズを設定する int begin_size=m_max_node_level+2; ::ArrayResize(l_prev_node_list_index,begin_size); ::ArrayResize(l_item_index,begin_size); ::ArrayResize(l_items_total,begin_size); ::ArrayResize(l_folders_total,begin_size); //--- 配列の初期化 ::ArrayInitialize(l_prev_node_list_index,-1); ::ArrayInitialize(l_item_index,-1); ::ArrayInitialize(l_items_total,-1); ::ArrayInitialize(l_folders_total,-1); //--- 表示されたツリービュー項目の空の配列 ::ArrayFree(m_td_list_index); //--- 項目のローカルインデックスのカウンタ int ii=0; //--- ルートカタログの最後の項目のフラグを設定 bool end_list=false; //--- 表示された項目を配列に集めるループは次の条件が満たされるまで続きます。 // 1:カウンタが事前定義された最大値を超えない // 2:(すべての入力項目が確認された後)最後の項目が到達される // 3:ユーザがプログラムを削除する int items_total=::ArraySize(m_items); for(int nl=m_min_node_level; nl<=m_max_node_level && !end_list; nl++) { for(int i=0; i<items_total && !::IsStopped(); i++) { //--- 「フォルダのみの表示」モードが有効な場合 if(m_file_navigator_mode==FN_ONLY_FOLDERS) { //--- ファイルの場合次の項目に進む if(!m_t_is_folder[i]) continue; } //--- (1) 私たちののーどではなく (2) 項目のローカルインデックスの順番が従われていない場合 // 次の項目に進む if(nl!=m_t_node_level[i] || m_t_item_index[i]<=l_item_index[nl]) continue; //--- ルートカタログ外で1つ前のノードの総合インデックスがメモリに格納されたものと // 一致しない場合には次の項目に進む if(nl>m_min_node_level && m_t_prev_node_list_index[i]!=l_prev_node_list_index[nl]) continue; //--- 次の項目がローカルリストのサイズを超えた場合は項目のローカルインデックスを記憶する if(m_t_item_index[i]+1>=l_items_total[nl]) ii=m_t_item_index[i]; //---現在の項目のリストが最大限の場合 if(m_t_item_state[i]) { //--- ツリービューで表示された項目の配列に項目を追加する AddDisplayedTreeItem(i); //--- 現在値を記憶して覚えて次のノードに進む int n=nl+1; l_prev_node_list_index[n] =m_t_list_index[i]; l_item_index[nl] =m_t_item_index[i]; l_items_total[n] =m_t_items_total[i]; l_folders_total[n] =m_t_folders_total[i]; //--- 項目のローカルインデックスのカウンタをゼロにする ii=0; //--- 次のノードに進む break; } //--- ツリービューで表示された項目の配列に項目を追加する AddDisplayedTreeItem(i); //--- 項目のローカルインデックスの数を増やす ii++; //--- ルートカタログの最終項目が達せられた場合 if(nl==m_min_node_level && ii>=m_root_items_total) { //--- フラグを設定してループを停止する end_list=true; break; } //--- ルートカタログの最終項目が達せられていない場合 else if(nl>m_min_node_level) { //--- 現在のノードの項目数を取得する int total=(m_file_navigator_mode==FN_ONLY_FOLDERS)?l_folders_total[nl]: l_items_total[nl]; //--- 項目の最後のローカルインデックスでない場合は次へ進む if(ii<total) continue; //--- 最後のローカルインデックスが達せられた場合は // 前のノードに戻って停止した項目から続行する必要がある while(true) { //--- 以下に示す配列で現在のノードの値をリセットする l_prev_node_list_index[nl] =-1; l_item_index[nl] =-1; l_items_total[nl] =-1; //--- ローカルリスト内のアイテム数の平等を維持したままノードの数を減少させる // またはルートカタログに達していない if(l_item_index[nl-1]+1>=l_items_total[nl-1]) { if(nl-1==m_min_node_level) break; //--- nl--; continue; } //--- break; } //--- 1つ前のノードに進む nl=nl-2; //--- 項目のローカルインデックスのカウンタをゼロにして次のノードに進む ii=0; break; } } } //--- 要素の再描画 RedrawTreeList(); }
スクロールバーに対するリストのシフトはCTreeView::ShiftTreeList()とCTreeView::ShiftContentList()メソッドを介して実行されます。これらのメソッドの主な論理は実質的に同じなのでここでは例としてそのうちの1つ(ツリービューのためのもの)のコードを提供します。
メソッドの開始時にはすべてのツリービュー項目が非表示になっています。その後、現在のリストの配列内でスクロールバーを考慮した項目の幅が計算されます。スクロールバーが存在する場合には、スライダーの位置が決定されます。そして、すべての項目のためのループで座標と幅が計算されて更新され、項目が表示されます。メソッドの最後には、スクロールバーが表示される場合には、リストの上に配置されるように再描画する必要があります。
class CTreeView : public CElement { private: //--- リストのシフト void ShiftTreeList(void); void ShiftContentList(void); }; //+------------------------------------------------------------------+ //| ツリービューをスクロールバーに対してシフトする | //+------------------------------------------------------------------+ void CTreeView::ShiftTreeList(void) { //--- ツリービューのすべての項目を非表示にする int items_total=ItemsTotal(); for(int i=0; i<items_total; i++) m_items[i].Hide(); //--- スクロールバーが必要な場合 bool is_scroll=m_items_total>m_visible_items_total; //--- リスト項目の幅の計算 int w=(is_scroll)?m_area.XSize()-m_scrollv.ScrollWidth()-1 : m_area.XSize()-2; //--- スクロールバーの位置の定義 int v=(is_scroll)?m_scrollv.CurrentPos() : 0; m_scrollv.CurrentPos(v); //--- ツリービューの最初の項目のY座標 int y=CElement::Y()+1; //--- for(int r=0; r<m_visible_items_total; r++) { //--- 範囲超過を防ぐための確認 if(v>=0 && v<m_items_total) { //--- Y座標を計算する y=(r>0)?y+m_item_y_size-1 : y; //--- ツリービュー項目の総合インデックスを取得する int li=m_td_list_index[v]; //--- 座標と幅を設定する m_items[li].UpdateX(m_area.X()+1); m_items[li].UpdateY(y); m_items[li].UpdateWidth(w); //--- 項目を表示する m_items[li].Show(); v++; } } //--- スクロールバーを再描画する if(is_scroll) m_scrollv.Reset(); }
ここでは普通のリストが形成され階層順序に従うことを必要としないため、コンテンツリストの形成と更新はかなり容易です。CTreeView::UpdateContentList() メソッドの冒頭で、コンテンツリスト形成を補助する配列が解放されます。その後、最初のループでツリービューのすべてのアイテムを反復処理して、下記ののパラメータがツリービューで選択した項目と一致している項目のみを保存します。
- ノードレベル
- 項目のローカルインデックス
- 項目の総合インデックス
子のループは項目の記述(表示されたテキスト)とツリービューの総合インデックスのみを格納します。
2番目のループでは、コンテンツリストを反復処理して このリストの総合インデックスの配列に書き入れます。必要な項目を決定するために1番目のループで取得されたパラメータが使われます。メソッドの最後には、スクロールバーのサイズが補正され、最新の変更を表示するためにリストが更新されます。
class CTreeView : public CElement { private: //--- コンテンツリストをレフレッシュする void UpdateContentList(void); }; //+------------------------------------------------------------------+ //| コンテンツリストを更新する | //+------------------------------------------------------------------+ void CTreeView::UpdateContentList(void) { //--- 選択された項目のインデックス int li=m_selected_item_index; //--- コンテンツリストの空の配列 ::ArrayFree(m_cd_item_text); ::ArrayFree(m_cd_list_index); ::ArrayFree(m_cd_tree_list_index); //--- コンテンツリストを形成する int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- (1) ノードレベルと (2) 項目のローカルインデックスが一致して // (3) 1つ前のノードのインデックスが選択された項目のインデックスと一致する場合 if(m_t_node_level[i]==m_t_node_level[li]+1 && m_t_prev_node_item_index[i]==m_t_item_index[li] && m_t_prev_node_list_index[i]==li) { //--- コンテンツリストの表示される項目の配列を増加する int size =::ArraySize(m_cd_list_index); int new_size =size+1; ::ArrayResize(m_cd_item_text,new_size); ::ArrayResize(m_cd_list_index,new_size); ::ArrayResize(m_cd_tree_list_index,new_size); //--- 項目のテキストおよびツリービューの総合インデックスを配列に格納する m_cd_item_text[size] =m_t_item_text[i]; m_cd_tree_list_index[size] =m_t_list_index[i]; } } //--- 最終的にリストが空でない場合は、コンテンツリストを総合インデックスの配列で埋める int cd_items_total=::ArraySize(m_cd_list_index); if(cd_items_total>0) { //--- 項目カウンタ int c=0; //--- リストを反復処理する int c_items_total=::ArraySize(m_c_list_index); for(int i=0; i<c_items_total; i++) { //--- ツリービュー項目の説明と総合インデックスが一致した場合 if(m_c_item_text[i]==m_cd_item_text[c] && m_c_tree_list_index[i]==m_cd_tree_list_index[c]) { //--- コンテンツリストの総合インデックスを格納して次に進む m_cd_list_index[c]=m_c_list_index[i]; c++; //--- 表示されたリストの最後に達した場合にはループを終了する if(c>=cd_items_total) break; } } } //--- スクロールバースライダーのサイズを修正する m_content_scrollv.ChangeThumbSize(cd_items_total,m_visible_items_total); //--- 項目コンテンツのリストを修正する ShiftContentList(); }
リスト領域の幅の管理
ここで、リストの幅を変更するモードがどのように動作するかをより詳細に見ていきます。次が必要とされます。(1)CTreeView::ResizeListArea()メインプライベートメソッド。すべての主要なチェックはここで行われこれらのチェックの結果に基づいてリストの幅が変更され (2) 下記のタスクを解決するための4つの補助プライベートメソッド
- リストの幅を変更するために準備ができているかを確認するCTreeView::CheckXResizePointer() メソッド ここでのメソッドのコードは、1つの項に連結された2つのブロックで構成されています。カーソルポインタがアクティブにされていないがマウスカーソルがその領域にある場合、ポインタの座標が更新されて表示されます。マウスの左ボタンが押されるとポインタがアクティブになります。このメソッドの1番目の条件が行われていない場合は、カーソルポインタが非アクティブになり隠されます。
class CTreeView : public CElement { private: //--- リストの幅を変更するために準備ができているかを確認する void CheckXResizePointer(const int x,const int y); }; //+------------------------------------------------------------------+ //| リストの幅を変更するために準備ができているかを確認する | //+------------------------------------------------------------------+ void CTreeView::CheckXResizePointer(const int x,const int y) { //--- ポインタがアクティブ化されていないがマウスカーソルが領域内にある場合 if(!m_x_resize.State() && y>m_area.Y() && y<m_area.Y2() && x>m_area.X2()-2 && x<m_area.X2()+3) { //--- ポインタ座標を更新して表示する int l_x=x-m_x_resize.XGap(); int l_y=y-m_x_resize.YGap(); m_x_resize.Moving(l_x,l_y); m_x_resize.Show(); //--- 可視性フラグを設定する m_x_resize.IsVisible(true); //--- 左マウスボタンが押された場合 if(m_mouse_state) //--- ポインタをアクティブにする m_x_resize.State(true); } else { //--- 左マウスボタンが押された場合 if(!m_mouse_state) { //--- ポインタを非アクティブ化して隠す m_x_resize.State(false); m_x_resize.Hide(); //--- 可視性フラグを削除する m_x_resize.IsVisible(false); } } }
- CTreeView::CheckOutOfArea() メソッドは制限が超えないようにするテストです。それらのいずれかを完全に視界から除外するためにリストの幅を変更することには意味がありません。よって80画素への制限が置かれます。カーソルが水平に設定された制限を超えた場合には、ポインタシフトがリストの領域を接続するエリア内のみで垂直方向にのみに可能になるように手配してみましょう。
class CTreeView : public CElement { private: //--- 制限を超えないように確認する bool CheckOutOfArea(const int x,const int y); }; //+------------------------------------------------------------------+ //| 制限を超えないことを確認する | //+------------------------------------------------------------------+ bool CTreeView::CheckOutOfArea(const int x,const int y) { //--- 制限 int area_limit=80; //--- 水平方向に要素の境界線を越えた場合は... if(x<m_area.X()+area_limit || x>m_content_area.X2()-area_limit) { // ... 境界を出ずに垂直方向にのみポインタをシフトする if(y>m_area.Y() && y<m_area.Y2()) m_x_resize.UpdateY(y-m_x_resize.YGap()); //--- リストの幅を変えない return(false); } //--- リストの幅を変える return(true); }
- CTreeView::UpdateTreeListWidth() とCTreeView::UpdateContentListWidth() メソッドは (1) ツリービューの幅と (2) コンテンツ領域のリスト幅の更新のためにあります。ツリービューでは右の境界のみがシフトします。このためには、幅の変更のみが必要です。さらにスクロールバーの座標の更新が必要です。コンテンツエリアのリストについては、左境界線のみのシフトを達成するために、その幅とX座標 を同時に更新する必要があります。
class CTreeView : public CElement { private: //--- ツリービューの幅を更新する void UpdateTreeListWidth(const int x); //--- コンテンツ領域のリストの幅を更新する void UpdateContentListWidth(const int x); }; //+------------------------------------------------------------------+ //| ツリービューの幅を更新する | //+------------------------------------------------------------------+ void CTreeView::UpdateTreeListWidth(const int x) { //--- ツリービューの幅を計算して設定する m_area.X_Size(x-m_area.X()); m_area.XSize(m_area.X_Size()); //--- ツリービュー内の項目の幅をスクロールバーを考慮して計算して設定する int l_w=(m_items_total>m_visible_items_total) ?m_area.XSize()-m_scrollv.ScrollWidth()-4 : m_area.XSize()-1; int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) m_items[i].UpdateWidth(l_w); //--- ツリービュースクロールバーの座標を計算して設定する m_scrollv.X(m_area.X2()-m_scrollv.ScrollWidth()); m_scrollv.XDistance(m_scrollv.X()); } //+------------------------------------------------------------------+ //| コンテンツ領域のリストの幅を更新する | //+------------------------------------------------------------------+ void CTreeView::UpdateContentListWidth(const int x) { //---コンテンツ領域のX座標、オフセットと幅を計算して設定する int l_x=m_area.X2()-1; m_content_area.X(l_x); m_content_area.X_Distance(l_x); m_content_area.XGap(l_x-m_wnd.X()); m_content_area.XSize(CElement::X2()-m_content_area.X()); m_content_area.X_Size(m_content_area.XSize()); //--- コンテンツリストの項目のX座標、オフセットと幅を計算して設定する l_x=m_content_area.X()+1; int l_w=(m_content_items_total>m_visible_items_total) ?m_content_area.XSize()-m_content_scrollv.ScrollWidth()-4 : m_content_area.XSize()-2; int total=::ArraySize(m_content_items); for(int i=0; i<total; i++) { m_content_items[i].UpdateX(l_x); m_content_items[i].UpdateWidth(l_w); } }
これらのすべての補助的なメソッドは、最終的にアプリケーションイベントの処理に使われるCTreeView::ResizeListArea()メインメソッドで呼び出されます。メソッドの初めにいくつかのチェックが行われる必要があります。<プログラムは、下記の場合にはメソッドを終了します。
- リストの幅を変更するためのモードが無効な場合
- コンテンツ領域が無効な場合
- タブ項目モードが有効な場合
- スクロールバーがアクティブな場合(スライダがシフトモードになっている)
チェックが完了するとCTreeView::CheckXResizePointer() メソッドが呼び出されてリストの幅を変更するために準備ができているかが確認されます。最終的にポインタが無効になっていることが判明した場合、以前にブロックされたフォームのブロックを解除する必要があります。
ポインタが有効になっている場合は初めに 指定された制限が越えられたかを確認する必要があります。終了の場合には、プログラムはこのメソッドを終了します。作業領域にある場合、フォームがブロックされます。がフォームのブロックを解除するオプションはブロックした要素のみに持たれる必要があるため、要素の識別子は保存される必要があります。カーソルポインタの座標はリストの座標とその 幅とともに更新されます。
//+------------------------------------------------------------------+ //| リストの幅の管理 | //+------------------------------------------------------------------+ void CTreeView::ResizeListArea(const int x,const int y) { //--- (1) コンテンツ領域の幅が変更されない場合や // (2) コンテンツ領域が無効か (3) タブ項目モードが有効な場合 if(!m_resize_list_area_mode || m_content_area_width<0 || m_tab_items_mode) return; //--- スクロールバーがアクティブな場合は終了する if(m_scrollv.ScrollState()) return; //--- リストの幅を変更するために準備ができているかを確認する CheckXResizePointer(x,y); //--- ポインタが無効の場合はフォームのロックを解除する if(!m_x_resize.State()) { //--- フォームをブロックしたものはブロック解除できる if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()==CElement::Id()) { m_wnd.IsLocked(false); m_wnd.IdActivatedElement(WRONG_VALUE); return; } } else { //--- 指定された制限を超えた存在を確認する if(!CheckOutOfArea(x,y)) return; //--- フォームをブロックしてアクティブな要素の識別子を記憶する m_wnd.IsLocked(true); m_wnd.IdActivatedElement(CElement::Id()); //--- マウスカーソルの中央にオブジェクトのX座標を設定する m_x_resize.UpdateX(x-m_x_resize.XGap()); //--- Y座標は要素領域が超えられていない場合のみに設定される if(y>m_area.Y() && y<m_area.Y2()) m_x_resize.UpdateY(y-m_x_resize.YGap()); //--- ツリービューの幅を更新する UpdateTreeListWidth(x); //--- コンテンツリストの幅を更新する UpdateContentListWidth(x); //--- リストの座標とサイズを更新する ShiftTreeList(); ShiftContentList(); //--- ポインタを再描画する m_x_resize.Reset(); } }
中間結果のために、 CTreeView::OnEvent() 要素ハンドラからのコードブロックを提供します。ここではCHARTEVENT_MOUSE_MOVEマウス移動イベントが処理されます。上記で考察されたたくさんのメソッドがここで特に使われています。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- カーソル移動イベントの処理 if(id==CHARTEVENT_MOUSE_MOVE) { //--- 要素が非表示の場合は終了する if(!CElement::IsVisible()) return; //--- 左マウスボタンの座標と状態 int x=(int)lparam; int y=(int)dparam; m_mouse_state=(bool)int(sparam); //--- リスト上の焦点をチェックする CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2()); //--- スクロールバーのスライダーが管理されている場合はツリービューをシフトする if(m_scrollv.ScrollBarControl(x,y,m_mouse_state)) { ShiftTreeList(); return; } //--- スクロールバーのスライダーが管理されている場合はコンテンツリストをシフトする if(m_content_scrollv.ScrollBarControl(x,y,m_mouse_state)) { ShiftContentList(); return; } //--- コンテンツ領域の幅の管理 ResizeListArea(x,y); //--- フォームがブロックされている場合は終了する if(m_wnd.IsLocked()) return; //--- マウスのホバ-時に色を変更する ChangeObjectsColor(); return; } }
タブ項目のモード
タブモードがツリービューでどのように動作するかを把握してみましょう。現時点では、ライブラリにはすでにタブを作成するためのCTabsとCIconTabsの2つのクラスがあります。設計方法の詳細はグラフィカルインタフェースVII: タブコントロール(チャプター2)稿で参照できます。これらのクラスでは、タブは、水平方向と垂直方向の両方の行で作成されます。ツリービューでは、要素はカテゴリ別に配置されることができ、これはアプリケーションのグラフィックインターフェイスに多くの要素が存在して何らかのグループわけが必要なときに非常に便利です。
よって、ツリービューの作成時にタブ項目のモードが設定された場合は、すべての要素オブジェクトが作成された後にどちらのツリービュー項目がタブになるかが決定されるべきです。タブになれるのはローカルリストが含まれていない項目のみです。タブ項目には索引付けの順番があることは考慮されるべきです(下図を参照)。タブ項目にコントロールを追加するときは、インデックスのこの特定の順序に焦点を置くべきです。
下の図は、タブ項目の索引付けの順序の例を示しています。ポイントA、D、G、Hはリストを含むのでタブではありません。
図17 タブ項目のインデックス
この問題を解決するためには(そのサンプルの配列の宣言をもった)構造体が必要になり、これには要素へのポインタを格納する動的配列と タブインデックスを格納するフィールドが含まれます。
class CTreeView : public CElement { private: //--- すべてのタブ項目に割り当てられた要素の構造体 struct TVElements { CElement *elements[]; int list_index; }; TVElements m_tab_items[]; };
タブになるポイントを決定しその配列を形成するためにはCTreeView::GenerateTabItemsArray() メソッドが使用されます。タブ項目モードが無効になっている場合、プログラムはすぐにこのメソッドを終了します。その後、ツリービュー全体を反復処理してシンプルな項目が発見されるたびに TVElements構造体の配列を1要素で減少して項目の総合インデックスを保存します。
次に、項目のコンテンツが表示されている場合には、リスト内の最初の項目がデフォルトで選択されます項目のコンテンツリスツの表示が無効になっていると、範囲が越された場合にはインデックスが修正されます。プロパティで示されたタブ項目が選択されます。
class CTreeView : public CElement { private: //--- タブ項目の配列を形成する void GenerateTabItemsArray(void); }; //+------------------------------------------------------------------+ //| タブ項目の配列を形成する | //+------------------------------------------------------------------+ void CTreeView::GenerateTabItemsArray(void) { //--- タブ項目モードが無効の場合は終了する if(!m_tab_items_mode) return; //--- 項目タブの配列に空の項目のみを追加する int items_total=::ArraySize(m_items); for(int i=0; i<items_total; i++) { //--- この項目が他の項目を持つ場合は次に進む if(m_t_items_total[i]>0) continue; //--- タブ配列サイズを1要素分増やす int array_size=::ArraySize(m_tab_items); ::ArrayResize(m_tab_items,array_size+1); //--- 総合項目インデックスを格納する m_tab_items[array_size].list_index=i; } //--- 項目コンテンツの表示が無効な場合 if(!m_show_item_content) { //--- タブ配列のサイズを受け取る int tab_items_total=::ArraySize(m_tab_items); //--- 範囲から出た時点でインデックスを補修する if(m_selected_item_index>=tab_items_total) m_selected_item_index=tab_items_total-1; //--- リスト内の現在の項目の選択を無効にする m_items[m_selected_item_index].HighlightItemState(false); //--- 選択されたタブのインデックス int tab_index=m_tab_items[m_selected_item_index].list_index; m_selected_item_index=tab_index; //--- この項目を選択する m_items[tab_index].HighlightItemState(true); } }
ツリービュータブに任意のコントロールを接続するためにはCTreeView::AddToElementsArray() メソッドが使用されます。このメソッドには、(1)タブ項目のインデックスと(2)ポインタを指定されたタブ項目の配列に格納する必要があるCElement 型のオブジェクトの2つの引数があります。
class CTreeView : public CElement { public: //--- タブ項目の配列に要素を追加する void AddToElementsArray(const int item_index,CElement &object); }; //+------------------------------------------------------------------+ //| 指定したタブの配列に要素を追加する | //+------------------------------------------------------------------+ void CTreeView::AddToElementsArray(const int tab_index,CElement &object) { //--- 配列サイズの超過を確認する int array_size=::ArraySize(m_tab_items); if(array_size<1 || tab_index<0 || tab_index>=array_size) return; //--- 指定したタブの配列へ送信された要素のポインタを追加する int size=::ArraySize(m_tab_items[tab_index].elements); ::ArrayResize(m_tab_items[tab_index].elements,size+1); m_tab_items[tab_index].elements[size]=::GetPointer(object); }
選択されたタブ項目の要素のみの表示にはCTreeView::ShowTabElements() メソッドが使われます。要素が隠されているかモードが無効になっていることが判明した場合、プログラムはメソッドを終了します。そして、メソッドの最初のループで選択されたタブ項目のインデックスが決定されます。そして、2番目のループで、選択したタブに割り当てられていた要素のみが表示されて残りは非表示にされます。<
class CTreeView : public CElement { public: //--- 選択されたタブ項目の要素のみを表示する void ShowTabElements(void); }; //+------------------------------------------------------------------+ //| 選択されたタブ項目の要素のみを表示する | //+------------------------------------------------------------------+ void CTreeView::ShowTabElements(void) { //--- 要素が隠されているか、タブ項目モードが無効になっている場合は終了する if(!CElement::IsVisible() || !m_tab_items_mode) return; //--- 選択されたタブのインデックス int tab_index=WRONG_VALUE; //--- 選択されたタブのインデックスを定義する int tab_items_total=::ArraySize(m_tab_items); for(int i=0; i<tab_items_total; i++) { if(m_tab_items[i].list_index==m_selected_item_index) { tab_index=i; break; } } //--- 選択されたタブ項目の要素のみを表示する for(int i=0; i<tab_items_total; i++) { //--- タブに接続されている要素の数を取得する int tab_elements_total=::ArraySize(m_tab_items[i].elements); //--- このタブポイントが選択された場合 if(i==tab_index) { //--- 要素を表示する for(int j=0; j<tab_elements_total; j++) m_tab_items[i].elements[j].Reset(); } else { //--- 要素を非表示にする for(int j=0; j<tab_elements_total; j++) m_tab_items[i].elements[j].Hide(); } } }
イベント処理メソッド
リスト内の項目が選択されると、ツリービュー項目へのパスが変更されたことを示すメッセージが生成されます。その後、このイベントは、ファイルへのパスを決定するためにファイルナビゲーターに受け入れられる可能性があります。計画を実装するためにはDefines.mqhファイルにON_CHANGE_TREE_PATH識別子が必要です(以下のコードを参照)。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #define ON_CHANGE_TREE_PATH (23) // ツリービューへのパスが変更した
選択された項目へのパスを定めるために、ナビゲータでファイルへのパスを取得するのに呼び出される CTreeView::CurrentFullPath()パブリックメソッドを書きます。要素リストでは選択されたファイルを受け取るフィールドとメソッドも必要です。これはツリービューがファイルナビゲータの一部として使用されている場合にのみ有効です。CTreeView::CurrentFullPath() メソッドの詳細を考慮してみましょう。
パスはpath_parts[] 配列に、現在選択されている項目からリストの階層を上って項目を1つづつ追加することで形成されます。一番初めに、選択した項目がファイルであるかどうかを確認します。フォルダの場合は配列に追加します(ファイル名はパスに追加されません)。
その後、選択された項目から上向きに全体のツリービューを反復処理します。実装はこれに沿います。ファイル項目はすべて抜かされます。配列に項目を追加するために、次の3つの条件がすべて真である必要があります。
- 総合リストのインデックスが1つ前のノードの相互リストのインデックスと一致する
- ローカルリストの項目インデックスが1つ前のノードの項目インデックスと一致する
- ノードが大きい順である
すべての3つの条件が満たされた場合は(1)項目名を配列に追加し(2)ループの現在のインデックスが後のチェックのために保存され(3)ループカウンタがリセットされます。しかしノードのゼロレベルに達した場合はループが停止されます。
その後、独立したループ内で文字列が「\\」区切り文字を追加して形成されます(選択したアイテムへのフルパス)。更に、ツリービューで選択された項目がフォルダの場合、それがコンテンツ領域のリストに存在するファイルであるかを確認します。「ツリービューでファイルを表示する」モードが有効でファイルが選択された場合、 その名前は文字列の形成直後に保存されます。
メソッドの終わりには、選択された項目へのパスを含む文字列が返されます。現在のカテゴリーでファイルも選択された場合は、それはCTreeView::SelectedItemFileName()パブリックメソッドで取得できます。
class CTreeView : public CElement { private: //--- リストで選択された項目のテキスト // ファイルナビゲータを作成するためのクラスを使用した場合のファイルのためのみ // リストで選択されたファイルでない場合は、このフィールドは空の文字列「 ""」である必要がある string m_selected_item_file_name; //--- public: //--- ファイル名を返す string SelectedItemFileName(void) const { return(m_selected_item_file_name); } //--- 選択された項目のフルパスを返す string CurrentFullPath(void); }; //+------------------------------------------------------------------+ //| 現在のフルパスを返す | //+------------------------------------------------------------------+ string CTreeView::CurrentFullPath(void) { //--- 選択された項目でのディレクトリの形成 string path=""; //--- 選択された項目のインデックス int li=m_selected_item_index; //--- ディレクトリ形成のための配列 string path_parts[]; //--- ツリービューの選択した項目の説明(テキスト)を受信する // (フォルダの場合のみ) if(m_t_is_folder[li]) { ::ArrayResize(path_parts,1); path_parts[0]=m_t_item_text[li]; } //--- リスト全体を反復処理する int total=::ArraySize(m_t_list_index); for(int i=0; i<total; i++) { //--- フォルダのみが考慮される // ファイルがあった場合は次の項目に進む if(!m_t_is_folder[i]) continue; //--- 総合リストのインデックスが1つ前のノードの総合リストのインデックスと一致し // ローカルリストの項目インデックスが1つ前のノードの項目インデックスと一致した場合 // ノードレベルの順序が維持される if(m_t_list_index[i]==m_t_prev_node_list_index[li] && m_t_item_index[i]==m_t_prev_node_item_index[li] && m_t_node_level[i]==m_t_node_level[li]-1) { //--- 配列を1要素で増加させて項目説明を保存する int sz=::ArraySize(path_parts); ::ArrayResize(path_parts,sz+1); path_parts[sz]=m_t_item_text[i]; //--- 次のチェックのためにインデックスを記憶する li=i; //---ゼロノードのレベルに到達した場合はループを終了する if(m_t_node_level[i]==0 || i<=0) break; //--- ループカウンタをリセットする i=-1; } } //--- ツリービューで選択した項目へのフルパスの文字列を形成する total=::ArraySize(path_parts); for(int i=total-1; i>=0; i--) ::StringAdd(path,path_parts[i]+"\\"); //--- フォルダがツリービューで選択された項目の場合 if(m_t_is_folder[m_selected_item_index]) { m_selected_item_file_name=""; //--- 項目がコンテンツ領域で選択された場合 if(m_selected_content_item_index>0) { //--- 選択された項目がファイルの場合には名前を格納する if(!m_t_is_folder[m_c_tree_list_index[m_selected_content_item_index]]) m_selected_item_file_name=m_c_item_text[m_selected_content_item_index]; } } //--- ツリービューで選択された項目がファイルの場合 else //--- 名前を格納する m_selected_item_file_name=m_t_item_text[m_selected_item_index]; //--- ディレクトリを返す return(path); }
現在のツリービュー要素のバージョンでは3つのユーザアクションが処理されます。
- 項目のローカルリストを展開する/折りたたむボタンの押下 – CTreeView::OnClickItemArrow() メソッドここで、グラフィックオブジェクトの名前がツリービューからのものではない、または要素識別子がオブジェクト名のツリービューの識別子と一致しない場合は、プログラムはメソッドを終了します。その後 (1) 項目ボタンの状態を取得して状態を反転し (2) ツリービューを最新の変更を表示するために更新し(3) スクロールバースライダーの位置を計算し (4) 関連するモードが有効な場合は、現時点で選択されたタブ項目の要素のみを表示します。
class CTreeView : public CElement { private: //--- 項目リストを展開する/折りたたむボタンの押下を処理する bool OnClickItemArrow(const string clicked_object); }; //+------------------------------------------------------------------+ //| 項目リストを展開する/折りたたむボタンの押下 | //+------------------------------------------------------------------+ bool CTreeView::OnClickItemArrow(const string clicked_object) { //--- オブジェクト名が異なる場合には終了する if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_arrow_",0)<0) return(false); //--- オブジェクト名から識別子を取得する int id=IdFromObjectName(clicked_object); //--- 識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //--- 総合リストでの項目のインデックスを取得する int list_index=IndexFromObjectName(clicked_object); //--- 項目の矢印の状態を取得して反対矢印を設定する m_t_item_state[list_index]=!m_t_item_state[list_index]; ((CChartObjectBmpLabel*)m_items[list_index].Object(1)).State(m_t_item_state[list_index]); //--- ツリービューを更新する UpdateTreeViewList(); //--- スクロールバースライダーの位置を計算する m_scrollv.CalculateThumbY(); //--- 選択されたタブ項目の要素を表示する ShowTabElements(); return(true); }
- ツリービュー項目のクリック - CTreeView::OnClickItem() メソッドここでは、一番最初に、説明の前の項目と同じようにいくつかのチェックがあります。中でも、メソッドの終了は、要素内のスクロールバーの1つが現時点でアクティブなときに実行されます。
そして、ループ内では、ツリービューの可視部分を反復処理します。押された項目を見つけます。項目がすでに選択されているような場合はプログラムがメソッドを終了します。選択されていない場合は、タブ項目のモードが有効、選択された項目のコンテンツの表示が無効で、項目がリストを含まない場合は、ループが直ちに停止されて変更は行われません。そうでない場合には、押された項目が選択されます。
ループの後では、この瞬間が到達した場合、新しい項目が選択されます。これは、コンテンツ領域のリストでの選択された項目のインデックスと色がリセットされる必要があることを意味します。例えばこれはウィンドウズエクスプローラでの実装です。そして、コンテンツ領域のリストは、最新の変更を表示するために更新されます。一番最後にはON_CHANGE_TREE_PATHイベント識別子を持ったメッセージ が生成され、カスタムアプリケーションや他のいくつかの要素のイベントハンドラで処理されます。
class CTreeView : public CElement { private: //--- ツリービュー項目のクリックを処理する bool OnClickItem(const string clicked_object); }; //+------------------------------------------------------------------+ //| ツリービュー項目のクリック | //+------------------------------------------------------------------+ bool CTreeView::OnClickItem(const string clicked_object) { //--- スクロールバーがアクティブな場合は終了する if(m_scrollv.ScrollState() || m_content_scrollv.ScrollState()) return(false); //--- オブジェクト名が異なる場合には終了する if(::StringFind(clicked_object,CElement::ProgramName()+"_0_treeitem_area_",0)<0) return(false); //--- オブジェクト名から識別子を取得する int id=IdFromObjectName(clicked_object); //--- 識別子が一致しない場合は終了する if(id!=CElement::Id()) return(false); //--- スクロールバースライダーの現在位置を取得する int v=m_scrollv.CurrentPos(); //--- リストを反復処理する for(int r=0; r<m_visible_items_total; r++) { //--- 範囲超過を防ぐための確認 if(v>=0 && v<m_items_total) { //--- 総合項目インデックスを取得する int li=m_td_list_index[v]; //--- この項目がリストで選択された場合 if(m_items[li].Object(0).Name()==clicked_object) { //--- この項目がすでに選択されている場合は終了する if(li==m_selected_item_index) return(false); //--- タブ項目モードが有効になっていてコンテンツを表示するモードが無効になっている場合は // リストを持たない項目は選択しない if(m_tab_items_mode && !m_show_item_content) { //--- 現在の項目にリストがない場合はループを停止する if(m_t_items_total[li]>0) break; } //--- 前回選択された項目に色を設定する m_items[m_selected_item_index].HighlightItemState(false); //--- 現在のインデックスを記録して色を変更する m_selected_item_index=li; m_items[li].HighlightItemState(true); break; } v++; } } //--- コンテンツ領域の色をリセットする if(m_selected_content_item_index>=0) m_content_items[m_selected_content_item_index].HighlightItemState(false); //--- 選択された項目をリセットする m_selected_content_item_index=WRONG_VALUE; //--- コンテンツリストを更新する UpdateContentList(); //--- スクロールバースライダーの位置を計算する m_content_scrollv.CalculateThumbY(); //--- コンテンツリストを修正する ShiftContentList(); //--- 選択されたタブ項目の要素を表示する ShowTabElements(); //--- ツリービューで新しいディレクトリが選択されたことについてのメッセージを送る ::EventChartCustom(m_chart_id,ON_CHANGE_TREE_PATH,0,0,""); return(true); }
- コンテンツ領域のリストの項目を押す – CTreeView::OnClickContentListItem() メソッドこのメソッドのコードは、前の項目で説明したものと似ていてそれより断然単純なので、ここではお話しません。。コンテンツ領域で項目をクリックするとON_CHANGE_TREE_PATH識別子を持ったイベントが生成され、これは例としてファイルナビゲータでのファイルの選択を暗示することにご注意ください。
最終的に、以前本稿で考察されたメソッドを介した要素との相互作用のプ過程で、両リストは(コンテンツ領域が有効になっている場合)「オンザゴー」で再形成されることになります。上記で考察されたメソッドを持った要素のイベントハンドラのコードブロックは以下のコードで詳しく研究できます。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CTreeView::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- カーソル移動イベントの処理 //... //--- オブジェクトのマウス左クリックイベントの処理 if(id==CHARTEVENT_OBJECT_CLICK) { //--- コンテンツ領域の幅を変更するモードが有効な場合は終了する if(m_x_resize.IsVisible() || m_x_resize.State()) return; //--- 項目の矢印のクリックを処理する if(OnClickItemArrow(sparam)) return; //--- ツリービュー項目のクリックを処理する if(OnClickItem(sparam)) return; //--- コンテンツ領域の項目のクリックを処理する if(OnClickContentListItem(sparam)) return; //--- リストをスクロールバーに対してシフトする if(m_scrollv.OnClickScrollInc(sparam) || m_scrollv.OnClickScrollDec(sparam)) ShiftTreeList(); if(m_content_scrollv.OnClickScrollInc(sparam) || m_content_scrollv.OnClickScrollDec(sparam)) ShiftContentList(); //--- return; } }
ツリービュー要素作成クラスの最初のバージョンが完全に終わりました。しかし、そのすべてのモードで正確な動作を実現するためには、これがライブラリースライダーに統合されなければなりません。
ライブラリエンジンへの要素の統合
ツリービュークラスファイルはWndContainer.mqhファイルに接続され無ければなりません(下のコードを参照)。それぞれのツリービューの配列を要素配列の構造体に加えます。また、 ツリービューリストの数を受信するためのメソッドと ツリービューリストの一部であるこれらの要素へのポインタを格納するためのメソッドが必要となります。これらのメソッドのコードはより徹底的に、この記事の末尾に添付されたファイルで検討されることになります。
#include "TreeItem.mqh" #include "TreeView.mqh" //+------------------------------------------------------------------+ //| インターフェースオブジェクト格納クラス | //+------------------------------------------------------------------+ class CWndContainer { protected: //--- ウィンドウ配列 CWindow *m_windows[]; //--- 要素配列の構造体 struct WindowElements { //--- ツリービューリスト CTreeView *m_treeview_lists[]; }; //--- 各ウィンドウの要素のジャグ配列 WindowElements m_wnd[]; //--- public: //--- ツリービューリストの数 int TreeViewListsTotal(const int window_index); //--- private: //--- ツリービューリストの要素へのポインタを格納する bool AddTreeViewListsElements(const int window_index,CElement &object); };
CWndContainerクラスとメインライブラリイベントが処理されるCWndEvents派生クラスにはいくつかの追加が必要です。まず第一に、ツリービューをグラフィックインターフェースの要素をグループ化するためのタブとして用いた場合には、フォームを展開するときに、現時点で選択された要素のみを表示することが必要とされます。よって、下記のように、コードがCWndEvents::OnWindowUnroll() メソッドに追加される べきです。この特定の例の場合は要素はそれぞれの配列に分散されています。すべての要素の配列全体を反復処理する代わりに、特定の状況で必要とされているそれぞれの配列を反復処理するので十分です。
//+------------------------------------------------------------------+ //| ON_WINDOW_UNROLL イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnWindowUnroll(void) { //--- 「アンロールウィンドウ」シグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_WINDOW_UNROLL) return(false); //--- アクティブウィンドウのインデックス int awi=m_active_window_index; //--- ウィンドウ識別子とサブウィンドウ番号が一致する場合 if(m_lparam==m_windows[awi].Id() && (int)m_dparam==m_subwin) { int elements_total=CWndContainer::ElementsTotal(awi); for(int e=0; e<elements_total; e++) { //--- フォームや ... if(m_wnd[awi].m_elements[e].ClassName()!="CWindow") { //--- ... ドロップダウンリストの要素以外のすべての要素を表示する if(!m_wnd[awi].m_elements[e].IsDropdown()) m_wnd[awi].m_elements[e].Show(); } } //--- タブのみがある場合は選択された要素のみを表示する int tabs_total=CWndContainer::TabsTotal(awi); for(int t=0; t<tabs_total; t++) m_wnd[awi].m_tabs[t].ShowTabElements(); //--- ツリービューリストがある場合は選択されたタブ項目の要素のみを表示する int treeview_total=CWndContainer::TreeViewListsTotal(awi); for(int tv=0; tv<treeview_total; tv++) m_wnd[awi].m_treeview_lists[tv].ShowTabElements(); } //--- すべての要素の位置を更新する MovingWindow(); m_chart.Redraw(); return(true); }
同様のループはCWndEvents::OnOpenDialogBox() メソッドでCTabs型(タブ)の要素のための似たループの直後に追加されるべきです。記事のスペースを節約するためにここにはコードは記載しません。
ツリービューリストのそれぞれの配列を空にすることも重要です。次の行は、他のコントロール配列同様CWndEvents::Destroy() メソッドに追加されるべきです。
::ArrayFree(m_wnd[w].m_treeview_lists);
ツリービュ要素の検証
ツリービューをテストする準備が整いました。前回の記事のEAを使用して、メインメニューとステータス文字列以外のすべての要素を削除します。さら、ツリービューリストを作成するCTreeView クラスインスタンスと要素が接続される端の点からのオフセットを宣言します。
class CProgram : public CWndEvents { private: //--- ツリービュー CTreeView m_treeview; //--- private: //--- ツリービュー #define TREEVIEW1_GAP_X (2) #define TREEVIEW1_GAP_Y (43) bool CreateTreeView(void); };
例として、以前、本稿の図 12で提示されたツリービューを作成します(コードは以下を参照)。リストには全部で25項目があります。表示される項目は10を越しません。記事の同じセクションでは、そのようなリストを作成する際に、そのパラメータを個別に指定する必要があることが明確にされました。すべての項目のパラメータで配列を形成する前に、テーブルエディタでそれらすべてを表示することをお勧めします。このような単純な視覚化がタスクを簡素化し、間違いを犯すリスクを軽減します。
画像を項目のグループごとに設定します。最初の項目にはリストが含まれます。例えば、要素の作成後にはアンロールし(trueの状態),他の項目のリストは折りたたまれたままになります(falseの状態)。(1) マウスがホバーした際の項目の強調表示 (2) 隣接する領域内で項目のコンテンツを表示または (3) リスト領域の幅を変更するモードを有効にします。
要素のプロパティがすべて設定された後は、配列で示されたパラメータを持つ項目をCTreeView::AddItem() メソッドを使ってリストに追加します。ツリービューはその後に作成され、それへのポインタは要素のベースに保存されます。
//+------------------------------------------------------------------+ //| ツリービューを作成する | //+------------------------------------------------------------------+ bool CProgram::CreateTreeView(void) { //--- ツリービューの項目数 #define TREEVIEW_ITEMS 25 //--- ウィンドウへのポインタを格納する m_treeview.WindowPointer(m_window1); //--- 座標 int x=m_window1.X()+TREEVIEW1_GAP_X; int y=m_window1.Y()+TREEVIEW1_GAP_Y; //--- ツリービューの配列を形成する // 項目の画像 #define A "Images\\EasyAndFastGUI\\Icons\\bmp16\\advisor.bmp" // エキスパートアドバイザー #define I "Images\\EasyAndFastGUI\\Icons\\bmp16\\indicator.bmp" // 指標 #define S "Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp" // スクリプト string path_bmp[TREEVIEW_ITEMS]= {A,I,I,I,I,I,I,I,I,S,S,S,S,S,S,S,S,S,S,S,S,S,S,A,A}; //--- 項目の説明(表示されるテキスト) string item_text[TREEVIEW_ITEMS]= {"Advisor01","Indicators","01","02","03","04","05","06","07", "Scripts","01","02","03","04","05","06","07","08","09","10","11","12","13", "Advisor02","Advisor03"}; //--- 1つ前のノードの総合リストのインデックス int prev_node_list_index[TREEVIEW_ITEMS]= {-1,0,1,1,1,1,1,1,1,0,9,9,9,9,9,9,9,9,9,9,9,9,9,-1,-1}; //--- ローカルリストでの項目のインデックス int item_index[TREEVIEW_ITEMS]= {0,0,0,1,2,3,4,5,6,1,0,1,2,3,4,5,6,7,8,9,10,11,12,1,2}; //--- ノードレベルの数 int node_level[TREEVIEW_ITEMS]= {0,1,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0}; //--- 以前のノードの項目のローカルインデックス int prev_node_item_index[TREEVIEW_ITEMS]= {-1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1}; //--- ローカルリストでの項目数 int items_total[TREEVIEW_ITEMS]= {2,7,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //--- 項目リストの状態 bool item_state[TREEVIEW_ITEMS]= {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //--- 作成前にプロパティを設定する m_treeview.TreeViewAreaWidth(180); m_treeview.ContentAreaWidth(0); m_treeview.VisibleItemsTotal(10); m_treeview.LightsHover(true); m_treeview.ShowItemContent(true); m_treeview.ResizeListAreaMode(true); //--- スクロールバーのプロパティ m_treeview.GetScrollVPointer().AreaBorderColor(clrLightGray); m_treeview.GetContentScrollVPointer().AreaBorderColor(clrLightGray); //--- ポイントを追加する for(int i=0; i<TREEVIEW_ITEMS; i++) m_treeview.AddItem(i,prev_node_list_index[i],item_text[i],path_bmp[i], item_index[i],node_level[i],prev_node_item_index[i],items_total[i],0,item_state[i],true); //--- ツリービューを作成する if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,x,y)) return(false); //--- ベースに要素へのポインタを追加する CWndContainer::AddToElementsArray(0,m_treeview); return(true); }
このメソッドの呼び出しは MQLアプリケーションのグラフィカルインターフェースを作成するメインメソッドに配置されなければなりません。この場合、メソッドはCProgram::CreateExpertPanel()です。
プログラムをコンパイルしてEAをチャートで実行します。最終的に得なければならない結果は図に表わされています。最初のアイテムが展開されて選択されているようです。そのコンテンツが右側の領域に表示されています。
図18 ツリービュ要素の検証。最初の項目リストのみが展開されています。
お見せするために、すべての項目のリストを展開します。そのため、項目の矢印ボタンは押される必要があります。これを行った後、コンテンツ領域内でリストを表示するために「スクリプト」の記述を持つ項目を選択します。結果は下記の図に見られます。項目数が 10 可視項目の範囲内に収まらない場合はスクロールバーが表示されます。コンテンツ領域では3番目の項目が選択されています。また、要素のリストの2つの領域を接続する境界線上でマウスカーソルをホバーすると、マウスカーソルのユーザーポインタ(両面エラー)が表示されることが確認できます。
図19 すべての項目のリストが展開されている
タブ項目モードが示されているテストのために別のEAを作成します。以下の図で説明した方式で3つの展開されたリストを作ります。
図20 ツリービューのスキーム
CCheckBoxとCTableのような要素が「アドバイザー」と「指標」タブ項目リストのために確保されます。「スクリプト」リストのタブ項目は空のままなので、練習の機会です。コード全体は使用されません。どちらのキーモードとプロパティはこのオプションのために使用される点のみには注意してください。(1)タブ項目のモードが有効(2)項目コンテンツの表示が無効 (3)3番目のタブ項目が選択されている.。
//... m_treeview.TabItemsMode(true); m_treeview.LightsHover(true); m_treeview.ShowItemContent(false); m_treeview.SelectedItemIndex((m_treeview.SelectedItemIndex()==WRONG_VALUE) ?3 : m_treeview.SelectedItemIndex()); //--- スクロールバーのプロパティ //...
コンパイルしてプログラムをチャートに読み込みます。下のスクリーンショットが結果を示します。
図21 タブ項目モードの検証
おわりに
本稿(記事シリーズの第八部第2章)では、グラフィックインタフェースライブラリの中で最も複雑な要素の一つと考えられるツリービュー要素を考察してきました。本稿では、3つの要素のクラスが用意されています。
- マウスカーソルのユーザーポインタを作成するCPointerクラス
- ツリービュー項目を作成するCTreeItemクラス
- ツリービューを作成するCTreeView
次の記事では、このテーマを更に開発し、MQL-アプリケーションでファイルナビゲータを作成する過程を容易にする非常に有益なコードのクラスを作成します。
以下は、ダウンロード後に検証可能な第八部の記事の資料のすべてです。これらのファイルに含まれている資料の使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるか、本稿へのコメント欄でご質問ください。
第八部の記事(チャプター)のリストは下記の通りです。
- グラフィカルインタフェースVIII:カレンダーコントロール(チャプター1)
- グラフィカルインタフェースVIII: ツリービューコントロール(チャプター2)
- グラフィカルインタフェースVIII: ファイルナビゲータコントロール(チャプター3)
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2539
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索