English Русский 中文 Español Deutsch Português
preview
DoEasy-コントロール(第16部):TabControl WinFormsオブジェクト — 複数行のタブヘッダー、コンテナに合わせたヘッダーの伸び

DoEasy-コントロール(第16部):TabControl WinFormsオブジェクト — 複数行のタブヘッダー、コンテナに合わせたヘッダーの伸び

MetaTrader 5 | 8 12月 2022, 10:08
90 0
Artyom Trishkin
Artyom Trishkin

内容


概念

前回の記事では、タブヘッダーを表示するモードについて説明しました。

オブジェクトの幅に収まるよりも多くのタブがコントロールにある場合(上部にあると仮定)、要素に収まらないヘッダーをトリミングして、スクロールボタンを表示することができます。または、オブジェクトにmultilineモードフラグが設定されている場合、ヘッダーは複数の部分(要素のサイズに含まれる数に応じる)と複数の行に配置されます。行に配置されたタブのサイズを設定する方法は3つあります(SizeMode)。

  • Normal - タブの幅はヘッダーテキストの幅に従って設定され、ヘッダーのPaddingWidth値とPaddingHeight値で指定されたスペースがヘッダーの端に沿って追加されます。
  • Fixed - コントロール設定で指定された固定サイズ。ヘッダーテキストは、サイズに収まらない場合はトリミングされます。
  • FillToRight - コントロールの幅に収まるタブは、幅いっぱいに伸びます。

アクティブなMultilineモードでタブが選択されると、タブフィールドに隣接していないそのタイトルは、それが配置されている行全体とともにタブフィールドの近くに移動し、フィールドに隣接していたヘッダーが選択したタブの行を置き換えます。


また、NormalモードとFixedモードでコントロールの上にタブを配置する機能も実装しました。

この記事では、コントロールのすべての側面にMultilineモードでタブを配置し、コントロールのサイズに応じてタブの行を伸ばしてタブのサイズを設定するためのFillToRightモードを追加します。コンテナの上部または下部にタブヘッダーの行を配置すると、ヘッダーはコントロールの幅に沿って伸びます。タブヘッダーが左または右に配置されると、ヘッダーはコントロールの高さに合わせて伸びます。この場合、選択したタブヘッダーをクリックするとサイズが4ピクセル増加するため、ヘッダーが伸びる領域はこの領域の両側で2ピクセル小さくなります。したがって、余白の見出しに2ピクセルのギャップを残さないと、それが選択されてそのサイズがそれに応じて大きくなると、その端がコントロールを超えてしまいます。

タブヘッダーを1行に表示するモードで、コントロールサイズに収まりきらない数のヘッダーがある場合、コンテナを超えるすべてのヘッダーはコンテナの外側に配置されます。コンテナを超えるグラフィック要素を切り取るのに十分な機能がまだないため、このモードはまだ考慮されていません。以降の記事で実装します。


ライブラリクラスの改善

同じ行にあるすべてのヘッダーのタブヘッダーのリストを検索して、この行のヘッダーのみを操作できるようにします。これをおこなう最も簡単な方法は、ライブラリのWinFormsオブジェクトに新しいプロパティを追加し、ライブラリの長年の機能を使用してオブジェクトのリストを検索および並び替えることです。

\MQL5\Include\DoEasy\Defines.mqhで、キャンバス上のグラフィック要素の整数プロパティのリストに2つの新しいプロパティを追加して、合計数を90から92に増やします

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type

   //---...
   //---...

   CANV_ELEMENT_PROP_TAB_SIZE_MODE,                   // Tab size setting mode
   CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,                 // Tab index number
   CANV_ELEMENT_PROP_TAB_PAGE_ROW,                    // Tab row index
   CANV_ELEMENT_PROP_TAB_PAGE_COLUMN,                 // Tab column index
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Location of an object inside the control
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (92)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


キャンバス上のグラフィック要素を並べ替えるための基準の列挙にこれらの2つの新しいプロパティを追加します

//+------------------------------------------------------------------+
//| Possible sorting criteria of graphical elements on the canvas    |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Sort by integer properties
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Sort by element ID
   SORT_BY_CANV_ELEMENT_TYPE,                         // Sort by graphical element type

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE,                // Sort by the mode of setting the tab size
   SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER,              // Sort by the tab index number
   SORT_BY_CANV_ELEMENT_TAB_PAGE_ROW,                 // Sort by tab row index
   SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN,              // Sort by tab column index
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Sort by the location of the object inside the control
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
   SORT_BY_CANV_ELEMENT_TEXT,                         // Sort by graphical element text
   SORT_BY_CANV_ELEMENT_DESCRIPTION,                  // Sort by graphical element description
  };
//+------------------------------------------------------------------+

これで、同じ行にあるすべてのタブヘッダーをすばやく見つけてリストに追加し、それらの合計サイズを計算して、コントロールのサイズに合わせて伸ばして正しく配置できるようになりました。


オブジェクトに新しいプロパティを追加する場合は、それらの説明を追加する必要があります。プロパティの説明はすべて1つの多次元配列に配置されます。最初の次元にはメッセージインデックスが含まれ、残りの次元にはさまざまな言語のテキストが含まれます。現時点では、英語とロシア語のメッセージテキストがありますが、配列の次元を増やして対応する言語のテキストを必要な次元に追加することで、他の言語を簡単に追加できます。

\MQL5\Include\DoEasy\Data.mqhに、新しいメッセージインデックスを追加します

   MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE,               // Tab size setting mode
   MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,             // Tab index number
   MSG_CANV_ELEMENT_PROP_TAB_PAGE_ROW,                // Tab row index
   MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN,             // Tab column index
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Location of an object inside the control
//--- Real properties of graphical elements

//--- String properties of graphical elements
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Graphical element object name
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Graphical resource name
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Graphical element text
   MSG_CANV_ELEMENT_PROP_DESCRIPTION,                 // Graphical element description
  };
//+------------------------------------------------------------------+

また、新しく追加されたインデックスに対応するテキストも追加します。

   {"Режим установки размера вкладок","Tab Size Mode"},
   {"Порядковый номер вкладки","Tab ordinal number"},
   {"Номер ряда вкладки","Tab row number"},
   {"Номер столбца вкладки","Tab column number"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   
//--- String properties of graphical elements
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},
   {"Описание графического элемента","Description of the graphic element"},
  };
//+---------------------------------------------------------------------+

ライブラリメッセージクラスとそのデータを格納する概念についてはi別の記事で検討しました。


MQL5標準ライブラリのCCanvasクラスを使用する場合、常にエラーのコードを取得できるとは限らず、グラフィック要素を作成できませんでした。要素が作成されなかった理由をユーザーが理解できるように、ライブラリに修正を徐々に追加しています。

\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhグラフィック要素ファイルにある幅と高さを設定するメソッドで、サイズ変更エラーの説明を追加し、エラーメッセージを表示します。

//+------------------------------------------------------------------+
//| Set a new width                                                  |
//+------------------------------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width)
      return true;
   if(!this.m_canvas.Resize(width,this.m_canvas.Height()))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width);
   return true;
  }
//+------------------------------------------------------------------+
//| Set a new height                                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height)
      return true;
   if(!this.m_canvas.Resize(this.m_canvas.Width(),height))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height);
   return true;
  }
//+------------------------------------------------------------------+

ここで、マクロ置換は、サイズ変更できなかった要素のタイプの説明と、メソッドに渡されたパラメータ値を受け取ります。これにより、ライブラリクラスを開発する際のエラーを理解しやすくなります。


グラフィック要素の2つの新しいプロパティの説明を表示できるようにするために、\MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqhにある要素の整数プロパティの説明を返すメソッドにコードブロックを追加します。

//+------------------------------------------------------------------+
//| Return the description of the control integer property           |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_ID                           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TYPE                         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeElementDescription()
         )  :
      
      //---...
      //---...

      property==CANV_ELEMENT_PROP_TAB_SIZE_MODE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TAB_PAGE_ROW                 ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_ROW)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TAB_PAGE_COLUMN              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

ここで、メソッドに渡されたプロパティに応じて、テキストメッセージを作成し、メソッドから返します。


次に、TabControl WinFormsオブジェクトのクラスを完成させましょう。

オブジェクトは、タブが配置されているコンテナで構成されています。コンテナは、タブフィールドとそのヘッダーという2つの補助オブジェクトで構成されます。タブに配置する必要があるすべてのオブジェクトをタブフィールドに配置し、表示して操作するタブをタブヘッダーとして選択します。タブヘッダーは、\MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqhのButtonWinFormsオブジェクトから派生したTabHeaderクラスで実装されます。

これで、一般的なヘッダーリスト(コントロールに配置するために使用されるヘッダー行と列)のタブヘッダーの位置を示す2つの新しいプロパティができたのでこれらのプロパティを格納した2つのprivate変数を削除します。

//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
private:
   int               m_width_off;                        // Object width in the released state
   int               m_height_off;                       // Object height in the released state
   int               m_width_on;                         // Object width in the selected state
   int               m_height_on;                        // Object height in the selected state
   int               m_col;                              // Header column index
   int               m_row;                              // Header row index
//--- Adjust the size and location of the element depending on the state
   bool              WHProcessStateOn(void);
   bool              WHProcessStateOff(void);
//--- Draws a header frame depending on its position
   virtual void      DrawFrame(void);
//--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right
   void              CorrectSelectedRowTop(void);
   void              CorrectSelectedRowBottom(void);
   void              CorrectSelectedRowLeft(void);
   void              CorrectSelectedRowRight(void);
   
protected:


publicメソッドでは、これら2つのプロパティを設定して返すと、以前のように変数ではなく、それらの値がオブジェクトのプロパティに設定されます(そして返されます)

//--- Returns the control size
   int               WidthOff(void)                                                    const { return this.m_width_off;          }
   int               HeightOff(void)                                                   const { return this.m_height_off;         }
   int               WidthOn(void)                                                     const { return this.m_width_on;           }
   int               HeightOn(void)                                                    const { return this.m_height_on;          }
//--- (1) Set and (2) return the Tab row index
   void              SetRow(const int value)                { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_ROW,value);            }
   int               Row(void)                        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_ROW);      }
//--- (1) Set and (2) return the Tab column index
   void              SetColumn(const int value)             { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_COLUMN,value);         }
   int               Column(void)                     const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_COLUMN);   }
//--- Set the tab location
   void              SetTabLocation(const int row,const int col)
                       {
                        this.SetRow(row);
                        this.SetColumn(col);
                       }


タブヘッダーオブジェクトはコンストラクタで既定のサイズ値で作成され、オブジェクトコンテナクラスで設定されたヘッダーサイズモードに合わせてサイズ変更されます。これは、ライブラリのすべてのWinFormsオブジェクトに対して、作成後に特定のオブジェクトに属する追加のパラメータを設定する際に、すべてのオブジェクトに共通のパラメータの同じ値を使用するためです。これは便利ですが制限を伴います。便利なのは1つのメソッドで任意のオブジェクトを作成できるということです。その一方、プロパティの目的の値を使用してオブジェクトをすぐに構築できるとは限らず、オブジェクトの作成後にそれらを取り付ける必要があるという制限があります。

この場合、これはサイズ設定モードに依存するヘッダーサイズに関係します。ここでは、指定された座標で最初に作成されたオブジェクトのサイズがさらに変化し、構築されたオブジェクトの左上隅にある最初の座標が当初計画されていた場所ではなくなっているという事実に直面します。したがって、ヘッダーのサイズを変更するだけでなく、目的の座標でヘッダーの位置を制御する必要もあります。場合によっては、オブジェクトが移動し、そのコンテナを超えているからです。

Normalサイズモードでは、ヘッダーが受け取るサイズを事前に確認できます。このモードでは、オブジェクトのサイズはそれに書かれたテキストに合わせて調整され、このテキストは既知です。ヘッダーがコンテナの上部と下部にある場合、オブジェクトに設定されたPadding値(PaddingLeftとPaddingRight)は、オブジェクトのテキストサイズによって計算された幅、PaddingTopとPaddingBottomは高さに追加されます。.ヘッダーがコンテナの左右(垂直方向)に配置されている場合、オブジェクトの高さにPaddingLeftとPaddingRightが追加され、幅にPaddingTopとPaddingBottomが追加されます。そのテキストは垂直であるため、グラフィック要素の実際の高さは、垂直に回転したオブジェクトの表示幅です。

すべてのヘッダーサイズを設定するメソッドに変更を加えてみましょう。ヘッダーサイズを通常モードのテキストサイズに設定するためのコードブロックで、ヘッダーが配置されているコンテナの側面の制御を実装します上部と下部にヘッダーを設定するために、Padding値が正しい順序で追加されます。Paddingは幅に左右で追加され、高さの場合は上下に追加されます。ヘッダーを左右に配置する場合、幅と高さに追加されるPadding値が交換されます。上下のPaddingが幅に追加され、左右のPaddingが高さに追加されます。

//--- Depending on the header size setting mode
   switch(this.TabSizeMode())
     {
      //--- set the width and height for the Normal mode
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :
        switch(this.Alignment())
          {
           case CANV_ELEMENT_ALIGNMENT_TOP      :
           case CANV_ELEMENT_ALIGNMENT_BOTTOM   :
              this.TextSize(this.Text(),width,height);
              width+=this.PaddingLeft()+this.PaddingRight();
              height=h+this.PaddingTop()+this.PaddingBottom();
             break;
           case CANV_ELEMENT_ALIGNMENT_LEFT     :
           case CANV_ELEMENT_ALIGNMENT_RIGHT    :
              this.TextSize(this.Text(),height,width);
              height+=this.PaddingLeft()+this.PaddingRight();
              width=w+this.PaddingTop()+this.PaddingBottom();
             break;
           default:
             break;
          }
        break;
      //---CANV_ELEMENT_TAB_SIZE_MODE_FIXED
      //---CANV_ELEMENT_TAB_SIZE_MODE_FILL
      //--- For the Fixed mode, the dimensions remain specified,
      //--- In case of Fill, they are calculated in the StretchHeaders methods of the TabControl class
      default: break;
     }
//--- Set the results of changing the width and height to 'res'


メソッドの最後に、選択された状態と選択されていない状態のヘッダーのサイズがまったく同じ方法で設定されます。ヘッダーをクリックしてタブを選択すると、選択したタブのヘッダーのサイズが3辺で2ピクセル増加するため、水平に配置されたヘッダーの実際のサイズは、幅が4ピクセル増加し、高さが2ピクセル増加する必要があります。ヘッダーが垂直に配置されている場合、実際のオブジェクトの幅は垂直方向に回転されたヘッダーの高さであり、実際の高さはヘッダーの幅です。この場合、幅は2ピクセル増加し、高さは4ピクセル増加します。

//--- Set the changed size for different button states
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
         this.SetWidthOn(this.Width()+4);
         this.SetHeightOn(this.Height()+2);
         this.SetWidthOff(this.Width());
         this.SetHeightOff(this.Height());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
         this.SetWidthOn(this.Width()+2);
         this.SetHeightOn(this.Height()+4);
         this.SetWidthOff(this.Width());
         this.SetHeightOff(this.Height());
        break;
      default:
        break;
     }


すべての変更が実装されたメソッドは次のようになります。

//+------------------------------------------------------------------+
//| Set all header sizes                                             |
//+------------------------------------------------------------------+
bool CTabHeader::SetSizes(const int w,const int h)
  {
//--- If the passed width or height is less than 4 pixels, 
//--- make them equal to four pixels
   int width=(w<4 ? 4 : w);
   int height=(h<4 ? 4 : h);
//--- Depending on the header size setting mode
   switch(this.TabSizeMode())
     {
      //--- set the width and height for the Normal mode
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :
        switch(this.Alignment())
          {
           case CANV_ELEMENT_ALIGNMENT_TOP      :
           case CANV_ELEMENT_ALIGNMENT_BOTTOM   :
              this.TextSize(this.Text(),width,height);
              width+=this.PaddingLeft()+this.PaddingRight();
              height=h+this.PaddingTop()+this.PaddingBottom();
             break;
           case CANV_ELEMENT_ALIGNMENT_LEFT     :
           case CANV_ELEMENT_ALIGNMENT_RIGHT    :
              this.TextSize(this.Text(),height,width);
              height+=this.PaddingLeft()+this.PaddingRight();
              width=w+this.PaddingTop()+this.PaddingBottom();
             break;
           default:
             break;
          }
        break;
      //---CANV_ELEMENT_TAB_SIZE_MODE_FIXED
      //---CANV_ELEMENT_TAB_SIZE_MODE_FILL
      //--- For the Fixed mode, the dimensions remain specified,
      //--- In case of Fill, they are calculated in the StretchHeaders methods of the TabControl class
      default: break;
     }
//--- Set the results of changing the width and height to 'res'
   bool res=true;
   res &=this.SetWidth(width);
   res &=this.SetHeight(height);
//--- If there is an error in changing the width or height, return 'false'
   if(!res)
      return false;
//--- Set the changed size for different button states
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
         this.SetWidthOn(this.Width()+4);
         this.SetHeightOn(this.Height()+2);
         this.SetWidthOff(this.Width());
         this.SetHeightOff(this.Height());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
         this.SetWidthOn(this.Width()+2);
         this.SetHeightOn(this.Height()+4);
         this.SetWidthOff(this.Width());
         this.SetHeightOff(this.Height());
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+


「選択」状態の要素の大きさと位置を位置に応じて調整するメソッドで、タイトルがコントロールの左右にある場合の拡大されたヘッダーのシフトは、これまで実装していませんでした。さらに、一番下のヘッダー位置処理ブロックで、'break演算子を省略するという小さなエラーを犯しました。すべてのケースが空でコードが呼び出されなかったため、エラーは発生しませんでした。省略されたbreakステートメントに続くケースが処理されるという、誤った動作が発生するようになります。

ヘッダーを左右に配置するために、拡大されたヘッダーを右方向に2ポイントずらすコードブロックを追加しましょう

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "selected" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOn(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOn())
      return false;
//--- Get the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return false;
//--- Depending on the title location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowTop();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()-2))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative()-2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowBottom();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowLeft();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()-2))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative()-2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowRight();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX(),this.CoordY()-2))
          {
           this.SetCoordXRelative(this.CoordXRelative());
           this.SetCoordYRelative(this.CoordYRelative()-2);
          }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+


同様に、「選択されていない」状態の要素のサイズと位置をその位置に応じて調整するメソッドを仕上げます。選択中に上記のメソッドでサイズを大きくするときにヘッダーを移動した後に、復元されたサイズのヘッダーを元の位置に戻すコードブロックを追加します

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "released" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOff(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOff())
      return false;
//--- Depending on the title location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()+2))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative()+2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()+2))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative()+2);
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX(),this.CoordY()+2))
          {
           this.SetCoordXRelative(this.CoordXRelative());
           this.SetCoordYRelative(this.CoordYRelative()+2);
          }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

これらの改善後、左または右にあるタブのヘッダーを選択すると、選択時にサイズが正しく大きくなって選択解除時には小さくなります。視覚的に少し大きくなり、視覚的に元の場所に残ります。

タブヘッダーが複数の行に配置されている場合、ヘッダーがタブ自体に直接隣接していないが、他のタブの行のどこかにあるタブを選択すると、行全体を移動する必要があります。選択したタブをタブフィールドの近くに配置し、以前はフィールドに隣接していた行を移動して、その行を選択したヘッダーに置き換えます。以前の記事で、コントロールの上にタブヘッダーを配置する同様のメソッドを既に実装しています。次に、ヘッダーを下、左、右に配置するための同様のメソッドを作成する必要があります。

以下は、選択したタブヘッダーの行を上部の正しい位置に設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the bottom                            |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowBottom(void)
  {
   int row_pressed=this.Row();      // Selected header row
   int y_pressed=this.CoordY();     // Coordinate where all headers with Row() equal to zero should be moved to
   int y0=0;                        // Zero row coordinate (Row == 0)
//--- If the zero row is selected, then nothing needs to be done - leave
   if(row_pressed==0)
      return;
      
//--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line
   CWinFormBase *obj=this.GetFieldObj();
   if(obj==NULL)
      return;
   y0=obj.CoordY()+obj.Height();
   
//--- Get the base object (TabControl)
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all tab headers from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   if(list==NULL)
      return;
//--- Swap rows in the loop through all headers -
//--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is a zero row
      if(header.Row()==0)
        {
         //--- move the header to the position of the selected row
         if(header.Move(header.CoordX(),y_pressed))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one
            header.SetRow(-1);
           }
        }
      //--- If this is the clicked header line,
      if(header.Row()==row_pressed)
        {
         //--- move the header to the position of the zero row
         if(header.Move(header.CoordX(),y0))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one
            header.SetRow(-2);
           }
        }
     }
//--- Set the correct Row and Col
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it
      if(header.Row()==-1)
         header.SetRow(row_pressed);
      //--- If this is the selected row moved to the zero position, set Row of the zero row
      if(header.Row()==-2)
         header.SetRow(0);
     }
  }
//+------------------------------------------------------------------+


以下は、選択したタブヘッダーバーを正しい左位置に設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the left                              |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowLeft(void)
  {
   int row_pressed=this.Row();      // Selected header row
   int x_pressed=this.CoordX();     // Coordinate where all headers with Row() equal to zero should be moved to
   int x0=0;                        // Zero row coordinate (Row == 0)
//--- If the zero row is selected, then nothing needs to be done - leave
   if(row_pressed==0)
      return;
      
//--- Get the tab field object corresponding to this header and set the X coordinate of the zero line
   CWinFormBase *obj=this.GetFieldObj();
   if(obj==NULL)
      return;
   x0=obj.CoordX()-this.Width()+2;
   
//--- Get the base object (TabControl)
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all tab headers from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   if(list==NULL)
      return;
//--- Swap rows in the loop through all headers -
//--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is a zero row
      if(header.Row()==0)
        {
         //--- move the header to the position of the selected row
         if(header.Move(x_pressed,header.CoordY()))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one
            header.SetRow(-1);
           }
        }
      //--- If this is the clicked header line,
      if(header.Row()==row_pressed)
        {
         //--- move the header to the position of the zero row
         if(header.Move(x0,header.CoordY()))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one
            header.SetRow(-2);
           }
        }
     }
//--- Set the correct Row and Col
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it
      if(header.Row()==-1)
         header.SetRow(row_pressed);
      //--- If this is the selected row moved to the zero position, set Row of the zero row
      if(header.Row()==-2)
         header.SetRow(0);
     }
  }
//+------------------------------------------------------------------+


以下は、選択したタブヘッダーバーを正しい右位置に設定するメソッドです。

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the right                             |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowRight(void)
  {
   int row_pressed=this.Row();      // Selected header row
   int x_pressed=this.CoordX();     // Coordinate where all headers with Row() equal to zero should be moved to
   int x0=0;                        // Zero row coordinate (Row == 0)
//--- If the zero row is selected, then nothing needs to be done - leave
   if(row_pressed==0)
      return;
      
//--- Get the tab field object corresponding to this header and set the X coordinate of the zero line
   CWinFormBase *obj=this.GetFieldObj();
   if(obj==NULL)
      return;
   x0=obj.RightEdge();
   
//--- Get the base object (TabControl)
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all tab headers from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   if(list==NULL)
      return;
//--- Swap rows in the loop through all headers -
//--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is a zero row
      if(header.Row()==0)
        {
         //--- move the header to the position of the selected row
         if(header.Move(x_pressed,header.CoordY()))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one
            header.SetRow(-1);
           }
        }
      //--- If this is the clicked header line,
      if(header.Row()==row_pressed)
        {
         //--- move the header to the position of the zero row
         if(header.Move(x0,header.CoordY()))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one
            header.SetRow(-2);
           }
        }
     }
//--- Set the correct Row and Col
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it
      if(header.Row()==-1)
         header.SetRow(row_pressed);
      //--- If this is the selected row moved to the zero position, set Row of the zero row
      if(header.Row()==-2)
         header.SetRow(0);
     }
  }
//+------------------------------------------------------------------+

前回の記事で、多数のヘッダーがコントロールの上にあるときにそれらを移動する同様のメソッドを検討しました。これらの新しいメソッドの背後にあるロジックはまったく同じですが、下の配置ではヘッダー行をY軸に沿って移動し、左右の配置ではX軸に沿って移動します。ここで、メソッドのロジックは、コードへのコメントで詳細に説明されています。


ヘッダーをクリックしてタブを選択すると、ヘッダーがわずかに大きくなり、必要に応じてヘッダー行のリストからタブフィールドの近くに転送され、その結果、ヘッダー間の境界線(フィールドフレームによる)が表示されます。フィールドとヘッダーが1つの不可分な全体のように見えるように、タブフィールドが消去されます。前回の記事でフィールドとヘッダーの境界線を消しましたが、上下のヘッダーの位置でだけでした。フィールドとヘッダーが左右にある場合のヘッダーとの間の境界線の消去を追加する必要があります。

タブフィールドオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqhで、ヘッダーの位置に応じてコントロールフレームを描画するメソッドで、左側右側のヘッダーの位置に背景色を使用して線を引くようにします。

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

ここではすべてが簡単です。このフィールドに対応するヘッダーオブジェクトへのポインタを取得し、そのサイズからそのサイズを取得し、ヘッダーに指定された場所に従って、ヘッダーが隣接するフィールド領域に背景色で線を描画します。視覚的には、これによりフィールドとヘッダーの間の境界線が消去され、2つのオブジェクトが1つとして表示され始めます(TabControlタブは、さらに開発する予定です)。


\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqhのTabControlオブジェクトクラスファイルで、ヘッダー行の幅と高さを伸ばすための4つのprivateメソッドを宣言します

//--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right
   void              ArrangeTabHeadersTop(void);
   void              ArrangeTabHeadersBottom(void);
   void              ArrangeTabHeadersLeft(void);
   void              ArrangeTabHeadersRight(void);
//--- Stretch tab headers by control size
   void              StretchHeaders(void);
//--- Stretch tab headers by (1) control width and height when positioned on the (2) left and (3) right
   void              StretchHeadersByWidth(void);
   void              StretchHeadersByHeightLeft(void);
   void              StretchHeadersByHeightRight(void);
public:


これらのメソッドをクラス本体の外側で実装しましょう。

以下は、タブヘッダーをコントロールのサイズに伸ばすメソッド:です。

//+------------------------------------------------------------------+
//| Stretch tab headers by control size                              |
//+------------------------------------------------------------------+
void CTabControl::StretchHeaders(void)
  {
//--- Leave if the headers are in one row
   if(!this.Multiline())
      return;
//--- Depending on the location of headers
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.StretchHeadersByWidth();
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        this.StretchHeadersByHeightLeft();
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        this.StretchHeadersByHeightRight();
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

このメソッドは、タブヘッダーの場所に応じて適切なメソッドを呼び出すだけです。幅を伸ばす場合は、すべてのヘッダーが常に左から右に配置されるため、1つのメソッドで十分ですが、高さを伸ばす場合は、ヘッダーがどちら側に配置されるかが重要です。左に配置されている場合は下から上に配置され、左に配置されている場合は上から下に配置されます。したがって、左右に配置されたヘッダーの高さによって伸ばすための2つの別々のメソッドがあります。

以下は、コントロールの幅でタブヘッダーを伸ばすメソッド:です。

//+------------------------------------------------------------------+
//| Stretch tab headers by control width                             |
//+------------------------------------------------------------------+
void CTabControl::StretchHeadersByWidth(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
   if(last==NULL)
      return;
//--- In the loop by the number of header rows
   for(int i=0;i<last.Row()+1;i++)
     {
      //--- Get the list with the row index equal to the loop index
      CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL);
      if(list_row==NULL)
         continue;
      //--- Get the width of the container, as well as the number of headers in a row, and calculate the width of each header
      int base_size=this.Width()-4;
      int num=list_row.Total();
      int w=base_size/(num>0 ? num : 1);
      //--- In the loop by row headers
      for(int j=0;j<list_row.Total();j++)
        {
         //--- Get the current and previous headers from the list by loop index
         CTabHeader *header=list_row.At(j);
         CTabHeader *prev=list_row.At(j-1);
         if(header==NULL)
            continue;
         //--- If the header size is changed
         if(header.Resize(w,header.Height(),false))
           {
            //--- Set new sizes for the header for pressed/unpressed states
            header.SetWidthOn(w+4);
            header.SetWidthOff(w);
            //--- If this is the first header in the row (there is no previous header in the list),
            //--- then it is not necessary to shift it - move on to the next iteration
            if(prev==NULL)
               continue;
            //--- Shift the header to the coordinate of the right edge of the previous header
            if(header.Move(prev.RightEdge(),header.CoordY()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

ここでは、最初にヘッダー行の数を調べます。この数はリストから最後のヘッダーを取得することで見つけることができます。これには、Rowプロパティの行のインデックスが含まれます。行インデックスは0から始まるため、結果の値に1を追加して行数を指定します
次に、各行にあるヘッダーのリストを取得し、その中のすべてのヘッダーをオブジェクトの幅に伸ばす必要があります。オブジェクトのプロパティにRowとColumnの値を追加したので​。1つの行のヘッダーのリストを取得することが非常に簡単になりました。すべてのヘッダーのリストを行の値で並べ替え、指定された行インデックスを持つオブジェクトへのポインタを含むリストを取得します。結果のリストによるループで、各ヘッダーの幅を以前に計算された値(コンテナの幅を行のヘッダーの数で割った値)に変更します。コンテナの幅全体を使用する代わりに左右から2ピクセルを削除して、極端なヘッダーが選択されてサイズが大きくなったときにコンテナを超えないようにします。事前に不明な値でサイズを割るので、除数が値と一致するかどうかを確認し、0の場合に備えて1で割り、0で割らないようにします。前のヘッダーがリストに存在しない場合(ループインデックスが最初のヘッダーを指している場合)、ヘッダーはどこにも移動する必要がありません。すべての後続のものは前のヘッダーの右端に移動する必要がありますが、それはそのまま残ります。すべてのヘッダー幅が大きくなり、互いに重なり合うように変更されました。

以下は、左に配置されたときに、コントロールの高さに合わせてタブヘッダーを伸ばすメソッド:です。

//+------------------------------------------------------------------+
//| Stretch tab headers by control height                            |
//| when placed on the left                                          |
//+------------------------------------------------------------------+
void CTabControl::StretchHeadersByHeightLeft(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
   if(last==NULL)
      return;
//--- In the loop by the number of header rows
   for(int i=0;i<last.Row()+1;i++)
     {
      //--- Get the list with the row index equal to the loop index
      CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL);
      if(list_row==NULL)
         continue;
      //--- Get the height of the container, as well as the number of headers in a row, and calculate the height of each header
      int base_size=this.Height()-4;
      int num=list_row.Total();
      int h=base_size/(num>0 ? num : 1);
      //--- In the loop by row headers
      for(int j=0;j<list_row.Total();j++)
        {
         //--- Get the current and previous headers from the list by loop index
         CTabHeader *header=list_row.At(j);
         CTabHeader *prev=list_row.At(j-1);
         if(header==NULL)
            continue;
         //--- Save the initial header height
         int h_prev=header.Height();
         //--- If the header size is changed
         if(header.Resize(header.Width(),h,false))
           {
            //--- Set new sizes for the header for pressed/unpressed states
            header.SetHeightOn(h+4);
            header.SetHeightOff(h);
            //--- If this is the first header in the row (there is no previous header in the list)
            if(prev==NULL)
              {
               //--- Calculate the Y offset
               int y_shift=header.Height()-h_prev;
               //--- Shift the header by its calculated offset and move on to the next one
               if(header.Move(header.CoordX(),header.CoordY()-y_shift))
                 {
                  header.SetCoordXRelative(header.CoordX()-this.CoordX());
                  header.SetCoordYRelative(header.CoordY()-this.CoordY());
                 }
               continue;
              }
            //--- Move the header by the coordinate of the top edge of the previous header minus the height of the current one and its calculated offset
            if(header.Move(header.CoordX(),prev.CoordY()-header.Height()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

メソッドのロジックは前のものと似ていますが、ここでは少し複雑です。ヘッダーはコンテナの下端から左に配置され、ヘッダーのアンカーポイントは左上隅にあるため、サイズを変更すると、ヘッダーの下端がコンテナの下端より下になります。したがって、ここでは、最初のヘッダーを計算されたオフセットだけ上に移動する必要があります。これをおこなうには、ヘッダーの高さを変更する前に保存し、サイズ変更後にサイズがどれだけ変更されたかを計算する必要があります。取得した値を使用して、最初のヘッダーをY軸に沿って移動し、その下端がコンテナを超えないようにします。


以下は、右に配置されたときに、コントロールの高さに合わせてタブヘッダーを伸ばすメソッド:です。

//+------------------------------------------------------------------+
//| Stretch tab headers by control height                            |
//| when placed on the right                                         |
//+------------------------------------------------------------------+
void CTabControl::StretchHeadersByHeightRight(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
   if(last==NULL)
      return;
//--- In the loop by the number of header rows
   for(int i=0;i<last.Row()+1;i++)
     {
      //--- Get the list with the row index equal to the loop index
      CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL);
      if(list_row==NULL)
         continue;
      //--- Get the height of the container, as well as the number of headers in a row, and calculate the height of each header
      int base_size=this.Height()-4;
      int num=list_row.Total();
      int h=base_size/(num>0 ? num : 1);
      //--- In the loop by row headers
      for(int j=0;j<list_row.Total();j++)
        {
         //--- Get the current and previous headers from the list by loop index
         CTabHeader *header=list_row.At(j);
         CTabHeader *prev=list_row.At(j-1);
         if(header==NULL)
            continue;
         //--- If the header size is changed
         if(header.Resize(header.Width(),h,false))
           {
            //--- Set new sizes for the header for pressed/unpressed states
            header.SetHeightOn(h+4);
            header.SetHeightOff(h);
            //--- If this is the first header in the row (there is no previous header in the list),
            //--- then it is not necessary to shift it - move on to the next iteration
            if(prev==NULL)
               continue;
            //--- Shift the header to the coordinate of the bottom edge of the previous header
            if(header.Move(header.CoordX(),prev.BottomEdge()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

このメソッドは、ヘッダーをコンテナの幅に合わせるメソッドと同じですが、ここではヘッダーを高さ方向に伸ばします。ここではヘッダーが左側にあり、レポートが上から下にあるため、サイズを変更した後に最初のヘッダーの位置を調整する必要はありません。最初の座標は配置ポイントの座標と一致し、オブジェクトは、コンテナを超えて上昇することなく、下方に増加します。

ヘッダーの位置に基づいて初期座標とサイズを計算する必要があるため、指定された数のタブを作成するメソッドが変更されました。ヘッダーを左右に配置するために、メソッドに渡されたヘッダーの高さと幅をそれに応じて幅と高さに割り当てますヘッダーが左側にある場合はヘッダーを垂直に90°回転させ、右側にある場合は270回転させます

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=0;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=0;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());

      
      //--- Save the initial height of the header and set its size in accordance with the header size setting mode
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_w=this.Width();
      int field_h=this.Height()-header.Height();
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height();
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height();
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width();
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width();
           break;
         default:
           break;
        }
      
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

メソッドのロジックと改善点はコードへのコメントで説明され、主な機能はメソッドコードの前に示され、色で強調表示されています。左側のヘッダーを見つけるには、変更する前にヘッダーサイズを保存し、オフセットを計算して、サイズ変更されたヘッダーを正しい位置に移動する必要があります。

前回の記事で説明した、タブヘッダーを上に配置するメソッドも次のように変更されました。

//+------------------------------------------------------------------+
//| Arrange tab headers on top                                       |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersTop(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int x1_base=2;                            // Initial X coordinate
   int x2_base=this.RightEdgeRelative()-2;   // Final X coordinate
   int x_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- Calculate the value of the right edge of the header, taking into account that
         //--- the origin always comes from the left edge of TabControl + 2 pixels
         int x2=header.RightEdgeRelative()-x_shift;
         //--- If the calculated value does not go beyond the right edge of the TabControl minus 2 pixels, 
         //--- set the column number equal to the loop index minus the value in the n variable
         if(x2<x2_base)
            col=i-n;
         //--- If the calculated value goes beyond the right edge of the TabControl minus 2 pixels,
         else
           {
            //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
            //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
            row++;
            x_shift=header.CoordXRelative()-2;
            n=i;
            col=0;
           }
         //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
         header.SetTabLocation(row,col);
         if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height()))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }

//--- The location of all tab titles is set. Now place them all together with the fields
//--- according to the header row and column indices.

//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received
   if(last!=NULL)
     {
      //--- If the mode of stretching headers to the width of the container is set, call the stretching method
      if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL)
         this.StretchHeaders();
      //--- If this is not the first row (with index 0)
      if(last.Row()>0)
        {
         //--- Calculate the offset of the tab field Y coordinate
         int y_shift=last.Row()*last.Height();
         //--- In a loop by the list of headers,
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next object
            CTabHeader *header=list.At(i);
            if(header==NULL)
               continue;
            //--- get the tab field corresponding to the received header
            CTabField  *field=header.GetFieldObj();
            if(field==NULL)
               continue;
            //--- shift the tab header by the calculated row coordinates
            if(header.Move(header.CoordX(),header.CoordY()+y_shift))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
            //--- shift the tab field by the calculated shift
            if(field.Move(field.CoordX(),field.CoordY()+y_shift))

              {
               field.SetCoordXRelative(field.CoordX()-this.CoordX());
               field.SetCoordYRelative(field.CoordY()-this.CoordY());
               //--- change the size of the shifted field by the value of its shift
               field.Resize(field.Width(),field.Height()-y_shift,false);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

メソッドのロジックは、コードのコメントに完全に記述されているので、ここでは繰り返しません。


以下は、タブヘッダーを下部に配置するメソッドです。

//+------------------------------------------------------------------+
//| Arrange tab headers at the bottom                                |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersBottom(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int x1_base=2;                            // Initial X coordinate
   int x2_base=this.RightEdgeRelative()-2;   // Final X coordinate
   int x_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- Calculate the value of the right edge of the header, taking into account that
         //--- the origin always comes from the left edge of TabControl + 2 pixels
         int x2=header.RightEdgeRelative()-x_shift;
         //--- If the calculated value does not go beyond the right edge of the TabControl minus 2 pixels, 
         //--- set the column number equal to the loop index minus the value in the n variable
         if(x2<x2_base)
            col=i-n;
         //--- If the calculated value goes beyond the right edge of the TabControl minus 2 pixels,
         else
           {
            //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
            //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
            row++;
            x_shift=header.CoordXRelative()-2;
            n=i;
            col=0;
           }
         //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
         header.SetTabLocation(row,col);
         if(header.Move(header.CoordX()-x_shift,header.CoordY()+header.Row()*header.Height()))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }

//--- The location of all tab titles is set. Now place them all together with the fields
//--- according to the header row and column indices.

//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received
   if(last!=NULL)
     {
      //--- If the mode of stretching headers to the width of the container is set, call the stretching method
      if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL)
         this.StretchHeaders();
      //--- If this is not the first row (with index 0)
      if(last.Row()>0)
        {
         //--- Calculate the offset of the tab field Y coordinate
         int y_shift=last.Row()*last.Height();
         //--- In a loop by the list of headers,
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next object
            CTabHeader *header=list.At(i);
            if(header==NULL)
               continue;
            //--- get the tab field corresponding to the received header
            CTabField  *field=header.GetFieldObj();
            if(field==NULL)
               continue;
            //--- shift the tab header by the calculated row coordinates
            if(header.Move(header.CoordX(),header.CoordY()-y_shift))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
            //--- shift the tab field by the calculated shift
            if(field.Move(field.CoordX(),field.CoordY()))
              {
               field.SetCoordXRelative(field.CoordX()-this.CoordX());
               field.SetCoordYRelative(field.CoordY()-this.CoordY());
               //--- change the size of the shifted field by the value of its shift
               field.Resize(field.Width(),field.Height()-y_shift,false);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

このメソッドは、ヘッダーを上に置くメソッドと同じです。唯一の違いは、ヘッダー行のオフセットの方向です。これは、ヘッダーが下部に配置され、前のメソッドとは逆に移動するためです。


以下は、左側のタブヘッダーを検索するメソッドです。

//+------------------------------------------------------------------+
//| Arrange tab headers on the left                                  |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersLeft(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int y1_base=this.BottomEdgeRelative()-2;  // Initial Y coordinate
   int y2_base=2;                            // Final Y coordinate
   int y_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- Calculate the value of the upper edge of the header, taking into account that
         //--- the origin always comes from the bottom edge of TabControl minus 2 pixels
         int y2=header.CoordYRelative()+y_shift;
         //--- If the calculated value does not go beyond the upper edge of the TabControl minus 2 pixels, 
         //--- set the column number equal to the loop index minus the value in the n variable
         if(y2>=y2_base)
            col=i-n;
         //--- If the calculated value goes beyond the upper edge of the TabControl minus 2 pixels,
         else
           {
            //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
            //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
            row++;
            y_shift=this.BottomEdge()-header.BottomEdge()-2;
            n=i;
            col=0;
           }
         //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
         header.SetTabLocation(row,col);
         if(header.Move(header.CoordX()-header.Row()*header.Width(),header.CoordY()+y_shift))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }

//--- The location of all tab titles is set. Now place them all together with the fields
//--- according to the header row and column indices.

//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received
   if(last!=NULL)
     {
      //--- If the mode of stretching headers to the width of the container is set, call the stretching method
      if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL)
         this.StretchHeaders();
      //--- If this is not the first row (with index 0)
      if(last.Row()>0)
        {
         //--- Calculate the offset of the tab field X coordinate
         int x_shift=last.Row()*last.Width();
         //--- In a loop by the list of headers,
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next object
            CTabHeader *header=list.At(i);
            if(header==NULL)
               continue;
            //--- get the tab field corresponding to the received header
            CTabField  *field=header.GetFieldObj();
            if(field==NULL)
               continue;
            //--- shift the tab header by the calculated row coordinates
            if(header.Move(header.CoordX()+x_shift,header.CoordY()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
            //--- shift the tab field by the calculated shift
            if(field.Move(field.CoordX()+x_shift,field.CoordY()))
              {
               field.SetCoordXRelative(field.CoordX()-this.CoordX());
               field.SetCoordYRelative(field.CoordY()-this.CoordY());
               //--- change the size of the shifted field by the value of its shift
               field.Resize(field.Width()-x_shift,field.Height(),false);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

ここでは、ヘッダーが左側にあり、行がX軸に沿ってシフトされています。それ以外、ロジックは前のメソッドと同じです。


以下は、タブヘッダーを右に配置するメソッドです。

//+------------------------------------------------------------------+
//| Arrange tab headers to the right                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersRight(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int y1_base=2;                            // Initial Y coordinate
   int y2_base=this.BottomEdgeRelative()-2;  // Final Y coordinate
   int y_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- Calculate the value of the bottom edge of the header, taking into account that
         //--- the origin always comes from the upper edge of TabControl + 2 pixels
         int y2=header.BottomEdgeRelative()-y_shift;
         //--- If the calculated value does not go beyond the bottom edge of the TabControl minus 2 pixels, 
         //--- set the column number equal to the loop index minus the value in the n variable
         if(y2<y2_base)
            col=i-n;
         //--- If the calculated value goes beyond the bottom edge of the TabControl minus 2 pixels,
         else
           {
            //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl bottom edge + 2 pixels),
            //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
            row++;
            y_shift=header.CoordYRelative()-2;
            n=i;
            col=0;
           }
         //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
         header.SetTabLocation(row,col);
         if(header.Move(header.CoordX()+header.Row()*header.Width(),header.CoordY()-y_shift))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }

//--- The location of all tab titles is set. Now place them all together with the fields
//--- according to the header row and column indices.

//--- Get the last title in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received
   if(last!=NULL)
     {
      //--- If the mode of stretching headers to the width of the container is set, call the stretching method
      if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL)
         this.StretchHeaders();
      //--- If this is not the first row (with index 0)
      if(last.Row()>0)
        {
         //--- Calculate the offset of the tab field X coordinate
         int x_shift=last.Row()*last.Width();
         //--- In a loop by the list of headers,
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next object
            CTabHeader *header=list.At(i);
            if(header==NULL)
               continue;
            //--- get the tab field corresponding to the received header
            CTabField  *field=header.GetFieldObj();
            if(field==NULL)
               continue;
            //--- shift the tab header by the calculated row coordinates
            if(header.Move(header.CoordX()-x_shift,header.CoordY()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
               //--- change the tab field size to the X offset value
               field.Resize(field.Width()-x_shift,field.Height(),false);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

ロジックは前のメソッドと似ていますが、ヘッダーが右側にあるため、行オフセットがミラーリングされています。

上記のメソッドはすべて、コード内で詳細にコメントされています。ご自分でご覧ください。質問がある場合は、下のコメント欄でお気軽にお問い合わせください。

次に、すべての変更と改善をテストします。タブのヘッダーを1行に配置するには、グラフィック要素の表示/非表示部分をトリミングできる必要があります。したがって、多くのタブがあるときにヘッダーを1行に配置するモード((Multilineモードがオフ)を選択すると、すべてのヘッダーがコントロールを超えて整列されます。この問題については、後続の記事で扱います。このモードで考慮されるクラスのメソッドには「スタブ」を残してあるので、このモードを処理するコードを入力します。

検証

テストを実行するには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part116\TestDoEasy116.mq5として保存します。

EA入力で、Multilineモードとタブヘッダーが配置される側を指定する変数を追加します

//--- input parameters
sinput   bool                          InpMovable           =  true;                   // Panel Movable flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize          =  INPUT_YES;              // Panel Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode      =  AUTO_SIZE_MODE_GROW;    // Panel Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle        =  BORDER_STYLE_SIMPLE;    // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign         =  ANCHOR_CENTER;          // Label text align
sinput   ENUM_INPUT_YES_NO             InpTextAutoSize      =  INPUT_NO;               // Label autosize
sinput   ENUM_ANCHOR_POINT             InpCheckAlign        =  ANCHOR_LEFT;            // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign    =  ANCHOR_LEFT;            // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState        =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize     =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle   =  BORDER_STYLE_NONE;      // CheckBox border style
sinput   ENUM_ANCHOR_POINT             InpButtonTextAlign   =  ANCHOR_CENTER;          // Button text align
sinput   ENUM_INPUT_YES_NO             InpButtonAutoSize    =  INPUT_YES;              // Button autosize
sinput   ENUM_AUTO_SIZE_MODE           InpButtonAutoSizeMode=  AUTO_SIZE_MODE_GROW;    // Button Autosize mode
sinput   ENUM_BORDER_STYLE             InpButtonFrameStyle  =  BORDER_STYLE_NONE;      // Button border style
sinput   bool                          InpButtonToggle      =  true ;                  // Button toggle flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
sinput   bool                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpTabCtrlMultiline  =  true;                   // Tab Control Multiline flag
sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
sinput   ENUM_ELEMENT_TAB_SIZE_MODE    InpTabPageSizeMode   =  ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode
//--- global variables


作成したパネルの幅を少し広げてみましょう(10ピクセルずつ)。

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {

2番目のGroupBoxコンテナの幅も12ピクセル分広げます。

      //--- Create the GroupBox2 WinForms object
      CGroupBox *gbox2=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2
      w=gbox1.Width()+12;
      int x=gbox1.RightEdgeRelative()+1;
      int h=gbox1.BottomEdgeRelative()-6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false))
        {

これをおこなう唯一の理由は、グラフィっク要素の非表示部分をトリミングする機能がないことです。親オブジェクトに配置され、コンテナ(親オブジェクト)よりもサイズが大きいすべてのWinFormオブジェクトがその制限を超えます。たとえば、タブフィールドに配置されたCheckBoxは、ヘッダーが左側にある場合にタブフィールドの外側に拡張するか、タブフィールドの外側に出て、TabControlの右側のタブヘッダーを覆います。まだ十分な機能がないので、そのような欠点を隠す必要があります。:)

OnInit()ハンドラで、TabControlを作成した後、タブヘッダーの位置と、ヘッダーがEA入力で指定された複数の行に配置される許可を設定します。

            //--- Create the TabControl object
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false);
            //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type
            CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
            //--- If TabControl is created and the pointer to it is received
            if(tab_ctrl!=NULL)
              {
               //--- Set the location of the tab titles on the element and the tab text, as well as create nine tabs
               tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
               tab_ctrl.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
               tab_ctrl.SetMultiline(InpTabCtrlMultiline);
               tab_ctrl.SetHeaderPadding(6,0);
               tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage"));


TabControlの3番目のタブにListBoxコントロールを作成するときは、そのY座標をタブの上部に近づけて設定します

               //--- Create the ListBox object on the third tab
               int lbw=146;
               if(!InpListBoxMColumn)
                  lbw=100;
               tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,2,lbw,60,clrNONE,255,true,false);
               //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type

以前は、オブジェクトは座標12に配置されていたため、タブヘッダーが複数行配置されている場合、下からタブフィールドを超えることになります(タブフィールドのサイズは、ヘッダー行の数の増加に比例して減少するため)。

EAをコンパイルし、チャート上で起動します。


ご覧のとおり、左右のタブタイトルのレイアウトは正しく機能しています。次の記事で説明して修正するいくつかの欠点がありますが、これまでのところすべてが良好です.


次の段階

次の記事では、TabControlに取り組みます。

現在のライブラリバージョン、テストEA、およびMQL5のチャートイベントコントロール指標のすべてのファイルが、テストおよびダウンロードできるように以下に接続されています。質問や提案はコメント欄にお願いします。

目次に戻る

連載のこれまでの記事

DoEasy.コントロール(第10部):WinFormsオブジェクト—インターフェイスのアニメーション化
DoEasy.コントロール(第11部):WinFormsオブジェクト—グループ、CheckedListBoxWinFormsオブジェクト
DoEasy.コントロール(第12部):基本リストオブジェクト、ListBoxおよびButtonListBoxWinFormsオブジェクト
DoEasy.コントロール(第13部):WinFormsオブジェクトとマウスの相互作用を最適化し、TabControl WinFormsオブジェクトの開発を開始
DoEasy.コントロール(第14部):グラフィック要素に名前を付けるための新しいアルゴリズム。TabControl WinFormsオブジェクトの継続作業
DoEasy.コントロール(第15部):TabControl WinFormsオブジェクト—複数行のタブヘッダー、タブ処理メソッド

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

添付されたファイル |
MQL5.zip (4435.36 KB)
ニューラルネットワークが簡単に(第27部):DQN (Deep Q-Learning) ニューラルネットワークが簡単に(第27部):DQN (Deep Q-Learning)
強化学習の研究を続けます。今回は、「Deep Q-Learning」という手法に触れてみましょう。この手法を用いることで、DeepMindチームはアタリ社のコンピューターゲームのプレイで人間を凌駕するモデルを作成することができました。取引上の問題を解決するための技術の可能性を評価するのに役立つと思います。
DIYテクニカル指標 DIYテクニカル指標
この記事では、独自のテクニカル指標を作成できるアルゴリズムについて検討します。非常に単純な初期仮定で、非常に複雑で興味深い結果を得る方法を学びます。
母集団最適化アルゴリズム 母集団最適化アルゴリズム
最適化アルゴリズム(OA)の分類についての入門記事です。この記事では、OAを比較するためのテストスタンド(関数群)を作成し、広く知られたアルゴリズムの中から最も普遍的なものを特定することを試みています。
VIDYAによる取引システムの設計方法を学ぶ VIDYAによる取引システムの設計方法を学ぶ
最も人気のあるテクニカル指標によって取引システムを設計する方法を学ぶ連載の新しい記事へようこそ。この新しい記事では、新しいテクニカルツールについて学び、VIDYA(Variable Index Dynamic Average、可変インデックス動的平均)テクニカル指標によって取引システムを設計する方法を学びます。