English Русский 中文 Español Deutsch Português
グラフィカルインタフェースX: Timeコントロール、チェックボックスコントロールのリストとテーブルのソート(ビルド6)

グラフィカルインタフェースX: Timeコントロール、チェックボックスコントロールのリストとテーブルのソート(ビルド6)

MetaTrader 5 | 13 2月 2017, 09:41
637 0
Anatoli Kazharski
Anatoli Kazharski
Untitled Document

コンテンツ


はじめに

このライブラリの目的のより良い理解を得るためにはシリーズ最初のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)稿をお読みください。各章の末尾では記事へのリンクの完全なリストがみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。

ライブラリはさらに成長します。Timeコントロールチェックボックスコントロールのリストが話し合われます。さらに、CTable クラスでデータを昇順または降順に並べ替えることができるようになりました。本稿ではこれらをはじめとするアップデートについて説明します。

 

Timeコントロール

指標やエキスパートのためのグラフィカルインタフェースを作成するときには時間範囲を指定する必要があることがあります。時には、この必要性はデイトレード中に発生します。カレンダーおよびドロップダウンカレンダーはすでに実演されました。これは日付の設定には使用できますが、時刻(時および分)には使用できません。

Timeコントロールのすべての構成要素を列挙してみましょう。

  • 背景
  • アイコン
  • 説明
  • 2つのエディットボックス

図1 Timeコントロールの複合部分

図1 Timeコントロールの複合部分

 

このコントロールのクラスを詳しく見てみましょう。

 

Timeコントロール作成クラス

すべてのコントロールに標準的なメソッドを持つCTimeEditクラスを持ったTimeEdit.mqhファイルを作成しライブラリエンジン(WndContainer.mqhファイル)に関連付けます。下にあるのはユーザがカスタマイズできるコントロールプロパティのリストです。

  • コントロールの背景色
  • アクティブ状態とブロック状態でのコントロールアイコン
  • 2つの軸(x、y)に沿ったアイコンのマージン
  • コントロールの説明テキスト
  • 2つの軸(x、y)に沿ったテキストラベルのマージン
  • コントロールの異なる状態にあるテキストの色
  • エディットボックスの幅
  • 2つの軸(x、y)に沿ったエディットボックスのマージン
  • コントロールの状態(使用可能/ブロック)
  • エディットボックスの値のリセットモード
//+------------------------------------------------------------------+
//| Timeコントロール作成クラス                              |
//+------------------------------------------------------------------+
class CTimeEdit : public CElement
  {
private:
   //--- コントロールの背景色
   color             m_area_color;
   //--- アクティブ状態とブロック状態でのコントロールアイコン
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- アイコンのマージン
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- コントロールの説明テキスト
   string            m_label_text;
  //--- テキストラベルのマージン
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- 異なる状態でのテキストの色
   color             m_label_color;
   color             m_label_color_hover;
   color             m_label_color_locked;
   color             m_label_color_array[];
   //--- エディットボックスの大きさ
   int               m_edit_x_size;
   //--- エディットボックスのマージン
   int               m_edit_x_gap;
   int               m_edit_y_gap;
   //--- コントロールの状態(使用可能/ブロック)
   bool              m_time_edit_state;
   //--- 値をリセットするモード
   bool              m_reset_mode;
   //---
public:
   //--- (1) 背景色 (2) アイコンのマージン
   void              AreaColor(const color clr)                     { m_area_color=clr;                  }
   void              IconXGap(const int x_gap)                      { m_icon_x_gap=x_gap;                }
   void              IconYGap(const int y_gap)                      { m_icon_y_gap=y_gap;                }
   //--- (1) コントロールの説明テキスト (2) テキストラベルのマージン
   string            LabelText(void)                          const { return(m_label.Description());     }
   void              LabelText(const string text)                   { m_label.Description(text);         }
   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              LabelColor(const color clr)                    { m_label_color=clr;                 }
   void              LabelColorHover(const color clr)               { m_label_color_hover=clr;           }
   void              LabelColorLocked(const color clr)              { m_label_color_locked=clr;          }
   //--- (1) エディットボックスの大きさ (2) エディットボックスのマージン
   void              EditXSize(const int x_size)                    { m_edit_x_size=x_size;              }
   void              EditXGap(const int x_gap)                      { m_edit_x_gap=x_gap;                }
   void              EditYGap(const int y_gap)                      { m_edit_y_gap=y_gap;                }
   //--- (1) テキストラベルを押すときのリセットモード (2) テキスト選択モード
   bool              ResetMode(void)                                { return(m_reset_mode);              }
   void              ResetMode(const bool mode)                     { m_reset_mode=mode;                 }
  };

Timeコントロールの作成には、5つのprivateメソッドと1つのpublicメソッドが必要です。このコントロールはコンポジットで、準備済みのCSpinEditコントロールがエディットボックスとして使用されます。 

class CTimeEdit : public CElement
  {
private:
   //--- コントロール作成のためのオブジェクト
   CRectLabel        m_area;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CSpinEdit         m_hours;
   CSpinEdit         m_minutes;

   //---
public:
   //--- コントロール作成メソッド
   bool              CreateTimeEdit(const long chart_id,const int subwin,const string label_text,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateHoursEdit(void);
   bool              CreateMinutesEdit(void);
   //---
public:
   //--- エディットボックスへのポインタを返す
   CSpinEdit        *GetHoursEditPointer(void)                      { return(::GetPointer(m_hours));     }
   CSpinEdit        *GetMinutesEditPointer(void)                    { return(::GetPointer(m_minutes));   }

  };

プログラムによって、エディットボックス(時および分)の現在値を取得して設定するには、以下のコードリストのメソッドを使用します。 

class CTimeEdit : public CElement
  {
public:
   //--- エディットボックスの値を取得して設定する
   int               GetHours(void)                           const { return((int)m_hours.GetValue());   }
   int               GetMinutes(void)                         const { return((int)m_minutes.GetValue()); }
   void              SetHours(const uint value)                     { m_hours.ChangeValue(value);        }
   void              SetMinutes(const uint value)                   { m_minutes.ChangeValue(value);      }
  };

このコントロールがターミナルチャートでどのように表示されるかの例は、以下でさらに説明されます。 


チェックボックスコントロールのリスト

前の記事の1つでは、リストビューコントロール(CListView クラス)について説明しました。このクラスは、リストから1つのアイテムを選択するのに使用できます。しかし、時には複数の項目を選択する必要があるかもしれません。たとえば、MQLアプリケーションのユーザは自分の取引に必要な銘柄や時間枠だけを選んでリストを作成する必要があるかもしれません。

チェックボックスコントロールのリストを作成するためのコンポーネントのリスト:

  1. コントロールに共通する背景
  2. 縦スクロールバー
  3. チェックボックスグループ:
    • 背景
    • アイコン
    • テキストラベル

図2 チェックボックスコントロールのリストのコンポーネント

図2 チェックボックスコントロールのリストのコンポーネント

 

次に、このタイプのリスト( CCheckBoxList )と前に説明した単純なタイプのリスト(CListView )の違いを簡単に考えてみましょう。

 

チェックボックスコントロールのリスト作成クラス

CCheckBoxList クラスを持つ CheckBoxList.mqh ファイルを作成します。このクラスには、ライブラリのすべてのコントロールの標準メソッドが含まれています。CListView 型のリストとの最初の違いは、リストアイテムが 3つのグラフィカルオブジェクトで構成されていることです(以下のコードを参照)。オブジェクトタイプごとに個別のプライベートメソッドが作成されます。

//+------------------------------------------------------------------+
//| チェックボックスコントロールのリスト作成クラス      |
//+------------------------------------------------------------------+
class CCheckBoxList : public CElement
  {
private:
   //--- リスト作成のためのオブジェクト
   CRectLabel        m_area;
   CEdit             m_items[];
   CBmpLabel         m_checks[];
   CLabel            m_labels[];

   CScrollV          m_scrollv;
   //---
public:
   //--- コントロール作成メソッド
   bool              CreateCheckBoxList(const long chart_id,const int subwin,const int x_gap,const int y_gap);
   //---
private:
   bool              CreateArea(void);
   bool              CreateList(void);
   bool              CreateItems(void);
   bool              CreateChecks(void);
   bool              CreateLabels(void);

   bool              CreateScrollV(void);
  };

アイテムの値(アイテムの説明)とは別に、チェックボックスの状態(オン/オフ)を表す配列も必要です。リストでの指定されたインデックスによって値を設定/取得するメソッドも必要です。 

class CCheckBoxList : public CElement
  {
private:
   //--- リスト内のチェックボックスの値と状態の配列
   string            m_item_value[];
   bool              m_item_state[];
   //---
public:
   //--- 指定されたインデックスでの(1)状態と(2)リストアイテムのテキストを返す/格納する
   void              SetItemState(const uint item_index,const bool state);
   void              SetItemValue(const uint item_index,const string value);
   bool              GetItemState(const uint item_index);
   string            GetItemValue(const uint item_index);
  };
//+------------------------------------------------------------------+
//| 状態の設定                                                |
//+------------------------------------------------------------------+
void CCheckBoxList::SetItemState(const uint item_index,const bool state)
  {
   uint array_size=::ArraySize(m_item_state);
//--- リストにアイテムがない場合は報告する
   if(array_size<1)
      ::Print(__FUNCTION__," > This method is to be called, if the list has at least one item!");
//--- 範囲を超えた場合の調整
   uint check_index=(item_index>=array_size)?array_size-1 : item_index;
//--- 値を格納する
   m_item_state[check_index]=state;
//--- リストをスクロールバーに沿って動かす
   ShiftList();
  }
//+------------------------------------------------------------------+
//| チェックボックスの状態を取得する                               |
//+------------------------------------------------------------------+
bool CCheckBoxList::GetItemState(const uint item_index)
  {
   uint array_size=::ArraySize(m_item_state);
//--- リストにアイテムがない場合は報告する
   if(array_size<1)
      ::Print(__FUNCTION__," > This method is to be called, if the list has at least one item!");
//--- 範囲を超えた場合の調整
   uint check_index=(item_index>=array_size)?array_size-1 : item_index;
//--- 値を格納する
   return(m_item_state[check_index]);
  }

メソッドにリストの制御に関する適切な変更が加えられました。ご自分で評価なさってください。 


テーブルのソート

アプリケーションのグラフィカルインターフェースがデータ付きの表を使用する場合、ユーザが指定した列によるソートが必要な場合があります。グラフィカルインターフェイスの多くの実装では、これは、列ヘッダーをクリックすることによってデータがソートされるような方法で実装されます。ヘッダをクリックすると、データが昇順、つまり、最小値から最大値にソートされます。2回目のクリックではデータが降順、つまり、最大値から最小値にソートされます。

下のスクリーンショットは、MetaEditorのツールボックスウィンドウと、3つの列を持つ表を示しています。テーブルは日付を含む第3列に従って(降順)にソートされます。

図3 データがソートされたテーブルの例

図3 データがソートされたテーブルの例

 

矢印は通常、ソートされたデータの符号として列ヘッダーに表示されます。矢印が上のスクリーンショットのように下向きの場合はデータは降順でソートされ、逆も同様です。

したがって、この記事ではCTable型のテーブルの並べ替えが行われます。列のヘッダーを有効にする機能はすでに含まれていますが、インタラクティブにする必要があります。まず、マウスがホバーしたときやクリックされたときにヘッダーの色を変える必要があります。 これを行うには、 CTable クラスにさまざまな状態の列ヘッダーの色を設定するためのフィールドとメソッドを追加します(下のコードを参照)。

class CTable : public CElement
  {
private:
   //--- ヘッダ背景色
   color             m_headers_color;
   color             m_headers_color_hover;
   color             m_headers_color_pressed;

   //---
public:
   //--- ヘッダ背景色
   void              HeadersColor(const color clr)                              { m_headers_color=clr;                     }
   void              HeadersColorHover(const color clr)                         { m_headers_color_hover=clr;               }
   void              HeadersColorPressed(const color clr)                       { m_headers_color_pressed=clr;             }

  };

表にソート機能が必要かどうかはユーザーが決定します。ソートモードはデフォルトで無効になっています。有効化にはCTable::IsSortMode()メソッドが使用されます。 

class CTable : public CElement
  {
private:
   //--- 列に従ってデータをソートするモード
   bool              m_is_sort_mode;
   //---
public:
   //--- データソートモード
   void              IsSortMode(const bool flag)                                { m_is_sort_mode=flag;                     }
  };

マウスのホバ-時のヘッダの色の変更にはプライベートCTable::HeaderColorByHover() メソッドが使われます。これは、コントロールのイベントハンドラで呼び出されます。 

class CTable : public CElement
  {
private:
   //--- マウスカーソルがホバーしたときにテーブルの行の色を変える
   void              HeaderColorByHover(void);
  };
//+------------------------------------------------------------------+
//| マウスカーソルがホバーしたときにテーブル行の色を変える     |
//+------------------------------------------------------------------+
void CTable::HeaderColorByHover(void)
  {
//--- 行のソートモードが無効の場合は終了する
   if(!m_is_sort_mode || !m_fix_first_row)
      return;
//---
   for(uint c=0; c<m_visible_columns_total; c++)
     {
      //--- 現在のヘッダーのフォーカスを確認する
      bool condition=m_mouse.X()>m_columns[c].m_rows[0].X() && m_mouse.X()<m_columns[c].m_rows[0].X2() &&
                     m_mouse.Y()>m_columns[c].m_rows[0].Y() && m_mouse.Y()<m_columns[c].m_rows[0].Y2();
      //---
      if(!condition)
         m_columns[c].m_rows[0].BackColor(m_headers_color);
      else
        {
         if(!m_mouse.LeftButtonState())
            m_columns[c].m_rows[0].BackColor(m_headers_color_hover);
         else
            m_columns[c].m_rows[0].BackColor(m_headers_color_pressed);
        }
     }
  }

データのソート方向のアイコンを作成するには CTable :: CreateSignSortedData()プライベートメソッドを追加する必要があります。テーブルの作成前にソートが有効になっていない場合、アイコンは作成されません。テーブルは初めはソートされていないので、ソートが有効な場合でも、作成直後にはアイコンは表示されません。 

class CTable : public CElement
  {
private:
   //--- テーブル作成のためのオブジェクト
   CBmpLabel         m_sort_arrow;
   //---
private:
   //--- テーブル作成メソッド
   bool              CreateSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| データがソートされた印として矢印アイコンを作成する |
//+------------------------------------------------------------------+
bool CTable::CreateSignSortedData(void)
  {
//--- ソートモードが無効の場合は終了する
   if(!m_is_sort_mode)
      return(true);

//--- オブジェクト名の形成
   string name=CElement::ProgramName()+"_table_sort_array_"+(string)CElement::Id();
//--- 座標
   int x =(m_anchor_right_window_side)?m_columns[0].m_rows[0].X()-m_columns[0].m_rows[0].XSize()+m_sort_arrow_x_gap : m_columns[0].m_rows[0].X2()-m_sort_arrow_x_gap;
   int y =(m_anchor_bottom_window_side)?CElement::Y()-m_sort_arrow_y_gap : CElement::Y()+m_sort_arrow_y_gap;
//--- 矢印アイコンが定義されていない場合はデフォルトを設定する
   if(m_sort_arrow_file_on=="")
      m_sort_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrow_file_off=="")
      m_sort_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//--- オブジェクトを設定する
   if(!m_sort_arrow.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- プロパティを設定する
   m_sort_arrow.BmpFileOn("::"+m_sort_arrow_file_on);
   m_sort_arrow.BmpFileOff("::"+m_sort_arrow_file_off);
   m_sort_arrow.Corner(m_corner);
   m_sort_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_sort_arrow.Selectable(false);
   m_sort_arrow.Z_Order(m_zorder);
   m_sort_arrow.Tooltip("\n");
//--- 座標を格納する
   m_sort_arrow.X(x);
   m_sort_arrow.Y(y);
//--- サイズを(オブジェクトに)格納する
   m_sort_arrow.XSize(m_sort_arrow.X_Size());
   m_sort_arrow.YSize(m_sort_arrow.Y_Size());
//--- 端からのマージン
   m_sort_arrow.XGap((m_anchor_right_window_side)?x : x-m_wnd.X());
   m_sort_arrow.YGap((m_anchor_bottom_window_side)?y : y-m_wnd.Y());
//--- オブジェクトを非表示にする
   m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- オブジェクトポインタを格納する
   CElement::AddToArray(m_sort_arrow);
   return(true);
  }

テーブルのセルの値とプロパティのための構造体は、値が実数の場合は各セルが小数点以下の桁数の配列で補完されなければなりません。テーブルの各行のデータ型を持つフィールドも必要です。

class CTable : public CElement
  {
private:
   //--- テーブルの値とプロパティの配列
   struct TOptions
     {
      ENUM_DATATYPE     m_type;
      string            m_vrows[];
      uint              m_digits[];
      ENUM_ALIGN_MODE   m_text_align[];
      color             m_text_color[];
      color             m_cell_color[];
     };
   TOptions          m_vcolumns[];
  };

テーブルのセルに値を入力すると、小数点以下の桁数はデフォルトで0に設定されます。 

class CTable : public CElement
  {
public:
   //--- 指定されたテーブルのセルの値を設定する
   void              SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
  };

特定のテーブルの列にデータ型を設定および取得する場合はCTable::DataType() メソッドを使用します。

class CTable : public CElement
  {
public:
   //--- データ型を取得/設定する
   ENUM_DATATYPE     DataType(const uint column_index)                          { return(m_vcolumns[column_index].m_type); }
   void              DataType(const uint column_index,const ENUM_DATATYPE type) { m_vcolumns[column_index].m_type=type;    }
  };

表を作成する前に、列と行の合計数を指定する必要があります。m_typeフィールドとm_digits配列は、デフォルト値で初期化されます。すべての列は初めは文字列(TYPE_STRING型で、すべてのセルの小数点以下の桁数はゼロです。 

//+------------------------------------------------------------------+
//| テーブルのサイズを設定する                                        |
//+------------------------------------------------------------------+
void CTable::TableSize(const uint columns_total,const uint rows_total)
  {
//--- 少なくても1列が必要
   m_columns_total=(columns_total<1) ?1 : columns_total;
//--- 少なくても2行が必要
   m_rows_total=(rows_total<2) ?2 : rows_total;
//--- 列の配列のサイズを設定する
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- 行の配列のサイズを設定する
   for(uint i=0; i<m_columns_total; i++)
     {
      ::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_digits,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_align,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_text_color,m_rows_total);
      ::ArrayResize(m_vcolumns[i].m_cell_color,m_rows_total);
      //--- 背景色の配列をデフォルト値で初期化する
      m_vcolumns[i].m_type=TYPE_STRING;
      ::ArrayInitialize(m_vcolumns[i].m_digits,0);
      ::ArrayInitialize(m_vcolumns[i].m_text_align,m_align_mode);
      ::ArrayInitialize(m_vcolumns[i].m_cell_color,m_cell_color);
      ::ArrayInitialize(m_vcolumns[i].m_text_color,m_cell_text_color);
     }
  }

テーブル配列のソートには複数のプライベートメソッドが必要です。この場合、次の操作が実行されます。

  • ソートアルゴリズム
  • 指定した条件での値の比較
  • 配列要素の値の入れ替え

CTable::Swap()メソッドを使用した配列要素の値の入れ替えここでは、入れ替えはテーブル行によって直接行われます。 セルの値だけでなく、テキストの色も入れ替えられます。 

class CTable : public CElement
  {
private:
   //--- 指定されたセルの値を入れ替える
   void              Swap(uint c,uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| 要素を入れ替える                                                |
//+------------------------------------------------------------------+
void CTable::Swap(uint c,uint r1,uint r2)
  {
//--- すべての列をループ内で反復処理する
   for(uint i=0; i<m_columns_total; i++)
     {
      //--- テキストを入れ替える
      string temp_text          =m_vcolumns[i].m_vrows[r1];
      m_vcolumns[i].m_vrows[r1] =m_vcolumns[i].m_vrows[r2];
      m_vcolumns[i].m_vrows[r2] =temp_text;
      //--- テキストの色を入れ替える
      color temp_text_color          =m_vcolumns[i].m_text_color[r1];
      m_vcolumns[i].m_text_color[r1] =m_vcolumns[i].m_text_color[r2];
      m_vcolumns[i].m_text_color[r2] =temp_text_color;
     }
  }

並べ替えを正しく行うには、m_typeフィールドで指定された型にキャストして値を比較する必要があります。このためには別のCTable::CheckSortCondition()メソッドが作成されました。

class CTable : public CElement
  {
private:
   //--- ソートの条件の確認
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };
//+------------------------------------------------------------------+
//| 指定したソートの条件での値の比較          |
//+------------------------------------------------------------------+
//| 方向:true (>), false (<)                                   |
//+------------------------------------------------------------------+
bool CTable::CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction)
  {
   bool condition=false;
//---
   switch(m_vcolumns[column_index].m_type)
     {
      case TYPE_STRING :
        {
         string v1=m_vcolumns[column_index].m_vrows[row_index];
         string v2=check_value;
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DOUBLE :
        {
         double v1=double(m_vcolumns[column_index].m_vrows[row_index]);
         double v2=double(check_value);
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
      //---
      case TYPE_DATETIME :
        {
         datetime v1=::StringToTime(m_vcolumns[column_index].m_vrows[row_index]);
         datetime v2=::StringToTime(check_value);
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
      //---
      default :
        {
         long v1=(long)m_vcolumns[column_index].m_vrows[row_index];
         long v2=(long)check_value;
         condition=(direction)?v1>v2 : v1<v2;
         break;
        }
     }
//---
   return(condition);
  }

CTable::Swap()及びCTable::CheckSortCondition()メソッドはソートアルゴリズムを持つメソッドで使われます。データをソートするために特定のアルゴリズムが選択されているかどうかを考えてみましょう。ArraySort()関数を使用したMQLの標準的ソートを含む10個のアルゴリズムがテストされました。

  • MQLソート
  • 選択ソート
  • バブルソート
  • カクテルシェーカーソート
  • 挿入ソート
  • シェルソート
  • バイナリツリーソート
  • クイックソート
  • ヒープソート
  • マージソート

これらのメソッドは、100,000(10万)要素を持つ配列でテストされました。配列は乱数で初期化されていました。テスト結果は以下のヒストグラムに表示されます。

図4 さまざまなソート方法のテスト結果を示すグラフ(配列は10万要素)

図4 さまざまなソート方法のテスト結果を示すグラフ(配列は10万要素)


クイックソートが最も速いことが証明されています。このメソッドのコードは標準ライブラリ、つまりQuickSort() メソッドから取られています。このテストではArraySort() 関数よりも優れた結果が示されています。アルゴリズムの動作時間は、測定精度を高めるためにGetMicrosecondCount()関数を使用して測定されています。最良の結果(1秒未満)を示したアルゴリズムだけを残しておきます。 

図5 ソート方法の最良のテスト結果のグラフ(配列は10万要素)

図5 ソート方法の最良のテスト結果のグラフ(配列は10万要素)


配列サイズを10倍に増やします。つまり、1000000(100万)要素の配列がソートされます。

図6 ソート方法の最良のテスト結果のグラフ(配列は100万要素)

図6 ソート方法の最良のテスト結果のグラフ(配列は100万要素)


このテストでは標準的なアルゴリズムを使うArraySort()関数が最速でした。クイックソートは、わずかに遅かっただけなので、選択されます。ArraySort()はタスクに適していません。その理由は (1) 両方向でソートする能力が必要、 (2) CTableクラスは構造体の配列を使用し (3) テーブルセル内の値だけでなく、それらの他のプロパティの位置も制御する必要があるということです。 

ソートの2つの方向のためのENUM_SORT_MODE列挙がEnums.mqhに追加されます。

  • SORT_ASCEND – 昇順
  • SORT_DESCEND – 降順
//+------------------------------------------------------------------+
//| ソートモードの列挙                                 |
//+------------------------------------------------------------------+
enum ENUM_SORT_MODE
  {
   SORT_ASCEND  =0,
   SORT_DESCEND =1
  };

CTable :: QuickSort() メソッドで使用されるクイックソートアルゴリズムの現在のバージョンは、以下のコードリストに示されています。本稿ですでに紹介されたCTable :: Swap() およびCTable::CheckSortCondition() メソッドは、黄色で強調表示されています。 

class CTable : public CElement
  {
private:
   //--- クイックソートメソッド
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
  };
//+------------------------------------------------------------------+
//| クイックソートアルゴリズム                                              |
//+------------------------------------------------------------------+
void CTable::QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND)
  {
   uint   r1         =beg;
   uint   r2         =end;
   uint   c          =column;
   string temp       =NULL;
   string value      =NULL;
   uint   data_total =m_rows_total-1;
//--- 左のインデックスが右端のインデックスよりも小さい限りアルゴリズムを実行する
   while(r1<end)
     {
      //--- 行の中央から値を取得する
      value=m_vcolumns[c].m_vrows[(beg+end)>>1];
      //--- 左のインデックスが見つかった右のインデックスよりも小さい限りアルゴリズムを実行する
      while(r1<r2)
        {
         //--- 指定した条件の値を見つけながら、インデックスを右にシフトする
         while(CheckSortCondition(c,r1,value,(mode==SORT_ASCEND)?false : true))
           {
            //--- 配列サイズの超過を確認する
            if(r1==data_total)
               break;
            r1++;
           }
         //--- 指定した条件の値を見つけながら、インデックスを左にシフトする
         while(CheckSortCondition(c,r2,value,(mode==SORT_ASCEND)?true : false))
           {
            //--- 配列サイズの超過を確認する
            if(r2==0)
               break;
            r2--;
           }
         //--- 左のインデックスがまだ右のインデックスより大きくない場合
         if(r1<=r2)
           {
            //--- 値を取り替える
            Swap(c,r1,r2);
            //--- 左端に達した場合
            if(r2==0)
              {
               r1++;
               break;
              }
            //---
            r1++;
            r2--;
           }
        }
      //--- 範囲の始めに達するまでのアルゴリズムの再帰的継続
      if(beg<r2)
         QuickSort(beg,r2,c,mode);
      //--- 次の反復の範囲を狭める
      beg=r1;
      r2=end;
     }
  }

これらのメソッドはすべてプライベートです。次に、(1) テーブルの列ヘッダーのヘッダーをクリックするとき(2) またはMQLアプリケーションの作者によって必要に応じて他の時間にプログラムで呼び出されるように設計されたパブリックCTable :: SortData ()メソッドを考察しましょう。CTable::SortData()には列のインデックスを渡す必要があります。最初の列はデフォルトでソートされています。指定された列が初めてソートされた場合、または降順で最後にソートされた場合、値は昇順にソートされます。データのソート後テーブルが更新されます。そしてCTable :: SortData()メソッドの最後の行は、対応するアイコンをソートされたテーブルの矢印記号に設定します。 

class CTable : public CElement
  {
private:
   //--- ソートされた列のインデックス(WRONG_VALUE - テーブルはソートされない)
   int               m_is_sorted_column_index;
   //--- 最後のソート方向
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- 指定した列に従ってデータをソートする
   void              SortData(const uint column_index=0);
  };
//+------------------------------------------------------------------+
//| 指定した列に従ってデータをソートする                  |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0)
  {
//--- ソートを開始する(ヘッダーの存在を考慮した)インデックス
   uint first_index=(m_fix_first_row) ?1 : 0;
//--- 配列の最後のインデックス
   uint last_index=m_rows_total-1;
//--- 最初には昇順でソートされ、それ以降は逆方向でソートされる
   if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
      m_last_sort_direction=SORT_ASCEND;
   else
      m_last_sort_direction=SORT_DESCEND;
//--- 最後にソートされたデータ列のインデックスを格納する
   m_is_sorted_column_index=(int)column_index;
//--- ソート
   QuickSort(first_index,last_index,column_index,m_last_sort_direction);
//--- テーブルを更新する
   UpdateTable();
//--- ソートの方向に応じてアイコンを設定する
   m_sort_arrow.State((m_last_sort_direction==SORT_ASCEND)?true : false);
  }

データソートモードが有効になっている場合は、列ヘッダのクリックを処理するCTable :: OnClickTableHeaders()メソッドが必要となります。コントロールに属するオブジェクトのすべての条件がみたされると、ヘッダ(列)のインデックスがループで決定され、次にテーブルがソートされます。テーブルをソートした直後に、新しいON_SORT_DATA識別子を持つイベントが生成されます。このイベント識別子の他に (1) 制御識別子、 (2) ソートされた列のインデックス、(3)この列のデータ型が含まれます。

class CTable : public CElement
  {
private:
   //--- テーブルヘッダのクリックの処理
   bool              OnClickTableHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| テーブルヘッダのクリックの処理                            |
//+------------------------------------------------------------------+
bool CTable::OnClickTableHeaders(const string clicked_object)
  {
//--- ソートモードが無効の場合は終了する
   if(!m_is_sort_mode)
      return(false);

//--- 押されたのがテーブルセルでなかった場合は終了する
   if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
      return(false);
//--- オブジェクト名から識別子を取得する
   int id=CElement::IdFromObjectName(clicked_object);
//--- 識別子が一致しない場合は終了する
   if(id!=CElement::Id())
      return(false);
//--- これがテーブルセルでない場合は終了する
   if(RowIndexFromObjectName(clicked_object)>0)
      return(false);
//--- 列インデックスの決定
   uint column_index=0;
//--- 固定ヘッダーモードが有効になっている場合、1インデックスでシフトする
   int l=(m_fix_first_column) ?1 : 0;
//--- 横スクロールバーのスライダーの現在位置を取得する
   int h=m_scrollh.CurrentPos()+l;
//--- 列
   for(uint c=l; c<m_visible_columns_total; c++)
     {
      //--- 押されたのがこのセルでなかった場合
      if(m_columns[c].m_rows[0].Name()==clicked_object)
        {
         //--- 列のインデックスを取得する
         column_index=(m_fix_first_column && c==0) ?0 : h;
         break;
        }
      //---
      h++;
     }
//--- 指定した列に従ってデータをソートする
   SortData(column_index);
//--- それについてのメッセージを送信する
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElement::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

列の総数が表示列の数よりも多い場合は、水平スクロールバーを移動するときにソートされたテーブルの矢印記号の位置を調整する必要があります。これはCTable::ShiftSortArrow() プライベートメソッドで行われます。

class CTable : public CElement
  {
private:
   //--- ソートされたデータの矢印のシフト
   void              ShiftSortArrow(const uint column);
  };
//+------------------------------------------------------------------+
//| ソートされたデータ列の矢印のシフト                   |
//+------------------------------------------------------------------+
void CTable::ShiftSortArrow(const uint column)
  {
//--- 要素が非表示の場合はオブジェクトを表示する
   if(CElement::IsVisible())
      m_sort_arrow.Timeframes(OBJ_ALL_PERIODS);
//--- 座標を計算して設定する
   int x=m_columns[column].m_rows[0].X2()-m_sort_arrow_x_gap;
   m_sort_arrow.X(x);
   m_sort_arrow.X_Distance(x);
//--- 端からのマージン
   m_sort_arrow.XGap((m_anchor_right_window_side)?m_wnd.X2()-x : x-m_wnd.X());
  }

このメソッドは、ヘッダがシフトされたコードブロック内のCTable::UpdateTable() メソッド内から呼び出されます。下記はCTable::UpdateTable() メソッドの短縮版にフラグメントを追加したものです。ここで、ソートされた列が最初のサイクルで見つかった場合、フラグが設定され、矢印が移動されます。サイクルが完了すると、ソートされた列が存在することがわかることがありますが、これは前のサイクルでは見つからなかったものです。これはそれが可視領域から外れており非表示にされるであることを意味する可能性があります。これが最初の列(インデックスゼロ)であり、同時に固定されてシフトできない場合は、矢印記号がその列に設定されます。 

//+------------------------------------------------------------------+
//| 最近の変更を考慮してテーブルのデータを更新する       |
//+------------------------------------------------------------------+
void CTable::UpdateTable(void)
  {
//...

//--- 一番上の行のヘッダーの移動
   if(m_fix_first_row)
     {
      //--- ソートアイコンのシフトの決定
      bool is_shift_sort_arrow=false;
      //--- 列
      for(uint c=l; c<m_visible_columns_total; c++)
        {
         //--- 配列の範囲が超えられていない場合
         if(h>=l && h<m_columns_total)
           {
            //--- ソートされた列が見つかった場合
            if(!is_shift_sort_arrow && m_is_sort_mode && h==m_is_sorted_column_index)
              {
               is_shift_sort_arrow=true;
               //--- ソートアイコンを調整する
               uint column=h-(h-c);
               if(column>=l && column<m_visible_columns_total)
                  ShiftSortArrow(column);
              }

            //--- (1) 値 (2) 背景色 (3) テキストの色 (4) セル内のテキストの配置を調節する
            SetCellParameters(c,0,m_vcolumns[h].m_vrows[0],m_headers_color,m_headers_text_color,m_vcolumns[h].m_text_align[0]);
           }
         //---
         h++;
        }
      //--- ソートされたテーブルが存在するが見つからなかった場合
      if(!is_shift_sort_arrow && m_is_sort_mode && m_is_sorted_column_index!=WRONG_VALUE)
        {
         //--- インデックスがゼロより大きい場合は非表示にする
         if(m_is_sorted_column_index>0 || !m_fix_first_column)
            m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
         //--- 最初の列のヘッダーに設定する
         else
            ShiftSortArrow(0);
        }

     }
//...

  }

テストエキスパートファイルを含むファイルは、本稿末尾でダウンロードできます。操作は、それらを使用して自分でテストすることができます。

 

その他のライブラリ更新

追加機能として、このビルドには次のライブラリの更新が含まれています。

1. フォントとフォントサイズをコントロール別に設定できるようになりました。この目的のために、コントロールのCElement 基本クラスに適切なフィールドとメソッドが追加されました。デフォルトではCalibri フォントが使用され、フォントサイズは8ポイントです。 

class CElement
  {
protected:
   //--- フォント
   string            m_font;
   int               m_font_size;
   //---
public:
   //--- (1) フォントと (2) フォントサイズ
   void              Font(const string font)                         { m_font=font;                          }
   string            Font(void)                                const { return(m_font);                       }
   void              FontSize(const int font_size)                   { m_font_size=font_size;                }
   int               FontSize(void)                            const { return(m_font_size);                  }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                 |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_font("Calibri"),
                           m_font_size(8)
  {
  }

したがって、コントロールを作成するためのすべてのメソッドで、フォントを指定する必要がある基本クラスから値が取得されます。. CCheckBoxクラスのテキストラベルの例を以下に示します。ライブラリのすべてのクラスで同じことが行われています。 

//+------------------------------------------------------------------+
//| チェックボックスのテキストラベルを作成する                           |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
  {
//--- オブジェクト名の形成
   string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- 座標
   int x =(m_anchor_right_window_side)?m_x-m_label_x_gap : m_x+m_label_x_gap;
   int y =(m_anchor_bottom_window_side)?m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- 状態に応じたテキストの色
   color label_color=(m_check_button_state) ?m_label_color : m_label_color_off;
//--- オブジェクトを設定する
   if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- プロパティを設定する
   m_label.Description(m_label_text);
   m_label.Font(CElement::Font());
   m_label.FontSize(CElement::FontSize());

   m_label.Color(label_color);
   m_label.Corner(m_corner);
   m_label.Anchor(m_anchor);
   m_label.Selectable(false);
   m_label.Z_Order(m_zorder);
   m_label.Tooltip("\n");
//--- 端からのマージン
   m_label.XGap((m_anchor_right_window_side)?x : x-m_wnd.X());
   m_label.YGap((m_anchor_bottom_window_side)?y : y-m_wnd.Y());
//--- グラデーション配列を初期化する
   CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- オブジェクトポインタを格納する
   CElement::AddToArray(m_label);
   return(true);
  }

 

2. グラフィカルインターフェイスの各コントロールのフォームの端からのマージンはコントロールを作成するメソッドに直接渡す必要があります。計算は自動的に行われます。例として、以下のリストはCProgramカスタムクラスからドロップダウンカレンダーを作成する方法を示しています。 

//+------------------------------------------------------------------+
//| ドロップダウンカレンダーを作成する                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar(const int x_gap,const int y_gap,const string text)
  {
//--- オブジェクトをパネルに渡す
   m_drop_calendar.WindowPointer(m_window);
//--- 2番目のタブに取り付ける
   m_tabs.AddToElementsArray(1,m_drop_calendar);
//--- 作成前にプロパティを設定する
   m_drop_calendar.XSize(140);
   m_drop_calendar.YSize(20);
   m_drop_calendar.AreaBackColor(clrWhite);
//--- コントロールを作成する
   if(!m_drop_calendar.CreateDropCalendar(m_chart_id,m_subwin,text,x_gap,y_gap))
      return(false);
//--- ベースにコントロールポインタを追加する
   CWndContainer::AddToElementsArray(0,m_drop_calendar);
   return(true);
  }

 


コントロールを検証するためのアプリケーション

次にテストMQLアプリケーションを作成して、すべての新しいコントロールをテストしましょう。ドロップダウンメニュー、ステータスバー、および2つのタブを持つメインメニュー(CMenuBar)を含むグラフィカルインターフェイスを作成します。T最初のタブにはCTable型のテーブルが含まれ、ソートモードが有効になっています。 

テ=ブルの最初の3つの列には、次のデータ型があります。

残りの列はデフォルトではTYPE_STRING型です。下のスクリーンショットは、最初のタブのテーブルとのグラフィカルインタフェースの外観を示しています。 

図7 2列目に従ってソート(昇順)されたテーブルの例

図7 2列目に従ってソート(昇順)されたテーブルの例


2番目のタブに4つのコントロールを作成します。 

  • ドロップダウンカレンダー(CDropCalendarクラス)
  • Timeコントロール(CTimeEditクラス)
  • チェックボックスのリスト(CCheckBoxListクラス)
  • リストビュー(CListViewクラス)

以下のスクリーンショットは、テスト用のMQLアプリケーションのグラフィカルインタフェースでの表示方法を示しています。

図8 2番目のタブのコントロール

図8 2番目のタブのコントロール


このテストアプリケーションのソースコードは、本稿末尾にあります。 

 

おわりに

グラフィカルインタフェースを作成するためのライブラリーの概略は現在以下の通りに見えます。

図9 開発の現段階でのライブラリの構造

図9 開発の現段階でのライブラリの構造


グラフィカルインターフェイスに関するシリーズの記事はこれで最後ではありません。私たちは、それを引き続き改善し、新しい機能を補完していきます。以下で、テスト用のライブラリとファイルの最新バージョンがダウンロードできます。

これらのファイルに含まれている資料の使用についてご質問がある場合は、記事のいずれかでライブラリの開発の詳細をご参照になるか、本稿へのコメント欄でご質問ください。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2897

添付されたファイル |
モスクワ為替先物のスプレッド戦略の開発例 モスクワ為替先物のスプレッド戦略の開発例
MT5 プラットフォームでは、同時に複数の金融商品のトレードロボットをテストすることができます。組み込みのストラテジーテスターは、自動的にヒストリーデータをブローカーのサーバーからダウンロードします。そのため、開発者は特別手動で何かをする必要はありません。シンプルかつ確実に異なるシンボルのミリ秒単位のティックによるトレード環境を再現することが可能です。この記事では、2つのモスクワ為替先物においてスプレッドストラテジーをテストと開発を行います。
取引通貨バスケットでの利用可能なパターン 取引通貨バスケットでの利用可能なパターン
前の記事に則って、トレーダーが認識可能なパターンについて分析を試みます。また、各パターンの利点、欠点を考慮し、それに関する推奨事項を示します。ウィリアムズのオシレータに基づいたインジケーターを分析ツールとして使用します。
ウェブサイトにMetaTrader 4/5 ウェブターミナルを無料で埋め込んで利益を得ましょう ウェブサイトにMetaTrader 4/5 ウェブターミナルを無料で埋め込んで利益を得ましょう
ブラウザから直接金融市場取引ができるウェブターミナルはトレーダーによく知られています。お客様のウェブサイトにウェブターミナルウィジェットを追加なさってください。これは完全に無償です。ご自分のウェブサイトでブローカーを照会して利益を得ることができます。このためにすぐに使えるWebベースのソリューションを用意させていただきました。お客様がなさるのはウェブサイトにiframeを1つ埋め込むことだけです。
インジケータのバッファや配列を使わずにヒストグラムを形成する統計分布 インジケータのバッファや配列を使わずにヒストグラムを形成する統計分布
この記事では、グラフィックメモリの助けを借りて、相場の統計的分布ヒストグラムをプロットする可能性について説明します。サンプルのヒストグラムと mql5のグラフィカルオブジェクトの「非表示」関数があります。