グラフィカルインターフェイスXI:レンダリングされたコントロール(ビルド14.2)
コンテンツ
はじめに
シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考察されます。開発の現段階でのライブラリのフルバージョンは、シリーズの各記事の末尾にあります。ファイルはアーカイブと同じディレクトリに配置される必要があります。
ライブラリのこの新バージョンのでは、すべてのコントロールが個別のOBJ_BITMAP_LABEL型のグラフィカルオブジェクトに描画されます。さらに、ライブラリコードの全体的な最適化について引き続き説明します。この説明は前の記事ですでに始まりました。次に、ライブラリの中核クラスの変更について考えてみましょう。新しいバージョンのライブラリは、よりオブジェクト指向になっています。コードはさらに理解しやすくなっています。これにより、ユーザーはそれぞれのタスクに応じて独自にライブラリを開発することができます。
コントロールを描画する方法
canvasクラスインスタンスがCElementクラスで宣言されています。そのメソッドを使用すると、オブジェクトを作成して削除することができます。必要に応じてそのポインタも取得できます。
class CElement : public CElementBase { protected: //--- コントロールを描画するキャンバス CRectCanvas m_canvas; //--- public: //--- コントロールのキャンバスへのポインタを返す CRectCanvas *CanvasPointer(void) { return(::GetPointer(m_canvas)); } };
これで、コントロールの外観を描画するためのオブジェクト(キャンバス)を作成する一般的なメソッドができました。このメソッドはCElement基本クラスに位置し、ライブラリコントロールのすべてのクラスからアクセスできます。この型のグラフィックオブジェクトを作成するにはCElement::CreateCanvas()を使い、引数として (1) 名前、(2) 座標、(3) 次元、 (4) 色形式を渡さなければなりません。デフォルト形式はCOLOR_FORMAT_ARGB_NORMALIZEでコントロールを透明にすることができます。無効な寸法が渡された場合、これはメソッドの初めに修正されます。オブジェクトが作成されてMQLアプリケーションのチャートに取り付けられると、基本プロパティが設定されます。以前はこれはすべてのクラスのコントロールで繰り返されていました。
class CElement : public CElementBase { public: //--- キャンバスを作成する bool CreateCanvas(const string name,const int x,const int y, const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE); }; //+------------------------------------------------------------------+ //| コントロールを描画するキャンバスの作成 | //+------------------------------------------------------------------+ bool CElement::CreateCanvas(const string name,const int x,const int y, const int x_size,const int y_size,ENUM_COLOR_FORMAT clr_format=COLOR_FORMAT_ARGB_NORMALIZE) { //--- サイズを調整する int xsize =(x_size<1)?50 : x_size; int ysize =(y_size<1)?20 : y_size; //--- 最後のエラーをリセットする ::ResetLastError(); //--- オブジェクトの作成 if(!m_canvas.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,xsize,ysize,clr_format)) { ::Print(__FUNCTION__," > Failed to create a canvas for drawing the control ("+m_class_name+"): ",::GetLastError()); return(false); } //--- 最後のエラーをリセットする ::ResetLastError(); //--- 基本クラスへのポインタを取得する CChartObject *chart=::GetPointer(m_canvas); //--- チャートに取り付ける if(!chart.Attach(m_chart_id,name,(int)m_subwin,(int)1)) { ::Print(__FUNCTION__," > Failed to attach the canvas for drawing to the chart: ",::GetLastError()); return(false); } //--- プロパティ m_canvas.Tooltip("\n"); m_canvas.Corner(m_corner); m_canvas.Selectable(false); //--- フォーム以外のすべてのコントロールはメインコントロールより優先度が高くなる Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)?0 : m_main.Z_Order()+1); //--- 座標 m_canvas.X(x); m_canvas.Y(y); //--- サイズ m_canvas.XSize(x_size); m_canvas.YSize(y_size); //--- 端点からのオフセット m_canvas.XGap(CalculateXGap(x)); m_canvas.YGap(CalculateYGap(y)); return(true); }
コントロールを描画する基本メソッドに移りましょう。これらはすべてCElementクラスにありvirtualとして宣言されています。
まずは背景の描画です。基本バージョンでは、 これはCElement::DrawBackground() メソッドを使用する単純な色塗りです。必要に応じて、透明性を有効にすることも可能で、これには、 CElement::Alpha() メソッドを使い、0から255までのアルファチャンネル値を引数として渡します。0の値は完全な透明度を意味します。現在のバージョンでは、透明度は背景の塗りつぶしと境界線にのみ適用されます。テキストと画像は、アルファチャンネルの任意の値で完全に不透明かつクリアに保たれます。
class CElement : public CElementBase { protected: //--- アルファチャネルの値(コントロールの透明性) uchar m_alpha; //--- public: //--- アルファチャネルの値(コントロールの透明性) void Alpha(const uchar value) { m_alpha=value; } uchar Alpha(void) const { return(m_alpha); } //--- protected: //--- 背景を描画する virtual void DrawBackground(void); }; //+------------------------------------------------------------------+ //| 背景を描画する | //+------------------------------------------------------------------+ void CElement::DrawBackground(void) { m_canvas.Erase(::ColorToARGB(m_back_color,m_alpha)); }
特定のコントロールの境界線を描画する必要があることはよくあります。CElement::DrawBorder()メソッドは、キャンバスオブジェクトの端の周りに境界線を描きます。これにはRectangle()メソッドを使うこともできますが、後者は塗りつぶさずに長方形を描画します。
class CElement : public CElementBase { protected: //--- フレームを描画する virtual void DrawBorder(void); }; //+------------------------------------------------------------------+ //| 境界を描画する | //+------------------------------------------------------------------+ void CElement::DrawBorder(void) { //--- 座標 int x1=0,y1=0; int x2=m_canvas.X_Size()-1; int y2=m_canvas.Y_Size()-1; //--- 塗りつぶさずに長方形を描画する m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(m_border_color,m_alpha)); }
任意の数の画像グループを任意のコントロールに割り当てることができることは前の記事ですでにお話ししました。したがって、コントロールを描画するメソッドでは、ユーザが設定したすべての画像を出力することが必要です。 この目的にはCElement::DrawImage()メソッドが使われます。プログラムは、 すべてのグループと画像を連続的に反復し、画素単位でキャンバスに出力します。画像出力ループが始まる前に、グループ内で現在選択されている画像が特定されます。本メソッドのコードは下記です。
class CElement : public CElementBase { protected: //--- 画像を描画する virtual void DrawImage(void); }; //+------------------------------------------------------------------+ //| 画像を描画する | //+------------------------------------------------------------------+ void CElement::DrawImage(void) { //--- グループ数 uint group_total=ImagesGroupTotal(); //--- 画像を描画する for(uint g=0; g<group_total; g++) { //--- 選択された画像のインデックス int i=SelectedImage(g); //--- 画像がない場合 if(i==WRONG_VALUE) continue; //--- 座標 int x =m_images_group[g].m_x_gap; int y =m_images_group[g].m_y_gap; //--- サイズ uint height =m_images_group[g].m_image[i].Height(); uint width =m_images_group[g].m_image[i].Width(); //--- 描画 for(uint ly=0,p=0; ly<height; ly++) { for(uint lx=0; lx<width; lx++,p++) { //--- 色がない場合は次のピクセルに移る if(m_images_group[g].m_image[i].Data(p)<1) continue; //--- アイコンの指定されたピクセルの下レイヤの色(セルの背景)と色を取得する uint background =::ColorToARGB(m_canvas.PixelGet(x+lx,y+ly)); uint pixel_color =m_images_group[g].m_image[i].Data(p); //--- 色を混ぜる uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color)); //--- オーバーレイアイコンのピクセルを描画する m_canvas.PixelSet(x+lx,y+ly,foreground); } } } }
多くのコントロールには説明テキストがありますが、これはCElement::DrawText()メソッドで表示できます。このメソッドのいくつかのフィールドでは、コントロールの状態に応じてテキストの表示をカスタマイズできます。コントロールの3つの状態が利用可能です。
- ロック
- 押下
- フォーカス(マウスがホバー)
さらに、このメソッドは、テキストの中央整列モードが有効になっているかどうかを考慮します。下記がコードです。
class CElement : public CElementBase { protected: //--- テキストを描画する virtual void DrawText(void); }; //+------------------------------------------------------------------+ //| テキストを描画する | //+------------------------------------------------------------------+ void CElement::DrawText(void) { //--- 座標 int x =m_label_x_gap; int y =m_label_y_gap; //--- テキストラベルの色を定義する color clr=clrBlack; //--- コントロールがロックされている場合 if(m_is_locked) clr=m_label_color_locked; else { //--- コントロールが押下されている場合 if(!m_is_pressed) clr=(m_mouse_focus)?m_label_color_hover : m_label_color; else { if(m_class_name=="CButton") clr=m_label_color_pressed; else clr=(m_mouse_focus)?m_label_color_hover : m_label_color_pressed; } } //--- フォントプロパティ m_canvas.FontSet(m_font,-m_font_size*10,FW_NORMAL); //--- 中央整列モードを考慮してテキストを描画する if(m_is_center_text) { x =m_x_size>>1; y =m_y_size>>1; m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_CENTER|TA_VCENTER); } else m_canvas.TextOut(x,y,m_label_text,::ColorToARGB(clr),TA_LEFT); }
上記のメソッドはすべて、一般的なパブリック仮想メソッドCElement::Draw()で呼び出されます。描画のために呼び出されるメソッドのセットは各コントロール内で一意になるため、基本コードはありません。
class CElement : public CElementBase { public: //--- コントロールを描画する virtual void Draw(void) {} };
CElement::Update()メソッドを考察しましょう。これは、グラフィカルインタフェースの制御をプログラムに変更するたびに呼び出されます。(1) コントロールの完全な再描画または (2) 前もって行った変更の適用の、2つの呼び出しオプションがあります (以下のコードを参照)。このメソッドは、特定のクラスのコントロールが独自のバージョンを持つことがあるので、仮想メソッドとして宣言されています。これは特にメソッドの特異性とレンダリングの順序を考慮します。
class CElement : public CElementBase { public: //--- 最新の変更を表示するようにコントロールを更新する virtual void Update(const bool redraw=false); }; //+------------------------------------------------------------------+ //| コントロールの更新 | //+------------------------------------------------------------------+ void CElement::Update(const bool redraw=false) { //--- コントロールの再描画を伴う if(redraw) { Draw(); m_canvas.Update(); return; } //--- 適用 m_canvas.Update(); }
グラフィカルインタフェースの新設計
ライブラリのすべてのコントロールがレンダリングされるようになったので、グラフィカルインターフェイスの新しいデザインを実装することが可能になりました。既成のソリューションを使用することができるため、特別なものを発明する必要はありません。Windows 10 OSの魅力的な美学が基礎として使われました。
コントロールフォームボタン、ラジオボタン、チェックボックス、コンボボックス、メニュー項目、ツリーリスト項目などののような要素のアイコンの画像は、Windows 10と同様に作成されています。
コントロールの透明性を設定できるようになったことは前述しました。下のスクリーンショットは半透明なウィンドウの例です(CWindow)。ここでのアルファチャンネルの値は200です。
図8 コントロールフォームの透明性のデモンストレーション
フォームの全領域を透明にするにはCWindow::TransparentOnlyCaption()メソッドを使用します。デフォルトモードはヘッダーのみに透過性を適用します。
class CWindow : public CElement { private: //--- ヘッダーのみで透過性を有効化する bool m_transparent_only_caption; //--- public: //--- ヘッダーのみで透過性を有効化する void TransparentOnlyCaption(const bool state) { m_transparent_only_caption=state; } }; //+------------------------------------------------------------------+ //| コンストラクタ | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_transparent_only_caption(true) { ... }
以下はさまざまな種類のボタンの外観です。
図9 いくつかのボタンタイプの外観のデモンストレーション
次のスクリーンショットは、チェックボックス、スピンエディットボックス、スクロールバーとドロップダウンリスト付きのコンボボックス、数値スライダーの現在の外観を示しています。アニメーションアイコンを作成できるようになったことにご注意ください。ステータスバーの3番目の項目はサーバーとの接続を模倣します。その外観は、MetaTrader 5のステータスバーにある同様の要素とまったく同じです。
図10 チェックボックス、コンボボックス、スライダー、その他のコントロールの外観のデモンストレーション
グラフィカルインターフェイスライブラリの他のコントロールの外観は、本稿添付のテストMQLアプリケーションで見ることができます。
ツールヒント
コントロールのツールヒントの表示を管理する追加のメソッドがCElementクラスに追加され、コントロールに最長63文字の標準ツールヒントを設定できるようになりました。ツールヒントテキストを設定および取得するにはCElement::Tooltip()メソッドを使います。
class CElement : public CElementBase { protected: //--- ツールヒントのテキスト string m_tooltip_text; //--- public: //--- ツールヒント void Tooltip(const string text) { m_tooltip_text=text; } string Tooltip(void) const { return(m_tooltip_text); } };
ツールヒントの表示を有効/無効化するにはCElement::ShowTooltip()メソッドを使います。
class CElement : public CElementBase { public: //--- ツールヒント表示モード void ShowTooltip(const bool state); }; //+------------------------------------------------------------------+ //| ツールヒント表示の設定 | //+------------------------------------------------------------------+ void CElement::ShowTooltip(const bool state) { if(state) m_canvas.Tooltip(m_tooltip_text); else m_canvas.Tooltip("\n"); }
各コントロールクラスには、ネストされたコントロールへのポインタを取得するメソッドがあります。たとえば、フォームボタンのツールヒントを作成する必要がある場合は、カスタムクラスのフォーム作成メソッドに次のコード行を追加します。
... //--- ツールヒントを設定する m_window.GetCloseButtonPointer().Tooltip("Close"); m_window.GetCollapseButtonPointer().Tooltip("Collapse/Expand"); m_window.GetTooltipButtonPointer().Tooltip("Tooltips"); ...
下の図でその動作を確認しましょう。フォームボタンの標準ツールヒントが有効になっています。63文字を超える記述が必要なコントロールでは、CTooltipコントロールを使用します。
図11 2種類のツールヒントのデモンストレーション(標準とカスタム)
新規イベント識別子
新しいイベント識別子が追加され、それによって、CPUリソースの消費が大幅に削減されました。これはどのように達成されたのでしょうか?
グラフィカルインタフェースと多くの制御機能を備えた大規模なMQLアプリケーションを作成する場合は、CPU消費を最小限に抑えることが重要です。
コントロールは、上にマウスを置くと強調表示されます。これは、コントロールとの対話が可能であることを示します。ただし、すべてのコントロールが同時に使用可能で表示されているわけではありません。
- ドロップダウンリストやカレンダー、コンテキストメニューはほとんどの場合隠されています。それらは時折開かれ、ユーザーが必要なオプション、日付、またはモードを選択できるようにします。
- コントロールグループは異なるタブに割り当てることができますが、一度に開くことのできるタブは1つだけです。
- フォームが最小化されている場合、すべてのコントロールも非表示になります。
- ダイアログボックスが開いている場合、このフォームのみがイベント処理を行います。
論理的には、グラフィカルインターフェイスの一部のコントロールしか使用できないのであれば、コントロールのリスト全体を常に処理する必要はありません。開いたコントロールのリストに対してのみイベント処理の配列を生成すればいいのです。
また、コントロール自体にのみ影響を及ぼす相互作用を持つコントロールもあり、そのようなコントロールは、処理のために利用可能なままでなければならない唯一のコントロールです。これらのコントロールとケースを挙げてみましょう。
- スクロールバー(CScroll)のサムの移動:スクロールバー自体とその一部であるコントロール(リストビュー、テーブル、マルチラインテキストボックスなど)のみを処理できるようにする必要があります。
- スライダー (CSlider)のサムの移動:スライダーコントロールとスピンエディットボックスが有効であれば、値の変更が反映されます。
- テーブルの列の幅の変更 (CTable):処理のためにテーブルのみを使用可能にする必要があります。
- ツリービューコントロール (CTreeView)でのリストの幅の変更:隣接するリストの境界線をドラッグするときは、このコントロールのみを処理する必要があります。
- フォーム(CWindow)の移動:フォームが移動されている間を除き、すべてのコントロールは処理対象となりません。
挙げられたすべてのケースでは、コントロールはメッセージを送信し、このメッセージはライブラリコアで受信して処理される必要があります。コアは、コントロールの可用性 (ON_SET_AVAILABLE) を判断し、コントロールの配列を生成するための2つのイベント識別子を処理 (ON_CHANGE_GUI)します。すべてのイベント識別子はDefine.mqhファイルにあります。
//+------------------------------------------------------------------+ //| Defines.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ ... #define ON_CHANGE_GUI (28) // グラフィカルインターフェイスが変わった #define ON_SET_AVAILABLE (39) // 利用可能な項目を設定する ...
コントロールを表示/非表示にするにはShow() and Hide()メソッドを使用します。可用性を設定するための新しいプロパティがCElementクラスに追加されました。その値は、仮想パブリックメソッドCElement::IsAvailable()を使用して設定されます。ここでは、コントロールの状態を定義する他のメソッドと同様に、渡された値がネストされたコントロールにも設定されます。マウスの左ボタンクリックの優先度は、渡された状態に対して相対的に設定されます。コントロールが利用不可能にされる場合、優先度はリセットされます。
class CElement : public CElementBase { protected: bool m_is_available; // 可用性 //--- public: //--- 利用可能なコントロールの印 virtual void IsAvailable(const bool state) { m_is_available=state; } bool IsAvailable(void) const { return(m_is_available); } }; //+------------------------------------------------------------------+ //| コントロールの可用性 | //+------------------------------------------------------------------+ void CElement::IsAvailable(const bool state) { //--- 既に設定されている場合は終了する if(state==CElementBase::IsAvailable()) return; //--- 設定 CElementBase::IsAvailable(state); //--- 他のコントロール int elements_total=ElementsTotal(); for(int i=0; i<elements_total; i++) m_elements[i].IsAvailable(state); //--- 左マウスクリックの優先度を設定する if(state) SetZorders(); else ResetZorders(); }
例として、コンボボックスコントロール内のドロップダウンリストの表示を決定するCComboBox :: ChangeComboBoxListState>() メソッドのコードを次に示します。
コンボボックスボタンを押してリストビューを表示する必要がある場合、リストビューを表示した直後にON_SET_AVAILABLEという識別子を持つイベントが送信されます。追加のパラメータとして、 (1) コントロールの識別子と (2) tイベントハンドラの必要なアクションのフラグが渡されます。すべての可視コントロールが復元されるかイベントで指定された識別子を持つコントロールのみが使用可能にされます。1の値を持つフラグは復元に使用され、0の値は指定されたコントロールの可用性を設定します。
ON_SET_AVAILABLE識別子を持ったメッセージの後にはON_CHANGE_GUIイベント識別子を持ったメッセージが続きます。それを処理するには、現在利用可能なコントロールで配列を生成する必要があります。
//+------------------------------------------------------------------+ //| コンボボックスの現在の状態を変更する | //+------------------------------------------------------------------+ void CComboBox::ChangeComboBoxListState(void) { //--- ボタンが押された場合 if(m_button.IsPressed()) { //--- リストビューを表示する m_listview.Show(); //--- 利用可能なコントロールを判断するメッセージを送信する ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,""); //--- グラフィカルインターフェイスの変更に関するメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); } else { //--- リストビューを隠す m_listview.Hide(); //--- コントロールを復元するためのメッセージを送信する ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,""); //--- グラフィカルインターフェイスの変更に関するメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); } }
しかし、タブコントロールの場合、たとえばON_CHANGE_GUI識別子を持つ処理済みのイベントのうちの1つだけを処理するだけで十分です。特定のコントロールを利用できるようにする必要はありません。タブを切り替えると、表示状態はタブグループに割り当てられたコントロールに設定されます。 CTabsクラスでは、コントロールグループの可視性は、新しいバージョンのライブラリで変更されたCTabs::ShowTabElements() メソッドによって管理されます。タブ内にタブグループを配置する必要性がある場合があるため、選択されたタブのコントロールの表示によってそれらのうちの1つがCTabs型であることがわかっても、 CTabs::ShowTabElements()メソッドがこのコントールでもすぐに呼び出されます。この方法を使うと、ネストされたタブを任意のネストレベルで配置することができます。
//+------------------------------------------------------------------+ //| 選択されたタブのコントロールのみを表示する | //+------------------------------------------------------------------+ void CTabs::ShowTabElements(void) { //--- タブが非表示の場合は終了する if(!CElementBase::IsVisible()) return; //--- 選択されたタブのインデックスを確認する CheckTabIndex(); //--- uint tabs_total=TabsTotal(); for(uint i=0; i<tabs_total; i++) { //--- タブに取りつけられているコントロールの数を取得する int tab_elements_total=::ArraySize(m_tab[i].elements); //--- このタブが選択された場合 if(i==m_selected_tab) { //--- タブコントロールを表示する for(int j=0; j<tab_elements_total; j++) { //--- コントロールを表示する CElement *el=m_tab[i].elements[j]; el.Reset(); //--- これがタブコントロールの場合は、開いているコントロールのコントロールを表示する CTabs *tb=dynamic_cast<CTabs*>(el); if(tb!=NULL) tb.ShowTabElements(); } } //--- アクティブでないタブのコントロールを隠す else { for(int j=0; j<tab_elements_total; j++) m_tab[i].elements[j].Hide(); } } //--- 関連したメッセージを送信する ::EventChartCustom(m_chart_id,ON_CLICK_TAB,CElementBase::Id(),m_selected_tab,""); }
選択したタブのコントロールが表示されると、メソッドはグラフィカルインターフェイスが変更されたことを示すメッセージを送信します 。ここで処理のために使用可能なコントロールの配列を生成する必要があります。
//+------------------------------------------------------------------+ //| グループでのタブの押下 | //+------------------------------------------------------------------+ bool CTabs::OnClickTab(const int id,const int index) { //--- (1) 識別子が一致しない場合 や (2) コントロールがロックされている場合は終了する if(id!=CElementBase::Id() || CElementBase::IsLocked()) return(false); //--- 識別子が一致しない場合は終了する if(index!=m_tabs.SelectedButtonIndex()) return(true); //--- 選択されたタブのインデックスを格納する SelectedTab(index); //--- コントロールを再描画する Reset(); Update(true); //--- 選択されたタブのコントロールのみを表示する ShowTabElements(); //--- グラフィカルインターフェイスの変更に関するメッセージを送信する ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0.0,""); return(true); }
イベントを生成するための2つの新しい識別子がDefines.mqhファイルに追加されました。
- ON_MOUSE_FOCUS — マウスカーソルがコントロールの領域に入った
- ON_MOUSE_BLUR — マウスカーソルがコントロールの領域を離れた
... #define ON_MOUSE_BLUR (34) // マウスカーソルがコントロールの領域を離れた #define ON_MOUSE_FOCUS (35) // マウスカーソルがコントロールの領域に入った ...
これらのイベントは、コントロールの境界線を超えた場合にのみ生成されます。コントロールの基本クラス(CElementBase) には、マウスカーソルがコントロール領域の境界を横切る瞬間を特定するCElementBase::CheckCrossingBorder()メソッドが含まれています。 これに上記のイベントの発生を補足しましょう。
//+------------------------------------------------------------------+ //| コントロールの境界の交差の確認 | //+------------------------------------------------------------------+ bool CElementBase::CheckCrossingBorder(void) { //--- これがコントロールの境界線を越える瞬間である場合 if((MouseFocus() && !IsMouseFocus()) || (!MouseFocus() && IsMouseFocus())) { IsMouseFocus(MouseFocus()); //--- 境界線を超えてコントロールの領域に入ったことに関するメッセージ if(MouseFocus()) ::EventChartCustom(m_chart_id,ON_MOUSE_FOCUS,m_id,m_index,m_class_name); //--- 境界線を超えてコントロールの領域から出たことに関するメッセージ else ::EventChartCustom(m_chart_id,ON_MOUSE_BLUR,m_id,m_index,m_class_name); //--- return(true); } //--- return(false); }
現在のバージョンのライブラリでは、これらのイベントはメインメニュー (CMenuBar)でのみ処理されます。仕組みを見てみましょう。
メインメニューを作成して保存すると、その項目(CMenuItem) は独立したコントロールとしてストレージリストに分類されます。CMenuItemクラスはCButton(ボタンコントロール)から派生しています。したがって、メニュー項目のイベントハンドラへの呼び出しは、CButton基本クラスのイベントハンドラの呼び出しで始まります。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CMenuItem::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- 基本クラスのイベントを処理する CButton::OnEvent(id,lparam,dparam,sparam); ... }
基本イベントハンドラにはすでにボタン交差のトラッキングが含まれているため、CMenuItem派生クラスで複製する必要はありません。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- マウス移動イベントを処理する if(id==CHARTEVENT_MOUSE_MOVE) { //--- 境界線が超えられたらコントロールを再描画する if(CheckCrossingBorder()) Update(true); //--- return; } ... }
カーソルがボタン領域内の境界線を超えるとON_MOUSE_FOCUS 識別子を持つイベントが生成されます。CMenuBarクラスのイベントハンドラーは、メインメニューコントロールがアクティブになったときにこのメインイベントを使用してコンテキストメニューを切り替えます。
//+------------------------------------------------------------------+ //| イベントハンドラ | //+------------------------------------------------------------------+ void CMenuBar::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //---メニュー項目のフォーカスを変更するイベントの処理 if(id==CHARTEVENT_CUSTOM+ON_MOUSE_FOCUS) { //--- (1) メインメニューがアクティブにされていない場合 や (2) 識別子が一致しない場合は終了する if(!m_menubar_state || lparam!=CElementBase::Id()) return; //--- メインメニューのアクティブ項目によってコンテキストメニューを変える SwitchContextMenuByFocus(); return; } ... }
ライブラリコアの最適化
ライブラリの中核とも言えるCWndContainerクラスとCWndEventsクラスに加えられた変更、修正、追加について考えてみましょう。結局のところ、これらはすべてのコントロールへのアクセスを構成し、グラフィカルインターフェイスのコントロールによって生成されるイベントフローを処理します。
CWndContainer::ResizeArray()テンプレートメソッドが、配列を操作するためのCWndContainer クラスに追加されました。このメソッドに渡される任意の型の配列は1要素だけ増加し、メソッドは最後の要素のインデックスを返します。
//+------------------------------------------------------------------+ //| インタフェースのオブジェクト全てを格納するクラス | //+------------------------------------------------------------------+ class CWndContainer { private: //--- 配列を1要素増加して、最後のインデックスを返す template<typename T> int ResizeArray(T &array[]); }; //+------------------------------------------------------------------+ //| 配列を1要素増加させ、最後のインデックスを返す | //+------------------------------------------------------------------+ template<typename T> int CWndContainer::ResizeArray(T &array[]) { int size=::ArraySize(array); ::ArrayResize(array,size+1,RESERVE_SIZE_ARRAY); return(size); }
CWndContainerクラス(グラフィカルインターフェイスのすべてのコントロールへのポインタの格納)の多くのコントロールのWindowElements構造体でプライベート配列が宣言されていることを思い出してください。このリストから特定の型のコントロールの数を取得するために、汎用の CWndContainer::ElementsTotal()メソッドが実装されています。ウィンドウのインデックスとコントロールのタイプを渡して、MQLアプリケーションのグラフィカルインタフェースでその番号を取得します。 コントロールの型を指定するための、新しいENUM_ELEMENT_TYPE列挙型がEnums.mqhファイルに追加されました。
//+------------------------------------------------------------------+ //| コントロール型の列挙体 | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE { E_CONTEXT_MENU =0, E_COMBO_BOX =1, E_SPLIT_BUTTON =2, E_MENU_BAR =3, E_MENU_ITEM =4, E_DROP_LIST =5, E_SCROLL =6, E_TABLE =7, E_TABS =8, E_SLIDER =9, E_CALENDAR =10, E_DROP_CALENDAR =11, E_SUB_CHART =12, E_PICTURES_SLIDER =13, E_TIME_EDIT =14, E_TEXT_BOX =15, E_TREE_VIEW =16, E_FILE_NAVIGATOR =17, E_TOOLTIP =18 };
CWndContainer::ElementsTotal()メソッドのコードは下に示されています。
//+------------------------------------------------------------------+ //| 指定されたウィンドウインデックスで指定された型のコントロールの数 | //+------------------------------------------------------------------+ int CWndContainer::ElementsTotal(const int window_index,const ENUM_ELEMENT_TYPE type) { //--- 配列の範囲が超えられているかどうかの確認 int index=CheckOutOfRange(window_index); if(index==WRONG_VALUE) return(WRONG_VALUE); //--- int elements_total=0; //--- switch(type) { case E_CONTEXT_MENU : elements_total=::ArraySize(m_wnd[index].m_context_menus); break; case E_COMBO_BOX : elements_total=::ArraySize(m_wnd[index].m_combo_boxes); break; case E_SPLIT_BUTTON : elements_total=::ArraySize(m_wnd[index].m_split_buttons); break; case E_MENU_BAR : elements_total=::ArraySize(m_wnd[index].m_menu_bars); break; case E_MENU_ITEM : elements_total=::ArraySize(m_wnd[index].m_menu_items); break; case E_DROP_LIST : elements_total=::ArraySize(m_wnd[index].m_drop_lists); break; case E_SCROLL : elements_total=::ArraySize(m_wnd[index].m_scrolls); break; case E_TABLE : elements_total=::ArraySize(m_wnd[index].m_tables); break; case E_TABS : elements_total=::ArraySize(m_wnd[index].m_tabs); break; case E_SLIDER : elements_total=::ArraySize(m_wnd[index].m_sliders); break; case E_CALENDAR : elements_total=::ArraySize(m_wnd[index].m_calendars); break; case E_DROP_CALENDAR : elements_total=::ArraySize(m_wnd[index].m_drop_calendars); break; case E_SUB_CHART : elements_total=::ArraySize(m_wnd[index].m_sub_charts); break; case E_PICTURES_SLIDER : elements_total=::ArraySize(m_wnd[index].m_pictures_slider); break; case E_TIME_EDIT : elements_total=::ArraySize(m_wnd[index].m_time_edits); break; case E_TEXT_BOX : elements_total=::ArraySize(m_wnd[index].m_text_boxes); break; case E_TREE_VIEW : elements_total=::ArraySize(m_wnd[index].m_treeview_lists); break; case E_FILE_NAVIGATOR : elements_total=::ArraySize(m_wnd[index].m_file_navigators); break; case E_TOOLTIP : elements_total=::ArraySize(m_wnd[index].m_tooltips); break; } //--- 指定されたウィンドウインデックスのコントロール数を返す return(elements_total); }
CPUの負荷を軽減するには、WindowElements構造体にいくつかの配列を追加する必要がありました。これは、以下のカテゴリのコントロールのポインタを格納します。
- メインコントロールの配列
- タイマー付きコントロールの配列
- 可視で処理可能なコントロールの配列
- X軸に沿って自動サイズ変更が有効になっているコントロールの配列
- Y軸に沿って自動サイズ変更が有効になっているコントロールの配列
class CWndContainer { protected: ... //--- コントロール配列の構造体 struct WindowElements { ... //--- メインコントロールの配列 CElement *m_main_elements[]; //--- タイマー付きコントロール CElement *m_timer_elements[]; //--- 可視で利用可能なコントロール CElement *m_available_elements[]; //--- X軸に沿った自動サイズ変更が有効になっているコントロール CElement *m_auto_x_resize_elements[]; //--- Y軸に沿った自動サイズ変更が有効になっているコントロール CElement *m_auto_y_resize_elements[]; ... }; //--- 各ウィンドウのコントロールの配列の配列 WindowElements m_wnd[]; ... };
これらの配列のサイズは、適切な方法によって得られます。
class CWndContainer { public: //--- メインコントロールの数 int MainElementsTotal(const int window_index); //--- タイマー付きコントロールの数 int TimerElementsTotal(const int window_index); //--- X軸に沿った自動サイズ変更が有効になっているコントロールの数 int AutoXResizeElementsTotal(const int window_index); //--- Y軸に沿った自動サイズ変更が有効になっているコントロールの数 int AutoYResizeElementsTotal(const int window_index); //--- 現在利用できるコントロールの数 int AvailableElementsTotal(const int window_index); };
CWndContainer::AddToElementsArray()メソッドは、メインコントロールの配列へのポインタを追加します。下記はメソッドを短縮したものです。
//+------------------------------------------------------------------+ //| コントロール配列にポインタを追加する | //+------------------------------------------------------------------+ void CWndContainer::AddToElementsArray(const int window_index,CElementBase &object) { ... //--- メインコントロールの配列に追加する last_index=ResizeArray(m_wnd[window_index].m_main_elements); m_wnd[window_index].m_main_elements[last_index]=::GetPointer(object); ... }
他の分類の配列はCWndEventsクラスで生成されます(以下参照)。ポインタの追加には、別々のメソッドが使用されています。
class CWndContainer { protected: //--- タイマー付きコントロールの配列にポインタを追加する void AddTimerElement(const int window_index,CElement &object); //--- X軸に沿った自動サイズ変更が有効になっているコントロールの配列にポインタを追加する void AddAutoXResizeElement(const int window_index,CElement &object); //--- Y軸に沿った自動サイズ変更が有効になっているコントロールの配列にポインタを追加する void AddAutoYResizeElement(const int window_index,CElement &object); //--- 現在使用できるコントロールの配列にポインタを追加する void AddAvailableElement(const int window_index,CElement &object); }; //+------------------------------------------------------------------+ //| タイマー付きコントロールの配列にポインタを追加する | //+------------------------------------------------------------------+ void CWndContainer::AddTimerElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_timer_elements); m_wnd[window_index].m_timer_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| 自動サイズ変更(X)を使用するコントロールの配列にポインタを追加する | //+------------------------------------------------------------------+ void CWndContainer::AddAutoXResizeElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_auto_x_resize_elements); m_wnd[window_index].m_auto_x_resize_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| 自動サイズ変更(Y) を使用するコントロールの配列にポインタを追加する | //+------------------------------------------------------------------+ void CWndContainer::AddAutoYResizeElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_auto_y_resize_elements); m_wnd[window_index].m_auto_y_resize_elements[last_index]=::GetPointer(object); } //+------------------------------------------------------------------+ //| 現在使用できるコントロールの配列にポインタを追加する | //+------------------------------------------------------------------+ void CWndContainer::AddAvailableElement(const int window_index,CElement &object) { int last_index=ResizeArray(m_wnd[window_index].m_available_elements); m_wnd[window_index].m_available_elements[last_index]=::GetPointer(object); }
CWndEventsクラスには新しい内部使用のためのメソッドもあります。したがって、グラフィカルインターフェイスのすべてのコントロールを非表示にするには CWndEvents::Hide()メソッドが必要です。ここでは2回のループが使用されます。最初にフォームは隠され、2番目のループでフォームに取り付けられたコントロールを隠します。このメソッドでは、2番目のループがメインコントロールへのポインタで構成されるコントロールの配列を反復処理することに注意してください。コントロールのHide()およびShow() メソッドは、ネストされたコントロール内でこれらのメソッドのチェーン全体に影響するように配置されるようになりました。
//+------------------------------------------------------------------+ //| イベント処理のクラス | //+------------------------------------------------------------------+ class CWndEvents : public CWndContainer { protected: //--- すべてのコントロールを非表示にする void Hide(); }; //+------------------------------------------------------------------+ //| コントロールを非表示にする | //+------------------------------------------------------------------+ void CWndEvents::Hide(void) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { m_windows[w].Hide(); int main_total=MainElementsTotal(w); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[w].m_main_elements[e]; el.Hide(); } } }
ここには、指定したフォームでコントロールを表示するための新しいCWndEvents::Show()メソッドもあります。引数で指定されたウィンドウが初めに表示され、次に、ウィンドウが最小化されていない場合、このフォームに取り付けられているすべてのコントロールが表示されます。このループでは、 (1) ドロップダウン、 (2) タブコントロールをメインコントロールとして指定したコントロールだけをスキップします。タブ内のコントロールは、後にループ外でCWndEvents::ShowTabElements()メソッドを使用して表示されます。
class CWndEvents : public CWndContainer { protected: //--- 指定されたウィンドウのコントロールを表示する void Show(const uint window_index); }; //+------------------------------------------------------------------+ //| 指定されたウィンドウのコントロールを表示する | //+------------------------------------------------------------------+ void CWndEvents::Show(const uint window_index) { //--- 指定されたウィンドウのコントロールを表示する m_windows[window_index].Show(); //--- ウィンドウが最小化されていない場合 if(!m_windows[window_index].IsMinimized()) { int main_total=MainElementsTotal(window_index); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; //--- (1) ドロップダウンでなく (2) メインコントロールがタブではない場合はコントロールを表示する if(!el.IsDropdown() && dynamic_cast<CTabs*>(el.MainPointer())==NULL) el.Show(); } //--- 選択されたタブのコントロールのみを表示する ShowTabElements(window_index); } }
MQLアプリケーションのグラフィカルインタフェースのすべてのコントロールを再描画するにはCWndEvents::Update()メソッドが必要です。このメソッドは、 (1) すべてのコントロールを完全に再描画する、または (2) 以前に行った変更を適用する、の2つのモードで動作できます。グラフィカルインターフェイスを完全に再描画して更新するにはtrue値を渡す必要があります。
class CWndEvents : public CWndContainer { protected: //--- コントロールを再描画する void Update(const bool redraw=false); }; //+------------------------------------------------------------------+ //| コントロールを再描画する | //+------------------------------------------------------------------+ void CWndEvents::Update(const bool redraw=false) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { //--- コントロールを再描画する int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[w].m_elements[e]; el.Update(redraw); } } }
これらのメソッドについては後ほど説明します。今は、前述のカテゴリの配列を生成するために設計されたいくつかのメソッドを考えてみましょう。
以前のバージョンでは、マウスカーソルでマウスを動かすと、コントロールの色がタイマーベースで徐々に変更されていました。この余分な機能は、コード量を減らしてリソースの消費を減らすために削除されました。したがって、タイマーはライブラリの現在のバージョンのすべてのコントロールでは使用されず、 (1) スクロールバーのサム、 (2) スピンエディットボックスの値 と (3) カレンダー内の日付の高速スクロールにのみ存在します。したがって、CWndEvents::FormTimerElementsArray()メソッドに追加されるのは適切なコントロールだけです(下記コード参照)。
コントロールへのポインタは基本型のコントロール (CElement)の配列に格納されているため、ここではコントロールの派生型を特定するためのクラスの他の多くのメソッド同様、動的型キャスト(dynamic_cast) が使用されます。
class CWndEvents : public CWndContainer { protected: //--- タイマー付きコントロールの配列を作成する void FormTimerElementsArray(void); }; //+------------------------------------------------------------------+ //| タイマー付きコントロールの配列を作成する | //+------------------------------------------------------------------+ void CWndEvents::FormTimerElementsArray(void) { int windows_total=CWndContainer::WindowsTotal(); for(int w=0; w<windows_total; w++) { int elements_total=CWndContainer::ElementsTotal(w); for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[w].m_elements[e]; //--- if(dynamic_cast<CCalendar *>(el)!=NULL || dynamic_cast<CColorPicker *>(el)!=NULL || dynamic_cast<CListView *>(el)!=NULL || dynamic_cast<CTable *>(el)!=NULL || dynamic_cast<CTextBox *>(el)!=NULL || dynamic_cast<CTextEdit *>(el)!=NULL || dynamic_cast<CTreeView *>(el)!=NULL) { CWndContainer::AddTimerElement(w,el); } } } }
タイマーははるかに単純になり、コントロールのリスト全体の代わりにこの機能を含むものだけを確認するようになりました。
//+------------------------------------------------------------------+ //| 全てのコントロールのイベントのタイマーでのチェック | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEventsTimer(void) { int awi=m_active_window_index; int timer_elements_total=CWndContainer::TimerElementsTotal(awi); for(int e=0; e<timer_elements_total; e++) { CElement *el=m_wnd[awi].m_timer_elements[e]; if(el.IsVisible()) el.OnEventTimer(); } }
マウスオーバーイベントの処理は、グラフィカルインターフェイスの特定のコントロールにのみ必要です。イベントを処理するために使用できる以下のコントロールの配列は除外されています。
- CButtonsGroup — ボタングループ
- CFileNavigator — ファイルナビゲータ
- CLineGraph — 線チャート
- CPicture — 写真
- CPicturesSlider — ピクチャスライダー
- CProgressBar — プログレスバー
- CSeparateLine — 区切り線
- CStatusBar — ステータスバー
- CTabs — タブ
- CTextLabel — テキストラベル
これらのコントロールは、マウスでホバーしても強調表示されません。ただし、それらの一部では、ネストされたコントロールが強調表示されます。しかし、使用可能なコントロールの配列を形成するループでは共通の配列が使用されるため、ネストされたコントロールも選択に参加します。配列は可視、利用可能、またロックされていないすべてのコントロールを選択します。
class CWndEvents : public CWndContainer { protected: //--- 利用可能なコントロールの配列を作成する void FormAvailableElementsArray(void); }; //+------------------------------------------------------------------+ //| 利用可能なコントロールの配列を作成する | //+------------------------------------------------------------------+ void CWndEvents::FormAvailableElementsArray(void) { //--- ウィンドウインデックス int awi=m_active_window_index; //--- コントロールの総数 int elements_total=CWndContainer::ElementsTotal(awi); //--- 配列をクリアする ::ArrayFree(m_wnd[awi].m_available_elements); //--- for(int e=0; e<elements_total; e++) { CElement *el=m_wnd[awi].m_elements[e]; //--- 可視で処理可能なコントロールのみを追加する if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked()) continue; //--- マウスオーバーイベントを処理する必要のないコントロールを除外する if(dynamic_cast<CButtonsGroup *>(el)==NULL && dynamic_cast<CFileNavigator *>(el)==NULL && dynamic_cast<CLineGraph *>(el)==NULL && dynamic_cast<CPicture *>(el)==NULL && dynamic_cast<CPicturesSlider *>(el)==NULL && dynamic_cast<CProgressBar *>(el)==NULL && dynamic_cast<CSeparateLine *>(el)==NULL && dynamic_cast<CStatusBar *>(el)==NULL && dynamic_cast<CTabs *>(el)==NULL && dynamic_cast<CTextLabel *>(el)==NULL) { AddAvailableElement(awi,el); } } }
後はCWndEvents::FormAutoXResizeElementsArray()メソッドとCWndEvents::FormAutoYResizeElementsArray()メソッドを考察するだけです。これらは自動サイズ変更モードが有効になっているコントロールへのポインタを持つ配列を生成します。 このようなコントロールは、それらが接続されているメインコントロールのサイズに従います。すべてのコントロールが自動サイズ変更のメソッドコードを持っているわけではありません。以下はそれを持っているものです。
CElement::ChangeWidthByRightWindowSide()仮想メソッドで幅を自動サイズ変更するために定義されたコードを持つコントロール:
- CButton — ボタン
- CFileNavigator — ファイルナビゲータ
- CLineGraph — 線チャート
- CListView — リストビュー
- CMenuBar — メインメニュー
- CProgressBar — プログレスバー
- CStandardChart — 標準チャート
- CStatusBar — ステータスバー
- CTable — テーブル
- CTabs — タブ
- CTextBox — テキストエディットボックス
- CTextEdit — エディットボックス
- CTreeView — ツリービュー
CElement::ChangeHeightByBottomWindowSide()仮想メソッドで高さを自動サイズ変更するために定義されたコードを持つコントロール:
- CLineGraph — 線チャート
- CListView — リストビュー
- CStandardChart — 標準チャート
- CTable — テーブル
- CTabs — タブ
- CTextBox — テキストエディットボックス
これらのカテゴリの配列を作成するときは、これらのコントロールで自動サイズ変更モードが有効になっているかどうかが確認され、有効の場合にはコントロールが配列に追加されます。同様のメソッドについてすでにお話ししたので、これらのコードは考慮されません。
上記のカテゴリの配列がいつ生成されるかを見てみましょう。グラフィックインターフェイスを作成する主要メソッド(ユーザーが独自に作成する)では、指定したすべてのコントロールが正常に作成されたならば、それらをチャートに表示するにはCWndEvents::CompletedGUI()メソッドを1つだけ呼び出す必要があります。 これは、MQLアプリケーションのグラフィカルインタフェースの作成が完了したことを通知します。
CWndEvents::CompletedGUI()メソッドを詳しく見てみましょう。これは、このセクションの前半で説明したすべてのメソッドを呼び出します。まず、グラフィカルインターフェースのすべてのコントロールが隠されます。レンダリングはまだ一つも行われていないので、徐々に逐次出現するのを避けるために、レンダリングする前に隠す必要があるのです。次に、レンダリング自体が行われ、最新の変更が各コントロールに適用されます。その後はメインウィンドウのコントロールのみを表示する必要があります。次に、カテゴリ別のコントロールへのポインタの配列が生成されます。チャートはメソッドの終わりで更新されます。
class CWndEvents : public CWndContainer { protected: //--- GUI作成の完了 void CompletedGUI(void); }; //+------------------------------------------------------------------+ //| GUI作成の完了 | //+------------------------------------------------------------------+ void CWndEvents::CompletedGUI(void) { //--- ウィンドウがまだない場合は終了する int windows_total=CWndContainer::WindowsTotal(); if(windows_total<1) return; //--- ユーザーに通知するコメントを表示する ::Comment("Update. Please wait..."); //--- コントロールを隠す Hide(); //--- コントロールを描画する Update(true); //--- アクティブにされたウィンドウのコントロールを表示する Show(m_active_window_index); //--- タイマー付きコントロールの配列を作成する FormTimerElementsArray(); //--- 可視および利用可能なコントロールの配列を作成する FormAvailableElementsArray(); //--- 自動サイズ変更を使用するコントロールの配列を作成する FormAutoXResizeElementsArray(); FormAutoYResizeElementsArray(); //--- チャートを再描画する m_chart.Redraw(); //--- コメントをクリアする ::Comment(""); }
コントロールのイベントをチェックして処理するためのCWndEvents::CheckElementsEvents()メソッドが大幅に変更されました。これをもっと詳しく考察していきましょう。
このメソッドには、2つのイベント処理ブロックがあって、そのうち1つは、マウスカーソルの動きを処理するためにのみ設計されています (CHARTEVENT_MOUSE_MOVE)。以前のようにアクティブになったウィンドウのすべてのコントロールのリストをループするのではなく、ループは処理のために利用可能なコントロールを反復するだけです。これが、利用可能なコントロールへのポインタを持つ配列が最初に生成された理由です。大規模なMQLアプリケーションのグラフィカルインタフェースには数百から数千ものコントロールが含まれていたとしても、一度に可視で利用可能なのはそのうちのわずかです。このアプローチは、CPUリソースを大幅に節約します。
もう1つの変更点は、(1) フォームがあるサブウィンドウの確認と (2) コントロール上のフォーカスの確認が外部ループで実行され、各クラスのコントロールのハンドラには含まれないことです。したがって、各コントロールに関連する確認は、同じ場所に配置されるようになりました。これは、将来的にイベント処理アルゴリズムを変更する必要がある場合に便利です。
他のすべての種類のイベントは、別のブロックで処理されます。現在のバージョンでは、グラフィカルインターフェイスのコントロールのリスト全体が反復処理されます。以前にコントロールのクラスに含まれていたすべての確認も、外部ループに移動されました。
メソッドの最後には、イベントがMQLアプリケーションのカスタムクラスに送信されます。
//+------------------------------------------------------------------+ //| コントロールイベントのチェック | //+------------------------------------------------------------------+ void CWndEvents::CheckElementsEvents(void) { //--- マウスカーソル移動イベントの処理 if(m_id==CHARTEVENT_MOUSE_MOVE) { //--- フォームが別のサブウィンドウにある場合は終了する if(!m_windows[m_active_window_index].CheckSubwindowNumber()) return; //--- 使用可能なコントロールのみを確認する int available_elements_total=CWndContainer::AvailableElementsTotal(m_active_window_index); for(int e=0; e<available_elements_total; e++) { CElement *el=m_wnd[m_active_window_index].m_available_elements[e]; //--- コントロール上のフォーカスの確認 el.CheckMouseFocus(); //--- イベント処理 el.OnEvent(m_id,m_lparam,m_dparam,m_sparam); } } //--- マウスカーソル移動以外のすべてのイベント else { int elements_total=CWndContainer::ElementsTotal(m_active_window_index); for(int e=0; e<elements_total; e++) { //--- 使用可能なコントロールのみを確認する CElement *el=m_wnd[m_active_window_index].m_elements[e]; if(!el.IsVisible() || !el.IsAvailable() || el.IsLocked()) continue; //--- コントロールのイベントハンドラでのイベント処理 el.OnEvent(m_id,m_lparam,m_dparam,m_sparam); } } //--- イベントをアプリケーションファイルに転送する OnEvent(m_id,m_lparam,m_dparam,m_sparam); }
以下の場合には、処理のために可視で同時に利用可能なコントロールの配列を生成するためのCWndEvents::FormAvailableElementsArray() メソッドが呼び出されます。
- ダイアログウィンドウを開く。ダイアログボックスが開かれるとON_OPEN_DIALOG_BOXイベントが生成されて、CWndEvents::OnOpenDialogBox()メソッドで処理されます。イベントの処理後は、開いているウィンドウで使用可能なコントロールの配列を生成する必要があります。
- グラフィカルインターフェースでの変更。インタラクションによって引き起こされるグラフィカルインタフェースの変化はON_CHANGE_GUIイベントを生成します。これは、新しいprivateメソッドのCWndEvents::OnChangeGUI()によって処理されます。ここでは、 ON_CHANGE_GUIイベントが到着すると、最初に利用可能なコントロールの配列が生成され、それからすべてのツールヒントが最上位レイヤに移動されます。メソッドの最後には、チャートが再描画されて最新の変更が表示されます。
class CWndEvents : public CWndContainer { private: //--- グラフィカルインターフェースでの変更 bool OnChangeGUI(void); }; //+------------------------------------------------------------------+ //| グラフィカルインターフェース変更イベント | //+------------------------------------------------------------------+ bool CWndEvents::OnChangeGUI(void) { //--- シグナルがグラフィカルインターフェースの変更に関するものである場合 if(m_id!=CHARTEVENT_CUSTOM+ON_CHANGE_GUI) return(false); //--- 可視および利用可能なコントロールの配列を作成する FormAvailableElementsArray(); //--- ツールヒントを最上位例やに移動する ResetTooltips(); //--- チャートを再描画する m_chart.Redraw(); return(true); }
次に、処理に使用できるコントロールを特定するON_SET_AVAILABLE識別子を持つイベントの処理について検討します。
ON_SET_AVAILABLEイベントを処理するためのCWndEvents::OnSetAvailable()メソッドが実装されています。しかし、コードの記述に進む前に、いくつかの補助メソッドを検討する必要があります。このような識別子を持つイベントを生成するグラフィカルインタフェースコントロールは10あり、そのすべてに、活動状態を決定するメソッドがあります。以下でそれらを名付けます。
- メインメニュー — CMenuBar::State()
- メニュー項目 — CMenuItem::GetContextMenuPointer().IsVisible()
- スプリットボタン — CSplitButton::GetContextMenuPointer().IsVisible()
- コンボボックス — CComboBox::GetListViewPointer().IsVisible()
- ドローダウンカレンダー — DropCalendar::GetCalendarPointer().IsVisible()
- スクロールバー — CScroll::State()
- テーブル — CTable::ColumnResizeControl()
- 数値スライダー — CSlider::State()
- ツリービュー — CTreeView::GetMousePointer().State()
- 標準チャート— CStandartChart::GetMousePointer().IsVisible()
これらのコントロールのそれぞれは、 CWndContainerクラスにプライベート配列を持っています。CWndEventsクラスには、現在どのコントロールがアクティブ化されているかを判断するメソッドが実装されています。これらのメソッドはすべて、プライベート配列内のアクティブになったコントロールのインデックスを返します。
class CWndEvents : public CWndContainer { private: //--- アクティブにされたメインメニューのインデックスを返す int ActivatedMenuBarIndex(void); //--- アクティブにされたメニュー項目のインデックスを返す int ActivatedMenuItemIndex(void); //--- アクティブにされたスプリットボタンのインデックスを返す int ActivatedSplitButtonIndex(void); //--- アクティブにされたコンボボックスのインデックスを返す int ActivatedComboBoxIndex(void); //--- アクティブにされたドロップダウンカレンダーのインデックスを返す int ActivatedDropCalendarIndex(void); //--- アクティブにされたスクロールバーのインデックスを返す int ActivatedScrollIndex(void); //--- アクティブにされたテーブルのインデックスを返す int ActivatedTableIndex(void); //--- アクティブにされたスライダーのインデックスを返す int ActivatedSliderIndex(void); //--- アクティブにされたツリービューのインデックスを返す int ActivatedTreeViewIndex(void); //--- アクティブにされたサブチャートのインデックスを返す int ActivatedSubChartIndex(void); };
これらのメソッドの唯一の違いはコントロールの状態を特定するための条件にあるため、そのうちの1つのコードだけが考慮されます。下記は、起動されたツリービューのインデックスを返すCWndEvents::ActivatedTreeViewIndex()メソッドのコードを示しています。このタイプのコントロールでタブ項目モードが有効になっている場合は、確認が拒否されます。
//+------------------------------------------------------------------+ //| アクティブにされたツリービューのインデックスを返す | //+------------------------------------------------------------------+ int CWndEvents::ActivatedTreeViewIndex(void) { int index=WRONG_VALUE; //--- int total=ElementsTotal(m_active_window_index,E_TREE_VIEW); for(int i=0; i<total; i++) { CTreeView *el=m_wnd[m_active_window_index].m_treeview_lists[i]; //--- タブモードが有効な場合は次に移る if(el.TabItemsMode()) continue; //--- リストの幅を変更している最中の場合 if(el.GetMousePointer().State()) { index=i; break; } } return(index); }
CWndEvents::SetAvailable()メソッドはコントロールの可用性状態を設定するために設計されています。引数としては、(1) 必要なフォームインデックスと(2) コントロールに設定する状態を渡す必要があります。
すべてのコントロールを使えないようにする必要がある場合は、単にループで反復してfalse値を設定します。
コントロールを使えるようにする必要がある場合は、ツリービューの同じ名前でオーバーロードされたCTreeView :: IsAvailable()メソッドが呼び出されます。 これは、(1) メインコントロールのみか (2)すべてのネストレベルでのすべてのコントロールの2つのオードで状態を設定することができます。したがって、ここでは、動的型キャストを使用して、 派生コントロールクラスのコントロールのポインタを取得します。
class CWndEvents : public CWndContainer { protected: //--- コントロールの利用可能状態を設定する void SetAvailable(const uint window_index,const bool state); }; //+------------------------------------------------------------------+ //| コントロールの利用可能状態を設定する | //+------------------------------------------------------------------+ void CWndEvents::SetAvailable(const uint window_index,const bool state) { //--- メインコントロール数を取得する int main_total=MainElementsTotal(window_index); //--- コントロールが利用不可能にする必要がある場合 if(!state) { m_windows[window_index].IsAvailable(state); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; el.IsAvailable(state); } } else { m_windows[window_index].IsAvailable(state); for(int e=0; e<main_total; e++) { CElement *el=m_wnd[window_index].m_main_elements[e]; //--- ツリービューの場合 if(dynamic_cast<CTreeView*>(el)!=NULL) { CTreeView *tv=dynamic_cast<CTreeView*>(el); tv.IsAvailable(true); continue; } //--- ファイルナビゲータの場合 if(dynamic_cast<CFileNavigator*>(el)!=NULL) { CFileNavigator *fn =dynamic_cast<CFileNavigator*>(el); CTreeView *tv =fn.GetTreeViewPointer(); fn.IsAvailable(state); tv.IsAvailable(state); continue; } //--- コントロールを利用可能にする el.IsAvailable(state); } } }
コンテキストメニューを持つメニュー項目にはメソッドが必要でした。このメソッドは、開いているコンテキストメニューの全体をループしてアクセスすることができます。この場合、コンテキストメニューを処理可能にする必要があります。これは再帰的に実装されます。
以下はCWndEvents::CheckContextMenu()メソッドのコードです。最初に、メニュー項目タイプのオブジェクトを渡し、コンテキストメニューへのポインタの取得を試みます。ポインタが正しい場合は、このコンテキストメニューが開いているかを確認します。開いている場合は、可用性フラグをそれに設定します。次に、ループでこのメニューのすべての項目に可用性ステータスを設定します。同時に、 コンテキストメニューがある場合はCWndEvents::CheckContextMenu()メソッドを使用して、各項目をチェックします。
class CWndEvents : public CWndContainer { private: //---確認してコンテキストメニューを使用可能にする void CheckContextMenu(CMenuItem &object); }; //+------------------------------------------------------------------+ //| 再帰的に確認し、コンテキストメニューを利用可能にする | //+------------------------------------------------------------------+ void CWndEvents::CheckContextMenu(CMenuItem &object) { //--- コンテキストメニューのポインタを取得する CContextMenu *cm=object.GetContextMenuPointer(); //--- 項目にコンテキストメニューがない場合は終了する if(::CheckPointer(cm)==POINTER_INVALID) return; //--- コンテキストメニューがあるが非表示の場合は終了する if(!cm.IsVisible()) return; //--- 使用可能コントロールの印を設定する cm.IsAvailable(true); //--- int items_total=cm.ItemsTotal(); for(int i=0; i<items_total; i++) { //--- 使用可能コントロールの印を設定する CMenuItem *mi=cm.GetItemPointer(i); mi.IsAvailable(true); //--- 項目にコンテキストメニューがあるかどうかを確認する CheckContextMenu(mi); } }
ここで、指標可能なコントロールを特定するためのイベントを処理するCWndEvents::OnSetAvailable()メソッドについて考察しましょう。
ON_SET_AVAILABLE識別子を持つカスタムイベントが受信された場合は、まず現在アクティブなコントロールがあるかどうかを判断する必要があります。プライベート配列にすばやくアクセスできるように、アクティブになったコントロールのインデックスをローカル変数に格納します。
使用可能なコントロールを特定するシグナルが受信された場合は、最初にリスト全体のアクセスを無効にします。復元シグナルの場合、アクティブになったコントロールがないことを確認した後にアクセスがリスト全体で復元され、プログラムはメソッドを終了します。
でプログラムがこのメソッド内の次のコードブロックに到達すると、これは (1) 使用可能なコントロールを判別するシグナルか(2) 復元シグナルのいずれかであることを意味しますが、アクティブなドロップダウンカレンダーがあることを意味します。2番目の状況は、ドロップダウンリストが閉じられているコンボボックスがアクティブになったドロップダウンカレンダーを開くと発生することがあります。
上記のいずれかの条件が満たされている場合は、アクティブになったコントロールへのポインタの取得を試みます。ポインタが受信されなかった場合、プログラムはメソッドを終了します。。
ポインタが得られた場合、コントロールは利用可能になります。いくつかのコントロールには、これがどのように行われているかのニュアンスがあります。以下はそのすべてのケースです。
- メインメニュー (CMenuBar):アクティブな場合は、関連するすべての開かれているコンテキストメニューを利用可能にする必要があります。この目的のための、再帰的メソッドCWndEvents::CheckContextMenu() は、上記のコードリストで検討されています。
- メニュー項目 (CMenuItem):メニュー項目は、コンテキストメニューを添付できる独立したコントロールになることができます。したがって、このようなコントロールがアクティブな場合は、コントロール自体(メニュー項目)とそこから開いたすべてのコンテキストメニューも使用できます。
- スクロールバー (CScroll):スクロールバーがアクティブな場合(移動中のサム)、最初からすべてのコントロールを使用可能にします。たとえば、スクロールバーがリストビューに取り付けられている場合、リストビューとそのすべてのコントロールは、ネストされたものを含んで完全に使用可能になります。
- ツリービュー (CTreeView):このコントロールは、リストの幅が変更されているときにアクティブになることがあります。マウスオーバー時のリストビューの処理を除外し、ツリービュー自体を処理可能にする必要があります。
下記はCWndEvents::OnSetAvailable()メソッドのコードです。
class CWndEvents : public CWndContainer { private: //--- 使用可能なコントロールを特定する bool OnSetAvailable(void); }; //+------------------------------------------------------------------+ //| 使用可能なコントロールを特定するイベント | //+------------------------------------------------------------------+ bool CWndEvents::OnSetAvailable(void) { //--- コントロールの利用可能性を変更するシグナルの場合 if(m_id!=CHARTEVENT_CUSTOM+ON_SET_AVAILABLE) return(false); //--- 設定/復元シグナル bool is_restore=(bool)m_dparam; //--- アクティブなコントロールを特定する int mb_index =ActivatedMenuBarIndex(); int mi_index =ActivatedMenuItemIndex(); int sb_index =ActivatedSplitButtonIndex(); int cb_index =ActivatedComboBoxIndex(); int dc_index =ActivatedDropCalendarIndex(); int sc_index =ActivatedScrollIndex(); int tl_index =ActivatedTableIndex(); int sd_index =ActivatedSliderIndex(); int tv_index =ActivatedTreeViewIndex(); int ch_index =ActivatedSubChartIndex(); //--- 使用可能なコントロールを特定するシグナルの場合は、初めにアクセスを無効にする if(!is_restore) SetAvailable(m_active_window_index,false); //--- アクティブになった項目がない場合のみに復元する else { if(mb_index==WRONG_VALUE && mi_index==WRONG_VALUE && sb_index==WRONG_VALUE && dc_index==WRONG_VALUE && cb_index==WRONG_VALUE && sc_index==WRONG_VALUE && tl_index==WRONG_VALUE && sd_index==WRONG_VALUE && tv_index==WRONG_VALUE && ch_index==WRONG_VALUE) { SetAvailable(m_active_window_index,true); return(true); } } //--- (1) アクセスを無効化する、または (2) ドローダウンカレンダーを復元するシグナルの場合r if(!is_restore || (is_restore && dc_index!=WRONG_VALUE)) { CElement *el=NULL; //--- メインメニュー if(mb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_menu_bars[mb_index]; } //--- メニュー項目 else if(mi_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_menu_items[mi_index]; } //--- スプリットボタン else if(sb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_split_buttons[sb_index]; } //--- ドロップダウンリストなしのドロップダウンカレンダー else if(dc_index!=WRONG_VALUE && cb_index==WRONG_VALUE) { el=m_wnd[m_active_window_index].m_drop_calendars[dc_index]; } //--- ドローダウンリスト else if(cb_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_combo_boxes[cb_index]; } //--- スクロールバー else if(sc_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_scrolls[sc_index]; } //--- テーブル else if(tl_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_tables[tl_index]; } //--- スライダー else if(sd_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_sliders[sd_index]; } //--- ツリービュー else if(tv_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_treeview_lists[tv_index]; } //--- サブチャート else if(ch_index!=WRONG_VALUE) { el=m_wnd[m_active_window_index].m_sub_charts[ch_index]; } //--- コントロールポインタが受信されなかった場合は終了する if(::CheckPointer(el)==POINTER_INVALID) return(true); //--- メインメニューをブロックする if(mb_index!=WRONG_VALUE) { //--- メインメニューとその表示可能なコンテキストメニューを利用可能にする el.IsAvailable(true); //--- CMenuBar *mb=dynamic_cast<CMenuBar*>(el); int items_total=mb.ItemsTotal(); for(int i=0; i<items_total; i++) { CMenuItem *mi=mb.GetItemPointer(i); mi.IsAvailable(true); //---確認してコンテキストメニューを使用可能にする CheckContextMenu(mi); } } //--- メニュー項目をブロックする if(mi_index!=WRONG_VALUE) { CMenuItem *mi=dynamic_cast<CMenuItem*>(el); mi.IsAvailable(true); //---確認してコンテキストメニューを使用可能にする CheckContextMenu(mi); } //--- スクロールバーのブロック else if(sc_index!=WRONG_VALUE) { //--- メインノードから初めて使用可能にする el.MainPointer().IsAvailable(true); } //--- ツリービューのブロック else if(tv_index!=WRONG_VALUE) { //--- メインコントロール以外のすべてのコントロールをロックする CTreeView *tv=dynamic_cast<CTreeView*>(el); tv.IsAvailable(true,true); int total=tv.ElementsTotal(); for(int i=0; i<total; i++) tv.Element(i).IsAvailable(false); } else { //--- コントロールを利用可能にする el.IsAvailable(true); } } //--- return(true); }
コントロールを検証するためのアプリケーション
テスト目的のMQLアプリケーションを実装しました。グラフィカルインターフェイスには、ライブラリのすべてのコントロールが含まれています。これは次のようになります。
図12 MQLテストアプリケーションのグラフィカルインタフェース
これは本稿末尾でダウンロードでき、より詳細に研究することができます。
おわりに
このバージョンのライブラリは、グラフィカルインターフェイスX:マルチラインテキストボックスでのテキスト選択(ビルド13)稿のものとは大きく異なります。多くの作業が行われ、ライブラリのほとんどすべてのファイルに影響を及ぼしました。ライブラリのすべてのコントロールが別々のオブジェクトで描画されるようになりました。コードの可読性が向上し、コード量が30%程減少し、その機能が拡張されました。ユーザによって報告された他の多くのエラーや欠陥が修正されました。
すでに以前のバージョンのライブラリを使用してMQLアプリケーションの作成を開始された方は、ライブラリを研究して徹底的にテストするためには、別途インストールされたMetaTrader 5端末のコピーに新しいバージョンをダウンロードすることをお勧めします。
グラフィカルインターフェイスを作成するためのライブラリは、開発の現段階では下の図のようになります。これはライブラリの最終版ではありません。 ライブラリは将来さらに発展し、改善されていくでしょう。
図13 開発の現段階でのライブラリの構造
これらのファイルに含まれている資料の使用についてご質問がある場合は、記事のいずれかでライブラリの開発の詳細をご参照になるか、本稿へのコメント欄でご質問ください。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/3366
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索