グラフィカルインタフェースX:リストとテーブルの高度な管理コードの最適化(ビルド7)

Anatoli Kazharski | 22 2月, 2017


コンテンツ


はじめに

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

ライブラリコードは、より規則正しく、したがって学習のために読みやすく、理解しやすいように最適化することができます。さらに、前回の記事で作成したコントロール(リスト、テーブル、スクロールバー)を引き続き開発します。MQLアプリケーションの実行時にこれらのコントロールのプロパティをプログラムで管理できるメソッドを追加していきましょう。 


ライブラリスキームの変更とコードの最適化

コントロールに関連するすべてのライブラリファイルのコードを部分的に最適化しました。頻繁に繰り返されるコードが別々のメソッドに配置され、メソッド自体は別のクラスに移動されました。

以下がその方法です。CElementの名前がCElementBaseに変えられました。これは、ライブラリのすべてのコントロールの基本クラスです。これに続く派生クラスは新しいCElementクラスで、すべてのコントロールで頻繁に繰り返されるメソッドを含みます。含まれているメソッドは下記です。

  • コントロールが取り付けられたフォームポインタを格納するメソッド
  • フォームポインタの使用可能性の確認
  • アクティブ化されたコントロールの識別子の確認
  • 絶対座標の計算
  • フォームの端からの相対座標の計算

CElementBase及びCElementクラスはそれぞれElementBase.mqhElement.mqhの別々のファイルに位置します。よって、基本クラスを持つElementBase.mqhファイルはElement.mqhファイルにインクルードされますCWindows型はここで定義される必要があるのでWindow.mqhファイルもインクルードします。これは下記のコードで示されています。

//+------------------------------------------------------------------+
//|                                                      Element.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ElementBase.mqh"
#include "Controls\Window.mqh"
//+------------------------------------------------------------------+
//| マウスパラメータを取得するクラス                              |
//+------------------------------------------------------------------+
class CElement : public CElementBase
  {
protected:
   //--- 要素が取り付けられるフォームへのポインタ
   CWindow          *m_wnd;
   //---
public:
                     CElement(void);
                    ~CElement(void);
   //--- 要素が取り付けられるフォームへのポインタ
   void              WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }
   //---
protected:
   //--- フォームポインタの存在の確認
   bool              CheckWindowPointer(void);
   //--- アクティブ化されたコントロールの識別子の確認
   bool              CheckIdActivatedElement(void);
  
   //--- 絶対座標の計算
   int               CalculateX(const int x_gap);
   int               CalculateY(const int y_gap);
   //--- フォームの端からの相対座標の計算
   int               CalculateXGap(const int x);
   int               CalculateYGap(const int y);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                  |
//+------------------------------------------------------------------+
CElement::CElement(void)
  {
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CElement::~CElement(void)
  {
  }

これらのメソッドとそのコードはすべてのコントロールクラスで頻繁に繰り返されていました。それらを別のクラスへ移動することによって、コントロールクラスのコードがはるかに理解しやすく、読みやすくなりました。これらのメソッドのコードはすべてシンプルで、文字通り1行に収まります(下のコードを参照)。座標の計算ではフォームの側面に相対したコントロールの配置が考慮されます。

//+------------------------------------------------------------------+
//| アクティブ化されたコントロールの識別子の確認             |
//+------------------------------------------------------------------+
bool CElement::CheckIdActivatedElement(void)
  {
   return(m_wnd.IdActivatedElement()==CElementBase::Id());
  }
//+------------------------------------------------------------------+
//| Xの絶対座標を計算する                                   |
//+------------------------------------------------------------------+
int CElement::CalculateX(const int x_gap)
  {
   return((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x_gap : m_wnd.X()+x_gap);
  }
//+------------------------------------------------------------------+
//| Yの絶対座標を計算する                                   |
//+------------------------------------------------------------------+
int CElement::CalculateY(const int y_gap)
  {
   return((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y_gap : m_wnd.Y()+y_gap);
  }
//+------------------------------------------------------------------+
//| フォームの端に相対したX座標を計算する             |
//+------------------------------------------------------------------+
int CElement::CalculateXGap(const int x)
  {
   return((CElementBase::AnchorRightWindowSide())?m_wnd.X2()-x : x-m_wnd.X());
  }
//+------------------------------------------------------------------+
//| フォームの端に相対したY座標を計算する    |
//+------------------------------------------------------------------+
int CElement::CalculateYGap(const int y)
  {
   return((CElementBase::AnchorBottomWindowSide())?m_wnd.Y2()-y : y-m_wnd.Y());
  }

これらのメソッドが何故CElementクラスの古いバージョンに存在しなかったのかと問われるかもしれません。これは不可能だったからです。Window.mqhファイルをインクルードしてコンパイルしようとすると、タイプなし宣言(declaration without type)エラーが発生し、結果として他の多くの関連エラーが発生しました。

 図1 CElement型が欠如しているとのコンパイルエラーメッセージ

図1 CElement型が欠如しているとのコンパイルエラーメッセージ


CWindowオブジェクトが既に宣言されているときに、CElementクラスのボディの後にWindow.mqh をインクルードすることによってこの問題を回避しようとしても、指定された型が欠如している(specified type missing)とのコンパイルエラーが発生します。

 図2 CWindow型が欠如しているとのコンパイルエラーメッセージ

図2 CWindow型が欠如しているとのコンパイルエラーメッセージ


したがって、頻繁に繰り返されるコードを配置するための追加の継承中間クラスを作成し、コントロールが取り付けられたフォームポインタを操作するメソッドを作成することにしました。ライブラリスキームのフォームとコントロールの関係に関する部分は次のようになります。

 図3 ライブラリスキームのフォームとコントロールの関係に関する部分

図3 フォームとコントロールの関係に関するライブラリスキームの一部


上記のスキームからわかるように、CWindowクラスはCElementBaseクラスから直接派生しており、中間のCElementクラスは余分でフォームには不適切です。他のすべてのクラスのコントロールは、中間のCElementクラスから派生しています。 

 

プログラムによるスクロールバーの制御

ライブラリのアプリケーション中でスクロールバーをプログラムで制御する必要性が生じました。この目的のために、MovingThumb()メソッドがCScrollVクラスとCScrollHクラスで実装されています。 これは、指定された位置にスクロールバーのサムを動かすのには使用できません。 

下記は、縦スクロールバーのコードを示しています。横スクロールバーはほぼ同じです。メソッドの引数は1つで、デフォルトはWRONG_VALUEです。位置を指定せずに(デフォルト値で)メソッドが呼び出されると、サムはリストの終わりの位置に移動されます。これは、プログラムの実行中に項目がリストに追加される場合に便利で、リストの自動スクロールを実装できます。

//+------------------------------------------------------------------+
//| 縦スクロールバーの管理のためのクラス                         |
//+------------------------------------------------------------------+
class CScrollV : public CScroll
  {
public:
   //--- 指定された位置にサムを移動する
   void              MovingThumb(const int pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| 指定された位置にサムを移動する                            |
//+------------------------------------------------------------------+
void CScrollV::MovingThumb(const int pos=WRONG_VALUE)
  {
//--- スクロールバーが必要でない場合は終了する
   if(m_items_total<=m_visible_items_total)
      return;
// --- サムの位置の確認
   uint check_pos=0;
//--- 範囲を超えた場合の位置の調整
   if(pos<0 || pos>m_items_total-m_visible_items_total)
      check_pos=m_items_total-m_visible_items_total;
   else
      check_pos=pos;
//--- サムの位置を格納する
   CScroll::CurrentPos(check_pos);
//--- スクロールバーのY座標を計算して設定する
   CalculateThumbY();
  }

 

プログラムによるリストの制御

リストを管理するためのパブリックメソッドのいくつかは実装されており、以下のアクションを実行します。

  • リストの再構築
  • リストの最後への項目を追加
  • リストの消去(すべての項目の削除)
  • リストのスクロール

さらに、ライブラリコードの最適化の一環として、繰り返しの多いコードがプライベートメソッドとしてリストクラスに追加されました。

  • 項目のY座標の計算
  • 項目の幅の計算
  • Y軸に沿ったリストサイズの計算

これらのメソッドの構造をCListViewクラスで詳しく見てみましょう。プライベートメソッドは、クラスの異なる部分で複数回繰り返されるコードを持ち、単なる補助メソッドです。各メソッドは1行です。

//+------------------------------------------------------------------+
//| リストビュー作成クラス                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
private:
   //--- 項目のY座標の計算
   int               CalculationItemY(const int item_index=0);
   //--- 項目の幅の計算
   int               CalculationItemsWidth(void);
   //--- Y軸に沿ったリストサイズの計算
   int               CalculationYSize(void);
//+------------------------------------------------------------------+
//| 項目のY座標の計算                               |
//+------------------------------------------------------------------+
int CListView::CalculationItemY(const int item_index=0)
  {
   return((item_index>0)?m_items[item_index-1].Y2()-1 : CElementBase::Y()+1);
  }
//+------------------------------------------------------------------+
//| 項目の幅の計算                                        |
//+------------------------------------------------------------------+
int CListView::CalculationItemsWidth(void)
  {
   return((m_items_total>m_visible_items_total) ?CElementBase::XSize()-m_scrollv.ScrollWidth()-1 : CElementBase::XSize()-2);
  }
//+------------------------------------------------------------------+
//| Y軸に沿ったリストサイズの計算                        |
//+------------------------------------------------------------------+
int CListView::CalculationYSize(void)
  {
   return(m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2);
  }

リストの消去とは、文字通り、すべての項目がリストから削除されることです。このためにはCListView::Clear()メソッドを使います。ここでは、グラフィカルプリミティブが最初に削除され、それらのオブジェクトへのポインタの配列が解放され、クラスの特定のフィールドにデフォルト値が設定されます。その後、リストサイズがゼロに設定され、スクロールバーのパラメータがリセットされます。このメソッドの最後では、以前にCElementBase::FreeObjectsArray()メソッドで削除されたポインタの配列にリストの背景へのポインタを再度追加する必要があります。

//+------------------------------------------------------------------+
//| リストビュー作成クラス                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- リストを消去する(すべての項目を削除する)
   void              Clear(void);
  };
//+------------------------------------------------------------------+
//| リストを消去する(すべての項目を削除する) |
//+------------------------------------------------------------------+
void CListView::Clear(void)
  {
//--- 項目オブジェクトを削除する
   for(int r=0; r<m_visible_items_total; r++)
      m_items[r].Delete();
//--- オブジェクトへのポインタの配列を消去する
   CElementBase::FreeObjectsArray();
//--- 初期値を設定する
   m_selected_item_text  ="";
   m_selected_item_index =0;
//--- リストに0サイズを設定する
   ListSize(0);
//--- スクロールバーの値をリセットする
   m_scrollv.Hide();
   m_scrollv.MovingThumb(0);
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
//--- コントロールのオブジェクトへのポインタの配列にリストの背景を追加する
   CElementBase::AddToArray(m_area);
  }

リストを再構築するにはCListView :: Rebuilding()メソッドを使用します。再構築はリストを完全に再作成する必要がある場合に行われます。このメソッドを使用すると、項目の総数と表示項目の数を変更できます。つまり、表示される項目の数が元の値と異なる場合、リストのサイズも変更されます。

リストはCListView::Rebuilding()メソッドの冒頭で消去されます。次に、表示される項目の数が変更された場合、渡された引数の値を使用して新しいサイズを設定し、リストの高さを調整します。そして、スクロールバーオブジェクトのサイズが調整されます。その後、リストが作成され、項目の総数が指定された表示項目の数を超えると、スクロールバーが表示されます。 

//+------------------------------------------------------------------+
//| リストビュー作成クラス                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- リストの再構築
   void              Rebuilding(const int items_total,const int visible_items_total);
  };
//+------------------------------------------------------------------+
//| リストの再構築                                               |
//+------------------------------------------------------------------+
void CListView::Rebuilding(const int items_total,const int visible_items_total)
  {
//--- リストの消去
   Clear();
//--- リストビューと見える部分のサイズを設定する
   ListSize(items_total);
   VisibleListSize(visible_items_total);
//--- リストサイズを調整する
   int y_size=CalculationYSize();
   if(y_size!=CElementBase::YSize())
     {
      m_area.YSize(y_size);
      m_area.Y_Size(y_size);
      CElementBase::YSize(y_size);
     }
//--- スクロールバーのサイズを調整する
   m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
   m_scrollv.ChangeYSize(y_size);
//--- リストを作成する
   CreateList();
//--- 必要な場合はスクロールバーを表示する
   if(m_items_total>m_visible_items_total)
     {
      if(CElementBase::IsVisible())
         m_scrollv.Show();
     }
  }

CListView::CreateItem()メソッドのコ―ドはClistView :: CreateList()メソッド内のループでリスト全体を作成するときだけでなく、実行中にリストに項目を追加するときにCListView :: AddItem()メソッドでも使用されるため、このメソッドでは1項目が作成されます。 

CListView::AddItem() には引数が1つだけあり、これは項目の表示テキストです。デフォルトではこれは空の文字列です。テキストは項目の作成後にCListView :: SetItemValue()メソッドを使用して追加することもできます。項目の配列はCListView :: AddItem()メソッドの開始時に1要素分だけ増加します。次に、項目数が現在の標示項目数以下の場合は、グラフィカルオブジェクトを作成する必要があります。表示数が超えられた場合は、スクロールバーを表示してサムサイズを調整し、項目の幅を調整する必要があります。 

//+------------------------------------------------------------------+
//| リストビュー作成クラス                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- リストに項目を追加する
   void              AddItem(const string value="");
  };
//+------------------------------------------------------------------+
//| リストに項目を追加する                                     |
//+------------------------------------------------------------------+
void CListView::AddItem(const string value="")
  {
//--- 配列サイズを1要素で増やす
   int array_size=ItemsTotal();
   m_items_total=array_size+1;
   ::ArrayResize(m_item_value,m_items_total);
   m_item_value[array_size]=value;
//--- 項目の総数が表示数よりも大きい場合
   if(m_items_total>m_visible_items_total)
     {
      //--- サムのサイズを調整してスクロールバーを表示する
      m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
      if(CElementBase::IsVisible())
         m_scrollv.Show();
      //--- 配列の要素が1つ未満の場合は終了する
      if(m_visible_items_total<1)
         return;
      //--- リストビュー項目の幅の算出
      int width=CElementBase::XSize()-m_scrollv.ScrollWidth()-1;
      if(width==m_items[0].XSize())
         return;
      //--- リスト項目に新しいサイズを設定する
      for(int i=0; i<m_items_total && i<m_visible_items_total; i++)
        {
         m_items[i].XSize(width);
         m_items[i].X_Size(width);
        }
      //---
      return;
     }
//--- 座標の計算
   int x=CElementBase::X()+1;
   int y=CalculationItemY(array_size);
//--- リストビュー項目の幅の計算
   int width=CalculationItemsWidth();
//--- オブジェクトの作成
   CreateItem(array_size,x,y,width);
//--- 選択された項目の強調表示
   HighlightSelectedItem();
//--- 選択された項目のテキストを格納する
   if(array_size==1)
      m_selected_item_text=m_item_value[0];
  }

CListView::Scrolling()メソッドはリスト項目をプログラムでスクロールするために設計されています。リスト内の位置番号が唯一の引数です。デフォルト値はWRONG_VALUEです。これは、リストを最後の位置にシフトすることを意味します。 

//+------------------------------------------------------------------+
//| リストビュー作成クラス                                   |
//+------------------------------------------------------------------+
class CListView : public CElement
  {
public:
   //--- リストのスクロール
   void              Scrolling(const int pos=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| リストのスクロール                                          |
//+------------------------------------------------------------------+
void CListView::Scrolling(const int pos=WRONG_VALUE)
  {
//--- スクロールバーが必要でない場合は終了する
   if(m_items_total<=m_visible_items_total)
      return;
//--- サムの位置の決定
   int index=0;
//--- 最後の位置のインデックス
   int last_pos_index=m_items_total-m_visible_items_total;
//--- 範囲を超えた場合の調整
   if(pos<0)
      index=last_pos_index;
   else
      index=(pos>last_pos_index)?last_pos_index : pos;
//--- スクロールバーのサムを動かす
   m_scrollv.MovingThumb(index);
//--- リストを動かす
   UpdateList(index);
  }

CCheckBoxList 型のリストに対しては同様のメソッドが実装されています。 

 

CTable型のテーブルのコード最適化

CTableクラスのコ―ドもまた最適化されました。頻繁に繰り返されるコードを含む多数のプライベートメソッドの追加により、コ―ドがコンパクトで読みやすくなりました。下記がこれらのメソッドです。

  • 行の配列のサイズ変更
  • デフォルト値でのセルの初期化
  • X軸に沿ったテーブルサイズの計算
  • Y軸に沿ったテーブルサイズの計算
  • セルのX座標の計算
  • セルのY座標の計算
  • 列の幅の計算
  • 列の幅の変更
  • Y軸に沿ったテーブルサイズの変更
//+------------------------------------------------------------------+
//| エディットボックステーブル作成クラス                      |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- 行の配列のサイズ変更
   void              RowResize(const uint column_index,const uint new_size);
   //--- デフォルト値でのセルの初期化
   void              CellInitialize(const uint column_index,const int row_index=WRONG_VALUE);
   //--- X軸に沿ったテーブルサイズの計算
   int               CalculationXSize(void);
   //--- Y軸に沿ったテーブルサイズの計算
   int               CalculationYSize(void);
   //--- セルのX座標の計算
   int               CalculationCellX(const int column_index=0);
   //--- セルのY座標の計算
   int               CalculationCellY(const int row_index=0);
   //--- 列の幅の計算
   int               CalculationColumnWidth(const bool is_last_column=false);
   //--- 列の幅の変更
   void              ColumnsXResize(void);
   //--- Y軸に沿ったテーブルサイズの変更
   void              YResize(void);
  };

CTable::CalculationColumnWidth()メソッドは、テーブルの列の幅を計算するためのものでfalseの値を持つ引数を1つだけもちます。デフォルト値は、すべての列の合計幅を計算するために使用されます。trueの値が渡された場合は、 最後の列の幅が計算されます。この場合再帰的メソッド呼び出しが使われます。一般的な計算と同様に、最後の列の右端がテーブルの右端と一致しないことがあるので、最後の列の幅と幅の計算への分割が必要です。

//+------------------------------------------------------------------+
//| 列の幅の計算                                           |
//+------------------------------------------------------------------+
int CTable::CalculationColumnWidth(const bool is_last_column=false)
  {
   int width=0;
//--- 縦スクロールバーの存在を確認する
   bool is_scrollv=m_rows_total>m_visible_rows_total;
//---
   if(!is_last_column)
     {
      if(m_visible_columns_total==1)
         width=(is_scrollv)?m_x_size-m_scrollv.ScrollWidth() : width=m_x_size-2;
      else
        {
         if(is_scrollv)
            width=(m_x_size-m_scrollv.ScrollWidth())/int(m_visible_columns_total);
         else
            width=m_x_size/(int)m_visible_columns_total+1;
        }
     }
   else
     {
      width=CalculationColumnWidth();
      int last_column=(int)m_visible_columns_total-1;
      int w=m_x_size-(width*last_column-last_column);
      width=(is_scrollv) ?w-m_scrollv.ScrollWidth()-1 : w-2;
     }
//---
   return(width);
  }

CTable::ColumnsXResize()メソッドは、テーブルが作成されたとき、またはテーブルの幅が変更されたときに呼び出されます。ここで、CTable::CalculationColumnWidth()メソッドは列の幅の計算のために呼び出されます。これは上で説明されました。テーブルがソートされている場合は、このメソッドの最後でソートされたテーブルの矢印記号の位置を調整する必要があります。 

//+------------------------------------------------------------------+
//| 列の幅の変更                                             |
//+------------------------------------------------------------------+
void CTable::ColumnsXResize(void)
  {
//--- 列の幅の計算
   int width=CalculationColumnWidth();
//--- 列
   for(uint c=0; c<m_columns_total && c<m_visible_columns_total; c++)
     {
      //--- X座標の計算
      int x=CalculationCellX(c);
      //--- 最後の列の幅を調整する
      if(c+1>=m_visible_columns_total)
         width=CalculationColumnWidth(true);

      //--- 行
      for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
        {
         //--- 座標
         m_columns[c].m_rows[r].X(x);
         m_columns[c].m_rows[r].X_Distance(x);
         //--- 幅
         m_columns[c].m_rows[r].XSize(width);
         m_columns[c].m_rows[r].X_Size(width);
         //--- パネルの端からのマージン
         m_columns[c].m_rows[r].XGap(CalculateXGap(x));
        }
     }
//--- テーブルがソートされていない場合は終了する
   if(m_is_sorted_column_index==WRONG_VALUE)
      return;
//--- 固定ヘッダーモードが有効になっている場合、1インデックスでシフトする
   int l=(m_fix_first_column) ?1 : 0;
//--- 縦横スクロールバーのサムの現在位置を取得する
   int h=m_scrollh.CurrentPos()+l;
//--- 配列の範囲が超えられていない場合
   if(m_is_sorted_column_index>=h && m_is_sorted_column_index<(int)m_visible_columns_total)
     {
      //--- ソートされたテーブルの列への矢印のシフト
      ShiftSortArrow(m_is_sorted_column_index);
     }
  }

このセクションの冒頭にあるリストにある他のプライベートメソッドのコードは、みな複雑ではないため、簡単に自習することができます。

上記のメソッドに加えて、最適化の一環として、テーブルセルを作成するための CTable :: CreateCell() プライベートメソッドが別に実装されています。この更新であと一つ有用なのはCTable型のテーブルでの縞模様の自動設定の追加です。以前は、ライブラリユーザがデータ配列の理解を深めるためにテーブルを縞模様にする場合CTable :: CellColor()メソッドを使用する必要がありました。つまり、テーブルの各セルに個別に色を割り当てる必要がありました。これは不便で時間がかかります。テーブルに縞模様をつけるには、コントロールを作成する前にCTable::IsZebraFormatRows()メソッドを呼び出し、2番目の色を唯一の引数として渡します。CTable::CellColor()メソッドですべてのテーブルセルのために定義された値(デフォルト - 白)が最初の色として使用されます。 

//+------------------------------------------------------------------+
//| エディットボックステーブル作成クラス                      |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- テーブルの縞模様モード
   color             m_is_zebra_format_rows;
   //---
public:
   //--- 行に縞模様をつけるモード
   void              IsZebraFormatRows(const color clr)                         { m_is_zebra_format_rows=clr;      }
  };

縞模様を設定するための2番目の色が指定されている場合、必要に応じて CTable::ZebraFormatRows()プライベートメソッドが呼び出されます。 

//+------------------------------------------------------------------+
//| エディットボックステーブル作成クラス                      |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- テーブルに縞模様をつける
   void              ZebraFormatRows(void);
  };
//+------------------------------------------------------------------+
//| テーブルに縞模様をつける     |
//+------------------------------------------------------------------+
void CTable::ZebraFormatRows(void)
  {
//--- モードが無効の場合は終了する
   if(m_is_zebra_format_rows==clrNONE)
      return;
//--- デフォルトの色
   color clr=m_cell_color;
//---
   for(uint c=0; c<m_columns_total; c++)
     {
      for(uint r=0; r<m_rows_total; r++)
        {
         if(m_fix_first_row)
           {
            if(r==0)
               continue;
            //---
            clr=(r%2==0)?m_is_zebra_format_rows : m_cell_color;
           }
         else
            clr=(r%2==0)?m_cell_color : m_is_zebra_format_rows;
         //--- セルの背景色を共通配列に格納する
         m_vcolumns[c].m_cell_color[r]=clr;
        }
     }
  }

 

プログラムによるCTable型のテーブルの制御

このライブラリ更新ではプログラムで制御されるのはCTable型テーブルのみです。 以下のアクションを実行するために、複数のパブリックメソッドが実装されています。

  • テーブルの再構築
  • 列の追加
  • 行の追加
  • テーブルの消去(すべての列と行の削除)
  • テーブルの縦横のスクロール
//+------------------------------------------------------------------+
//| エディットボックステーブル作成クラス                      |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
public:
   //--- テーブルの再構築
   void              Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total);
   //--- テーブルに列を追加する
   void              AddColumn(void);
   //--- テーブルに行を追加する
   void              AddRow(void);
   //--- テーブルを消去する(すべての列と行を削除する)
   void              Clear(void);
   //--- テーブルスクロール:(1)垂直 および(2)水平
   void              VerticalScrolling(const int pos=WRONG_VALUE);
   void              HorizontalScrolling(const int pos=WRONG_VALUE);
  };

テーブルを消去するためのCTable :: Clear()メソッドは、この記事の前のセクションで説明したリストのものと事実上同じなのでここではカバーしません。

テーブルを再構築するにはCTable :: Rebuilding()メソッドの呼び出しが必要がです。ここでは、列と行の総数と表示数を引数として渡す必要があります。ここでは、メソッドの開始時にテーブルが消去されます。つまり、その列と行がすべて削除されます。次に、渡された引数の値に基づいて配列の新しいサイズが設定されます。スクロールバーは、表示数に対する現在の行と列の総数に応じて設定されます。すべての計算が完了すると、テーブルのセルが作成され、必要に応じてスクロールバーが表示されます。 

//+------------------------------------------------------------------+
//| テーブルの再構築                                              |
//+------------------------------------------------------------------+
void CTable::Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total)
  {
//--- テーブルの消去
   Clear();
//--- テーブルサイズと見える部分のサイズを設定する
   TableSize(columns_total,rows_total);
   VisibleTableSize(visible_columns_total,visible_rows_total);
//--- スクロールバーのサイズを調整する
   m_scrollv.ChangeThumbSize(rows_total,visible_rows_total);
   m_scrollh.ChangeThumbSize(columns_total,visible_columns_total);
//--- 縦スクロールバーの存在を確認する
   bool is_scrollv=m_rows_total>m_visible_rows_total;
//--- 横スクロールバーの存在を確認する
   bool is_scrollh=m_columns_total>m_visible_columns_total;
//--- Y軸に沿ったテーブルサイズを計算する
   int y_size=CalculationYSize();
//--- 縦スクロールバーのサイズを変更する
   m_scrollv.ChangeYSize(y_size);
//--- テーブルのサイズを変更する<
   m_y_size=(is_scrollh)?y_size+m_scrollh.ScrollWidth()-1 : y_size;
   m_area.YSize(m_y_size);
   m_area.Y_Size(m_y_size);
//--- 横スクロールバーのY軸に沿った位置を調整する
   m_scrollh.YDistance(CElementBase::Y2()-m_scrollh.ScrollWidth());
//--- 横スクロールバーが必要な場合
   if(is_scrollh)
     {
      //--- 縦スクロールバーの有無に応じてサイズを設定する
      if(!is_scrollv)
         m_scrollh.ChangeXSize(m_x_size);
      else
        {
         //--- 横スクロールバーの幅を計算して変更する
         int x_size=m_area.XSize()-m_scrollh.ScrollWidth()+1;
         m_scrollh.ChangeXSize(x_size);
        }
     }
//--- テーブルのセルを作成する
   CreateCells();
//--- 必要な場合はスクロールバーを表示する
   if(rows_total>visible_rows_total)
     {
      if(CElementBase::IsVisible())
         m_scrollv.Show();
     }
   if(columns_total>visible_columns_total)
     {
      if(CElementBase::IsVisible())
         m_scrollh.Show();
     }
  }

列を追加するCTable::AddColumn()メソッドと行を追加するCTable::AddRow()メソッドは非常に似ているので、ここではそのうちの1つだけを検討します。 

CTable::AddColumn()メソッドの初めでは、対応する列の列と行の配列のサイズが設定されます。その後、追加された列のデフォルト値を持つセルはCTable::CellInitialize()メソッドで初期化されます。その後、列の総数が指定された表示数を超えていない場合は下記が行われます。 

  1. 列の幅の計算
  2. 追加された列に対する特定の数のグラフィカルオブジェクト(表セル)の作成
  3. 必要に応じたテーブルの縞模様の設定
  4. メソッドの終わりでのテーブルの更新

列と行の配列を増やした後に列と行の総数が指定された表示数より大きい場合、横スクロールバーを表示してテーブルの高さを調整する必要があります 。その後、テーブルには縞模様がつけられ、プログラムはメソッドを終了します。 

//+------------------------------------------------------------------+
//| テーブルに列を追加する                                       |
//+------------------------------------------------------------------+
void CTable::AddColumn(void)
  {
//--- 配列サイズを1要素で増やす
   uint array_size=ColumnsTotal();
   m_columns_total=array_size+1;
   ::ArrayResize(m_vcolumns,m_columns_total);
//--- 行の配列のサイズを設定する
   RowResize(array_size,m_rows_total);
//--- デフォルト値で配列を初期化する
   CellInitialize(array_size);
//--- 列の総数が指定された表示数を超えている場合
   if(m_columns_total>m_visible_columns_total)
     {
      //--- Y軸に沿ってテーブルを調整する
      YResize();
      //--- 縦スクロールバーがない場合は、横スクロールバーをテーブルの全幅に合わせる
      if(m_rows_total<=m_visible_rows_total)
         m_scrollh.ChangeXSize(m_x_size);
      //--- サムのサイズを調整してスクロールバーを表示する
      m_scrollh.ChangeThumbSize(m_columns_total,m_visible_columns_total);
      //--- スクロールバーを表示する
      if(CElementBase::IsVisible())
         m_scrollh.Show();
      //--- 行に縞模様をつける
      ZebraFormatRows();
      //--- テーブルを更新する
      UpdateTable();
      return;
     }
//--- 列の幅の計算
   int width=CalculationColumnWidth();
//--- 最後の列の幅を調整する
   if(m_columns_total>=m_visible_columns_total)
      width=CalculationColumnWidth(true);
//--- X座標の計算
   int x=CalculationCellX(array_size);
//---
   for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
     {
      //--- Y座標の計算
      int y=CalculationCellY(r);
      //--- オブジェクトの作成
      CreateCell(array_size,r,x,y,width);
      //--- 対応する色をヘッダに設定する
      if(m_fix_first_row && r==0)
         m_columns[array_size].m_rows[r].BackColor(m_headers_color);
     }
//--- 行に縞模様をつける
   ZebraFormatRows();
//--- テーブルを更新する
   UpdateTable();
  }

テーブルをスクロールするためのCTable::VerticalScrolling()及びCTable::HorizontalScrolling()メソッドはリストセクションで説明されたものと同一なので、ここではコードを表示しません。コードは本稿添付のファイルで参照できます。

次に、CTable型のリストとテーブルの新機能を示すMQLテストアプリケーションを作成しましょう。 

 

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

CTable型のリストとテーブルのクラスに追加されたメソッドの操作をすぐに見ることができるようなMQLアプリケーションをテスト目的で作成しましょう。このアプリケーションのグラフィカルインタフェースに2つのタブを作成します。最初のタブには、CTable 型のテーブルとテーブルの上にあるテーブルプロパティを管理するためのコントロールが含まれています。それらは2つのボタンと4つの数値エディットボックスで構成されます。

  • テーブルの消去(すべての列と行の削除)のための«CLEAR TABLE»ボタン
  • 数値エディットボックスで指定されたパラメータに基づいてテーブルを再構築するための«REBUILD TABLE»ボタン
  • テーブル行の総数を入力するための«Rows total»エディットボックス
  • テーブル列の総数を入力するための«Columns total»エディットボックス
  • テーブル行の表示数を入力するための«Visible rows total»エディットボックス
  • テーブル列の表示数を入力するための«Visible columns total»エディットボックス

下のスクリーンショットが外観を示します。

 図4 1番目のタブのコントロールグループ

図4 1番目のタブのコントロールグループ


2番目のタブには2つのリスト(リストビューとチェックボックスのリスト)が含まれます。リストのプログラムによる管理を示すためには以下のコントロールが表示されます。

  • リストの消去(すべての項目の削除)のための«CLEAR LISTS»ボタン
  • 数値エディットボックスで指定されたパラメータに基づいてリストを再構築するための«REBUILD LISTS»ボタン
  • リスト項目の合計数を入力するための«Items total»エディットボックス
  • リスト項目の表示数を入力するための«Visible items total»エディットボックス

 以下のスクリーンショットは2番目のタブのコントロールを示します。ドロップダウンカレンダーとタイムコントロールの2つがさらに追加されました。 

 図5 2番目のタブのコントロールグループ

図5 2番目のタブのコントロールグループ


この更新で実装されたリストとテーブルの機能の実証を進める前に、MQLアプリケーションのタイマーでのMQL開発者の作業を容易にする別の追加について考えましょう。これはCTimeCounterクラスで、指定された時間間隔でのグラフィカルインターフェイスコントロールの別々のグループの更新(再描画)頻度を管理するために使用できます。CTimeCounterクラスには3つのフィールドと2つのメソッドしかありません(下記のコードを参照)。

//+------------------------------------------------------------------+
//|                                                  TimeCounter.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| タイムカウンタ                                                     |
//+------------------------------------------------------------------+
class CTimeCounter
  {
private:
   //--- カウンタのステップ
   uint              m_step;
   //--- 時間間隔
   uint              m_pause;
   //--- タイムカウンタ
   uint              m_time_counter;
   //---
public:
                     CTimeCounter(void);
                    ~CTimeCounter(void);
   //--- ステップと時間間隔の設定
   void              SetParameters(const uint step,const uint pause);
   //--- 指定された時間間隔が経過したかどうかを確認する
   bool              CheckTimeCounter(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                      |
//+------------------------------------------------------------------+
CTimeCounter::CTimeCounter(void) : m_step(16),
                                   m_pause(1000),
                                   m_time_counter(0)
                                  
  {
  }
//+------------------------------------------------------------------+
//| デストラクタ                                                       |
//+------------------------------------------------------------------+
CTimeCounter::~CTimeCounter(void)
  {
  }

CTimeCounter::SetParameters()メソッドを使用するとポーズのカウンタ増分および時間間隔を設定できます。

//+------------------------------------------------------------------+
//| ステップと時間間隔の設定                                  |
//+------------------------------------------------------------------+
void CTimeCounter::SetParameters(const uint step,const uint pause)
  {
   m_step  =step;
   m_pause =pause;
  }

CTimeCounter::CheckTimeCounter()メソッドは、クラスパラメータで指定された時間間隔が経過したかどうかをチェックするように設計されています。時間間隔が経過した場合、メソッドはtrueを返します。

//+------------------------------------------------------------------+
//| 指定された時間間隔が経過したかどうかを確認する              |
//+------------------------------------------------------------------+
bool CTimeCounter::CheckTimeCounter(void)
  {
//--- 指定された時間間隔が経過していない場合はカウンタを増加させる
   if(m_time_counter<m_pause)
     {
      m_time_counter+=m_step;
      return(false);
     }
//--- カウンタをゼロにする
   m_time_counter=0;
   return(true);
  }

先に進む前に、開発されたライブラリのディレクトリ内のファイルの場所も変更されていることに注意してください。«MetaTrader 5\MQL5\Include\EasyAndFastGUI\Controls»ディレクトリに位置するのはコントロールのクラスを含むファイルのみです。他のすべてのファイルは、ライブラリのルートディレクトリである «MetaTrader 5\MQL5\Include\EasyAndFastGUI»に移動されました。したがって、カスタムクラスのファイルにライブラリをインクルードするには、以下に示されるようにパスを指定する必要があります。またCTimeCounterクラスを持つファイルをインクルードする方法も示されています(テスト例で使用されます)。 

//+------------------------------------------------------------------+
//|                                                      Program.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndEvents.mqh>
#include <EasyAndFastGUI\TimeCounter.mqh>

タイムカウンタのパラメータは、カスタムカウンタのコンストラクタで設定されます。

//+------------------------------------------------------------------+
//| アプリケーション作成のクラス                                 |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
protected:
   //--- タイムカウンタ
   CTimeCounter      m_counter1; // ステータスバー更新のため
   CTimeCounter      m_counter2; // リストとテーブルの更新のため
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                  |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
  {
//--- タイムカウンタのパラメータ設定
   m_counter1.SetParameters(16,500);
   m_counter2.SetParameters(16,150);
  }

これらのコントロールを完全に削除した後で、リストにアイテムを追加し、テーブルに列と行を追加するデモンストレーションは、タイマーで実装されます。指定された時間間隔の後、項目/行/列の数が対応するエディットボックスで指定された数よりも少ない場合、それらはこのブロックで追加されます(下記のコードを参照)。スクロールバーのプログラム的管理を示すために、項目がリストに追加されるたびにスクロールバーのサムがリストの最後に移動されます。 

//+------------------------------------------------------------------+
//| タイマー                                                       |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
   CWndEvents::OnTimerEvent();
...
//--- コントロールの更新間隔を一時停止する
   if(m_counter2.CheckTimeCounter())
     {
      //--- 総数が指定された数未満の場合はテーブルに行を追加する
      if(m_table.RowsTotal()<m_spin_edit1.GetValue())
         m_table.AddRow();
      //--- 総数が指定された数未満の場合はテーブルに列を追加する
      if(m_table.ColumnsTotal()<m_spin_edit2.GetValue())
         m_table.AddColumn();
      //--- 総数が指定された数未満の場合はリストビューに項目を追加する
      if(m_listview.ItemsTotal()<m_spin_edit5.GetValue())
        {
         m_listview.AddItem("SYMBOL "+string(m_listview.ItemsTotal()));
         //--- スクロールバーのサムをリストの最後に動かす
         m_listview.Scrolling();
        }
      //--- 総数が指定された数未満の場合はチェックボックスのリストに項目を追加する
      if(m_checkbox_list.ItemsTotal()<m_spin_edit5.GetValue())
        {
         m_checkbox_list.AddItem("Checkbox "+string(m_checkbox_list.ItemsTotal()));
         //--- スクロールバーのサムをリストの最後に動かす
         m_checkbox_list.Scrolling();
        }
      //--- チャートを再描画する
      m_chart.Redraw();
     }
  }

リストとテーブルを消去して再構築するためのボタン押下イベントの処理は次のようになります。 

//+------------------------------------------------------------------+
//| チャートイベントハンドラ                                   |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタン押下イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //--- 1番目のボタンのイベント
      if(lparam==m_simple_button1.Id())
        {
         //--- テーブルを消去する
         m_table.Clear();
         return;
        }
      //--- 2番目のボタンのイベント
      if(lparam==m_simple_button2.Id())
        {
         //--- テーブルを再構築する
         m_table.Rebuilding((int)m_spin_edit3.GetValue(),(int)m_spin_edit4.GetValue(),
                            (int)m_spin_edit1.GetValue(),(int)m_spin_edit2.GetValue());
         //--- テーブルを初期化する
         InitializeTable();
         //--- 変更を表示するためにテーブルを更新する
         m_table.UpdateTable();
         return;
        }
      //--- 3番目のボタンのイベント
      if(lparam==m_simple_button3.Id())
        {
         //--- リストを消去する
         m_listview.Clear();
         m_checkbox_list.Clear();
         return;
        }
      //--- 2番目のボタンのイベント
      if(lparam==m_simple_button4.Id())
        {
         //--- リストを再構築する
         m_checkbox_list.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
         m_listview.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
         //--- リストビューの8番目の項目を選ぶ
         m_listview.SelectItem(7);
         //--- リストビューにデータを入れる
         int items_total=m_listview.ItemsTotal();
         for(int i=0; i<items_total; i++)
            m_listview.SetItemValue(i,"SYMBOL "+string(i));
         //--- チェックボックスのリストにデータを入力し、1つごとにチェックを入れる
         items_total=m_checkbox_list.ItemsTotal();
         for(int i=0; i<items_total; i++)
           {
            m_checkbox_list.SetItemValue(i,"Checkbox "+string(i));
            m_checkbox_list.SetItemState(i,(i%2!=0)?true : false);
           }
         //---
         return;
        }
      //---
      return;
     }
  }

本稿で紹介されたテストアプリケーションをさらに研究するためには、以下のリンクを使用してダウンロードしてください。 

 

おわりに

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

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

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


ライブラリの次のバージョンでは、既存のコントロールが改善され、新しい機能が追加されます。テスト用のライブラリとファイルの最新バージョンは以下でダウンロードできます。

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