
グラフィカルインタフェースX: Timeコントロール、チェックボックスコントロールのリストとテーブルのソート(ビルド6)
コンテンツ
- はじめに
- Timeコントロール
- Timeコントロール作成クラス
- チェックボックスコントロールのリスト
- チェックボックスコントロールのリスト作成クラス
- テーブルのソート
- その他のライブラリ更新
- コントロールを検証するためのアプリケーション
- おわりに
はじめに
このライブラリの目的のより良い理解を得るためにはシリーズ最初のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)稿をお読みください。各章の末尾では記事へのリンクの完全なリストがみられます。そこではまた、開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。
ライブラリはさらに成長します。Timeコントロールチェックボックスコントロールのリストが話し合われます。さらに、CTable クラスでデータを昇順または降順に並べ替えることができるようになりました。本稿ではこれらをはじめとするアップデートについて説明します。
Timeコントロール
指標やエキスパートのためのグラフィカルインタフェースを作成するときには時間範囲を指定する必要があることがあります。時には、この必要性はデイトレード中に発生します。カレンダーおよびドロップダウンカレンダーはすでに実演されました。これは日付の設定には使用できますが、時刻(時および分)には使用できません。
Timeコントロールのすべての構成要素を列挙してみましょう。
- 背景
- アイコン
- 説明
- 2つのエディットボックス
図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コントロールがエディットボックスとして使用されます。
{
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)); }
};
プログラムによって、エディットボックス(時および分)の現在値を取得して設定するには、以下のコードリストのメソッドを使用します。
{
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アプリケーションのユーザは自分の取引に必要な銘柄や時間枠だけを選んでリストを作成する必要があるかもしれません。
チェックボックスコントロールのリストを作成するためのコンポーネントのリスト:
- コントロールに共通する背景
- 縦スクロールバー
- チェックボックスグループ:
- 背景
- アイコン
- テキストラベル
図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);
};
アイテムの値(アイテムの説明)とは別に、チェックボックスの状態(オン/オフ)を表す配列も必要です。リストでの指定されたインデックスによって値を設定/取得するメソッドも必要です。
{
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 データがソートされたテーブルの例
矢印は通常、ソートされたデータの符号として列ヘッダーに表示されます。矢印が上のスクリーンショットのように下向きの場合はデータは降順でソートされ、逆も同様です。
したがって、この記事ではCTable型のテーブルの並べ替えが行われます。列のヘッダーを有効にする機能はすでに含まれていますが、インタラクティブにする必要があります。まず、マウスがホバーしたときやクリックされたときにヘッダーの色を変える必要があります。 これを行うには、 CTable クラスにさまざまな状態の列ヘッダーの色を設定するためのフィールドとメソッドを追加します(下のコードを参照)。
{
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()メソッドが使用されます。
{
private:
//--- 列に従ってデータをソートするモード
bool m_is_sort_mode;
//---
public:
//--- データソートモード
void IsSortMode(const bool flag) { m_is_sort_mode=flag; }
};
マウスのホバ-時のヘッダの色の変更にはプライベートCTable::HeaderColorByHover() メソッドが使われます。これは、コントロールのイベントハンドラで呼び出されます。
{
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()プライベートメソッドを追加する必要があります。テーブルの作成前にソートが有効になっていない場合、アイコンは作成されません。テーブルは初めはソートされていないので、ソートが有効な場合でも、作成直後にはアイコンは表示されません。
{
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);
}
テーブルのセルの値とプロパティのための構造体は、値が実数の場合は各セルが小数点以下の桁数の配列で補完されなければなりません。テーブルの各行のデータ型を持つフィールドも必要です。
{
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に設定されます。
{
public:
//--- 指定されたテーブルのセルの値を設定する
void SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
};
特定のテーブルの列にデータ型を設定および取得する場合はCTable::DataType() メソッドを使用します。
{
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()メソッドを使用した配列要素の値の入れ替えここでは、入れ替えはテーブル行によって直接行われます。 セルの値だけでなく、テキストの色も入れ替えられます。
{
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()メソッドが作成されました。
{
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万要素)
クイックソートが最も速いことが証明されています。このメソッドのコードは標準ライブラリ、つまりQuickSort() メソッドから取られています。このテストではArraySort() 関数よりも優れた結果が示されています。アルゴリズムの動作時間は、測定精度を高めるためにGetMicrosecondCount()関数を使用して測定されています。最良の結果(1秒未満)を示したアルゴリズムだけを残しておきます。
図5 ソート方法の最良のテスト結果のグラフ(配列は10万要素)
配列サイズを10倍に増やします。つまり、1000000(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() メソッドは、黄色で強調表示されています。
{
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()メソッドの最後の行は、対応するアイコンをソートされたテーブルの矢印記号に設定します。
{
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)この列のデータ型が含まれます。
{
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() プライベートメソッドで行われます。
{
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ポイントです。
{
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_DATETIME.
- 2番目の列 - TYPE_DOUBLE.
- 3番目の列 - TYPE_LONG.
残りの列はデフォルトではTYPE_STRING型です。下のスクリーンショットは、最初のタブのテーブルとのグラフィカルインタフェースの外観を示しています。
図7 2列目に従ってソート(昇順)されたテーブルの例
2番目のタブに4つのコントロールを作成します。
- ドロップダウンカレンダー(CDropCalendarクラス)
- Timeコントロール(CTimeEditクラス)
- チェックボックスのリスト(CCheckBoxListクラス)
- リストビュー(CListViewクラス)
以下のスクリーンショットは、テスト用のMQLアプリケーションのグラフィカルインタフェースでの表示方法を示しています。
図8 2番目のタブのコントロール
このテストアプリケーションのソースコードは、本稿末尾にあります。
おわりに
グラフィカルインタフェースを作成するためのライブラリーの概略は現在以下の通りに見えます。
図9 開発の現段階でのライブラリの構造
グラフィカルインターフェイスに関するシリーズの記事はこれで最後ではありません。私たちは、それを引き続き改善し、新しい機能を補完していきます。以下で、テスト用のライブラリとファイルの最新バージョンがダウンロードできます。
これらのファイルに含まれている資料の使用についてご質問がある場合は、記事のいずれかでライブラリの開発の詳細をご参照になるか、本稿へのコメント欄でご質問ください。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/2897





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索