English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Guia prático do MQL5: Controles da sub-janela indicadora - barra de rolagem

Guia prático do MQL5: Controles da sub-janela indicadora - barra de rolagem

MetaTrader 5Exemplos | 28 março 2014, 15:06
2 586 0
Anatoli Kazharski
Anatoli Kazharski

Introdução

Vamos continuar a explorar vários controles e desta vez a nossa atenção é para a barra de rolagem. Assim como no artigo anterior "Guia prático do MQL5: Os controles da sub-janela indicadora - botões", vamos trabalhar na sub-janela indicadora. Tome um tempo para ler o artigo acima mencionado, uma vez que fornece uma descrição detalhada do trabalho com eventos na função OnChartEvent() enquanto este ponto somente será casualmente abordado neste artigo. Para fins ilustrativos, desta vez vamos criar uma barra de rolagem vertical para uma grande lista de todas as propriedades de instrumentos financeiros que possam ser obtidas usando recursos do MQL5.

Nos artigos anteriores sobre a programação MQL5 usamos o objeto gráfico OBJ_LABEL (etiqueta de texto) para criar listas. Neste artigo, vamos usar uma tela para exibir texto. A conveniência de tal abordagem reside no fato de, em vez de um grande número de objetos OBJ_LABEL, usaremos apenas uma - OBJ_BITMAP_LABEL (etiqueta Bitmap). Você pode desenhar qualquer elemento de interface em uma tela, mas desta vez, vamos nos limitar somente ao texto.

A barra de rolagem será muito simples. Ela geralmente tem botões de seta, mas eles não serão características no nosso caso. A caixa de rolagem só será composta pelo fundo e pela caixa de rolagem. A caixa de rolagem mudará a sua cor quando o cursor passar sobre ela. Quando clicada, mudará de cor também, sugerindo ao usuário que a caixa de rolagem está agora selecionada e pode ser arrastada. Ao criar objetos de rolagem, usaremos objetos gráficos do tipo OBJ_RECTANGLE_LABEL (Marcador de retângulo).

 

Desenvolvimento de indicador

Vamos começar a programar. Crie um modelo do indicador, como foi feito no artigo anterior. No início, como de costume, precisamos declarar as matrizes e variáveis. Para sermos capazes de trabalhar com a tela, incluímos a classe CCanvas da biblioteca padrão.

#define LIST_SIZE 71                // Number of strings in the list of financial instrument properties
//--- Include the class for working with the canvas
#include <Canvas\Canvas.mqh>
CCanvas canvas;
//+------------------------------------------------------------------+
//|   Global variables                                               |
//+------------------------------------------------------------------+
//--- Indicator subwindow properties
int               subwindow_number              =WRONG_VALUE;                    // Subwindow number
int               subwindow_height              =0;                              // Subwindow height
string            subwindow_shortname           ="TestScrollbar";                // Short name of the indicator
string            prefix                        =subwindow_shortname+"_";        // Prefix for objects
int               chart_width                   =0;                              // Chart width
int               chart_height                  =0;                              // Chart height
int               chart_y_offset                =0;                              // Distance from the chart top to the subwindow
//--- Canvas properties
string            canvas_name                   =prefix+"canvas";                // Canvas name
color             canvas_background_color       =C'20,20,20';                    // Canvas background color
ENUM_COLOR_FORMAT color_format                  =COLOR_FORMAT_XRGB_NOALPHA;      // Alpha channel component is ignored
//--- List properties
int               list_height                   =0;                              // List height
int               text_height                   =0;                              // Text height
int               font_size                     =15;                             // Font size
string            font_name                     ="Calibri";                      // Font
double            line_size                     =100/LIST_SIZE;                  // Size of a single string on the list, expressed as percentage
//--- Scrollbar properties: scroll box
string            scrollbar_thumb_name          =prefix+"scrollbar_thumb";       // Scroll box object name
int               scrollbar_thumb_x1            =0;                              // x1 coordinate
int               scrollbar_thumb_y1            =0;                              // y1 coordinate
int               scrollbar_thumb_x2            =0;                              // x2 coordinate
int               scrollbar_thumb_y2            =0;                              // y2 coordinate
double            scrollbar_thumb_y_percent     =0.0;                            // Y-coordinate expressed as percentage
int               scrollbar_thumb_width         =9;                              // Width
int               scrollbar_thumb_height        =0;                              // Height
bool              scrollbar_thumb_clicked       =false;                          // State (whether or not the scroll box is clicked)
color             scrollbar_thumb_color         =clrSilver;                      // Scroll box color
color             scrollbar_thumb_color_on_hover=clrDimGray;                     // Scroll box color when the cursor hovers over it
color             scrollbar_thumb_color_on_click=clrSlateGray;                   // Scroll box color when clicked
//--- Scrollbar properties: background
string            scrollbar_background_name     =prefix+"scrollbar_background";  // Background object name
int               scrollbar_background_width    =9;                              // Background width
color             scrollbar_background_color    =C'50,50,50';                    // Background color
//--- Scrollbar properties: other
int               scrollbar_fix_point           =0;                              // Y-coordinate of the fix point upon clicking
int               scrollbar_fix_point_y_offest  =0;                              // Distance along the Y-axis from the scrollbar top to the fix point
//--- Mouse button state (pressed/released)
bool              mouse_button_state=false;
//--- Arrays for financial instrument properties
color             symbol_property_colors[];                                      // Colors of values
string            symbol_property_values[];                                      // Values
//--- Financial instrument property names
string symbol_propety_names[LIST_SIZE]=
  {
   "Number of deals in the current session",
   "Total number of Buy orders at the moment",
   "Total number of Sell orders at the moment",
   "Volume of the last deal",
   "Maximum daily volume",
   "Minimum daily volume",
   "Time of the last quote",
   "Number of decimal places",
   "Spread in points",
   "Floating spread indication",
   "Maximum number of requests displayed in the Depth of Market",
   "Contract price calculation mode",
   "Order execution type",
   "Trading start date for an instrument (usually used for futures)",
   "Trading end date for an instrument (usually used for futures)",
   "Minimum distance in points from the current closing price for the purpose of setting Stop orders",
   "Freeze distance for trading operations (in points)",
   "Deal execution mode",
   "Swap calculation model",
   "Day of the week when triple swap is charged",
   "Flags of allowed order expiration modes",
   "Flags of allowed order filling modes",
//---
   "Bid - best price at which an instrument can be sold",
   "Maximum Bid of the day",
   "Minimum Bid of the day",
   "Ask - best price at which an instrument can be bought",
   "Maximum Ask of the day",
   "Minimum Ask of the day",
   "Last - last deal price",
   "Maximum Last of the day",
   "Minimum Last of the day",
   "Point value",
   "Calculated tick value for a winning position",
   "Calculated tick value for a losing position",
   "Minimum price change",
   "Trade contract size",
   "Minimum volume for deal execution",
   "Maximum volume for deal execution",
   "Minimum step of volume change for deal execution",
   "Maximum allowable total volume of an open position and pending orders in the same direction",
   "Long swap value",
   "Short swap value",
   "Initial margin - amount in the margin currency required for opening a position (1 lot)",
   "Maintenance margin for an instrument",
   "Margin requirement applicable to long positions",
   "Margin requirement applicable to short positions",
   "Margin requirement applicable to Limit orders",
   "Margin requirement applicable to Stop orders",
   "Margin requirement applicable to Stop Limit orders",
   "Total volume of deals in the current session",
   "Total turnover in the current session",
   "Total volume of open positions",
   "Total volume of buy orders at the moment",
   "Total volume of sell orders at the moment",
   "Open price of the session",
   "Close price of the session",
   "Average weighted price of the session",
   "Settlement price of the current session",
   "Minimum allowable price value for the session",
   "Maximum allowable price value for the session",
//---
   "Base currency of an instrument",
   "Profit currency",
   "Margin currency",
   "Current quote source",
   "String description of a symbol",
   "Name of a trading symbol in the international system of securities identification numbers (ISIN)",
   "Location in the symbol tree",
//---
   "Current number of bars for a symbol on a selected time frame",
   "The very first date for a symbol on a selected time frame",
   "The very first date in the history for a symbol on a selected time frame",
   "Symbol data synchronized"
  };

Vamos primeiro escrever todas as funções necessárias para a exibição da lista de propriedades na tela. Assim que isso for feito, prosseguiremos para a criação da barra de rolagem.

Para criar a tela, nós escreveremos a função AddCanvas() e utilizaremos a segunda variante do método CreateBitmapLabel() da classe CCanvas:

//+------------------------------------------------------------------+
//| Adding canvas                                                    |
//+------------------------------------------------------------------+
void AddCanvas()
  {
//--- If there is no canvas, add it
   if(ObjectFind(0,canvas_name)<0)
      canvas.CreateBitmapLabel(0,subwindow_number,canvas_name,0,0,chart_width,subwindow_height,color_format);
  }

Também precisaremos de um método para alterar o tamanho da tela para ajustá-la ao tamanho da sub-janela indicadora. Para isso, vamos escrever a função ResizeCanvas() que utiliza o método Resize() na classe CCanvas:

//+------------------------------------------------------------------+
//| Resizing canvas                                                  |
//+------------------------------------------------------------------+
void ResizeCanvas()
  {
//--- If the canvas has already been added to the indicator subwindow, set the new size
   if(ObjectFind(0,canvas_name)==subwindow_number)
      canvas.Resize(chart_width,subwindow_height);
//--- If there is no canvas, add it
   else
      canvas.CreateBitmapLabel(0,subwindow_number,canvas_name,0,0,chart_width,subwindow_height,color_format);
  }

Para apagar a tela, usamos o método Destroy():

//+------------------------------------------------------------------+
//| Deleting canvas                                                  |
//+------------------------------------------------------------------+
void DeleteCanvas()
  {
   if(ObjectFind(0,canvas_name)>0)
      canvas.Destroy();
  }

Neste artigo, também vamos usar outro método da classe CCanvas, tal como FontSet() para criação de fontes, TextHeight() para determinar a altura do texto, TextOut() para a impressão do texto à tela, Erase() para limpar a tela e Update() para redesenhar. Mais abaixo, vamos ver em que parte do programa são utilizados os métodos acima.

Durante a inicialização da função OnInit() precisamos preparar o caminho para a operação do programa. O código abaixo mostra o que precisa ser feito. Os comentários apresentados em cada sequência ajudarão você a compreender melhor as ações. Os métodos FontSet() e TextHeight() da classe CCanvas são utilizados apenas nesta parte do programa.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Enable tracking of mouse events
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Set the short name
   IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname);
//--- Set sizes of arrays of symbol properties and their colors
   ArrayResize(symbol_property_colors,LIST_SIZE);
   ArrayResize(symbol_property_values,LIST_SIZE);
//--- Set subwindow properties
   SetSubwindowProperties();
//--- Set the font for displaying on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Save the text size (height) for calculations
   text_height=canvas.TextHeight("A")-1;
//--- Calculate the height of the entire list
   list_height=text_height*LIST_SIZE;
//--- Add the canvas to the chart
   AddCanvas();
//--- Display the list of symbol properties
   ShowSymbolInfo();
//--- Refresh the chart
   ChartRedraw();
//--- Everything completed successfully
   return(INIT_SUCCEEDED);
  }

A função SetSubwindowProperties() foi tomada do artigo anterior pois ela atribui o número da sub-janela indicadora e o seu tamanho às variáveis globais. Vamos prosseguir direto para a função ShowSymbolInfo():

//+------------------------------------------------------------------+
//| Displaying current symbol information                            |
//+------------------------------------------------------------------+
void ShowSymbolInfo(double current_thumb_position=0.0)
  {
   int    list_lines       =0;   // Counter of strings displayed in the canvas
   double thumb_position   =0.0; // Position of the scroll box expressed as percentage for determining the first displayed string
   int    y_distance       =0;   // For determining the coordinate of the next string in the list
   int    line_number      =0;   // Number of the string starting from which the list will be displayed
//--- Determine the string starting from which the list will be displayed
   for(int i=0; i<LIST_SIZE; i++)
     {
      //--- Count strings until you reach the one starting from which the list will be displayed
      if(thumb_position>=current_thumb_position)
         break;
      //---
      thumb_position+=line_size;
      line_number++;
     }
//--- Initialize list arrays from the specified string
   InitializePropertyArrays(line_number);
//--- Clear the canvas
   canvas.Erase(canvas_background_color);
//--- Show the list on the canvas
   for(int i=line_number; i<LIST_SIZE; i++)
     {
      //--- Property name
      canvas.TextOut(655,y_distance,symbol_propety_names[i]+" :",ColorToARGB(clrWhite),TA_RIGHT|TA_TOP);
      //--- Property value
      canvas.TextOut(665,y_distance,symbol_property_values[i],ColorToARGB(symbol_property_colors[i]),TA_LEFT|TA_TOP);
      //--- Calculate the coordinate for the next string
      y_distance+=text_height;
      //--- Count the number of displayed strings
      list_lines++;
      //--- If you go beyond the subwindow boundaries, terminate the loop
      if(list_lines*text_height>subwindow_height)
         break;
     }
//--- Refresh the canvas
   canvas.Update();
  }

A função ShowSymbolInfo() tem um parâmetro, current_thumb_position, que por padrão é igual a zero (no caso de você precisar usar o valor padrão, não há necessidade de passar o valor à função). Este parâmetro determina a sequência de partida a partir do qual a lista deve ser exibida. Em outras palavras, o valor zero significa que a lista deve ser exibida a partir do início.

No início, determinamos o número da sequência a partir da qual a lista deve ser exibida. Então as matrizes de valores e cores (sequência destacada no código acima) são inicializadas na função InitializePropertyArrays(): A inicialização é realizada a partir da sequência determinada no ciclo anterior. Depois que a tela é apagada usando o método Erase() - na prática, toda a tela é preenchida com a cor especificada. Na última sequência, o texto é impresso à tela usando o método TextOut(). No final, a tela é atualizada utilizando o método Update().

O código de função InitializePropertyArrays() é fornecido abaixo:

//+-----------------------------------------------------------------------+
//| Initializing arrays of values and their colors for the current symbol |
//+-----------------------------------------------------------------------+
void InitializePropertyArrays(int line_number)
  {
   int lines_count=0;
//---
   for(int i=line_number; i<LIST_SIZE; i++)
     {
      //--- Determine the value and color of the symbol property
      symbol_property_values[i]=GetStringSymbolInfoByIndex(i);
      symbol_property_colors[i]=GetColorSymbolInfoByIndex(i);
      //--- Increase the counter
      lines_count++;
      //--- If the number of strings exceeds the subwindow height, exit
      if(lines_count*text_height>subwindow_height)
         break;
     }
  }

O código acima sugere que os valores das propriedades do símbolo e suas cores sejam determinadas utilizando duas funções que operam sob princípios semelhantes, GetStringSymbolInfoByIndex() e GetColorSymbolInfoByIndex().

A função GetStringSymbolInfoByIndex() é simples, ainda muito grande, devido ao número de propriedades. Além disso, a fim de obter algumas propriedades, precisamos de funções auxiliares (sequências destacadas no código abaixo).

//+------------------------------------------------------------------+
//| Returning the string value of the symbol property by index       |
//+------------------------------------------------------------------+
string GetStringSymbolInfoByIndex(int index)
  {
   string str           ="-";
   long   l_check_value =0;
   double d_check_value =0.0;
   string s_check_value ="";
//---
   switch(index)
     {
      case 0  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_DEALS);
         str=(l_check_value==0) ? "-" : IntegerToString(l_check_value);                                  break;
      case 1  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_BUY_ORDERS);
         str=(l_check_value==0) ? "-" : IntegerToString(l_check_value);                                  break;
      case 2  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_SESSION_SELL_ORDERS);
         str=(l_check_value==0) ? "-" : IntegerToString(l_check_value);                                  break;
      case 3  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUME);
         str=(l_check_value==0) ? "-" : IntegerToString(l_check_value);                                  break;
      case 4  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMEHIGH);
         str=(l_check_value==0) ? "-" : IntegerToString(l_check_value);                                  break;
      case 5  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_VOLUMELOW);
         str=(l_check_value==0) ? "-" : IntegerToString(l_check_value);                                  break;
      case 6  :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TIME);
         str=(l_check_value==0) ? "-" : TimeToString(l_check_value);                                     break;
      case 7  : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_DIGITS));                           break;
      case 8  : str=IntegerToString(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD));                           break;
      case 9  : str=(!SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT)) ? "false" : "true";                break;
      case 10 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TICKS_BOOKDEPTH);
         str=(l_check_value==0) ? "-" : DoubleToString(l_check_value,_Digits);                           break;
      case 11 : str=TradeCalcModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_CALC_MODE));            break;
      case 12 : str=TradeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_MODE));                     break;
      case 13 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_START_TIME);
         str=(l_check_value==0) ? "-" : TimeToString(l_check_value);                                     break;
      case 14 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME);
         str=(l_check_value==0) ? "-" : TimeToString(l_check_value);                                     break;
      case 15 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         str=(l_check_value==0) ? "false" : IntegerToString(l_check_value);                              break;
      case 16 :
         l_check_value=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL);
         str=(l_check_value==0) ? "false" : IntegerToString(l_check_value);                              break;
      case 17 : str=TradeExeModeToString(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE));               break;
      case 18 : str=SwapModeToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_MODE));                       break;
      case 19 : str=WeekdayToString(SymbolInfoInteger(_Symbol,SYMBOL_SWAP_ROLLOVER3DAYS));               break;
      case 20 : str=ExpirationModeToString();                                                            break;
      case 21 : str=FillingModeToString();                                                               break;
      //---
      case 22 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BID);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 23 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDHIGH);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 24 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_BIDLOW);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 25 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 26 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKHIGH);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 27 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_ASKLOW);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 28 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LAST);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 29 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTHIGH);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 30 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_LASTLOW);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,_Digits);                           break;
      case 31 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_POINT),_Digits);                      break;
      case 32 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT),2);          break;
      case 33 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_LOSS),2);            break;
      case 34 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE),_Digits);            break;
      case 35 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_TRADE_CONTRACT_SIZE),2);              break;
      case 36 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),2);                       break;
      case 37 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX),2);                       break;
      case 38 : str=DoubleToString(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP),2);                      break;
      case 39 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         str=(d_check_value==0) ? "Unlimited" : DoubleToString(d_check_value,2);                         break;
      case 40 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SWAP_LONG);
         str=(d_check_value==0) ? "false" : DoubleToString(d_check_value,2);                             break;
      case 41 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SWAP_SHORT);
         str=(d_check_value==0) ? "false" : DoubleToString(d_check_value,2);                             break;
      case 42 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_INITIAL);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 43 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_MAINTENANCE);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 44 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_LONG);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 45 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_SHORT);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 46 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_LIMIT);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 47 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_STOP);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 48 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_MARGIN_STOPLIMIT);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 49 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_VOLUME);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 50 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_TURNOVER);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 51 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_INTEREST);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 52 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_BUY_ORDERS_VOLUME);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 53 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_SELL_ORDERS_VOLUME);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 54 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_OPEN);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 55 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_CLOSE);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 56 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_AW);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 57 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_SETTLEMENT);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 58 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_LIMIT_MIN);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
      case 59 :
         d_check_value=SymbolInfoDouble(_Symbol,SYMBOL_SESSION_PRICE_LIMIT_MAX);
         str=(d_check_value==0) ? "-" : DoubleToString(d_check_value,2);                                 break;
         //---
      case 60 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE);                                      break;
      case 61 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT);                                    break;
      case 62 : str=SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN);                                    break;
      case 63 :
         s_check_value=SymbolInfoString(_Symbol,SYMBOL_BANK);
         str=(s_check_value!="") ? s_check_value : "-";                                                  break;
      case 64 : str=SymbolInfoString(_Symbol,SYMBOL_DESCRIPTION);                                        break;
      case 65 :
         s_check_value=SymbolInfoString(_Symbol,SYMBOL_ISIN);
         str=(s_check_value!="") ? s_check_value : "-";                                                  break;
      case 66 : str=SymbolInfoString(_Symbol,SYMBOL_PATH);                                               break;
      //---
      case 67 : str=IntegerToString(SeriesInfoInteger(_Symbol,_Period,SERIES_BARS_COUNT));               break;
      case 68 : str=TimeToString((datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE));         break;
      case 69 : str=TimeToString((datetime)SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE));  break;
      case 70 : str=(!(bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)) ? "false" : "true";  break;
     }
//---
   return(str);
  }

As funções destacadas acima: TradeCalcModeToString(), TradeModeToString(), TradeExeModeToString(), SwapModeToString() e WeekdayToString() simplesmente retornam a representação da sequência de propriedades dependendo do valor passado.

//+------------------------------------------------------------------+
//| Returning string representation of the margin calculation        |
//| method for an instrument                                         |
//+------------------------------------------------------------------+
string TradeCalcModeToString(long mode)
  {
   string str="?";
//---
   switch((ENUM_SYMBOL_CALC_MODE)mode)
     {
      case SYMBOL_CALC_MODE_FOREX               :
         str="Forex mode";          break;
      case SYMBOL_CALC_MODE_FUTURES             :
         str="Futures mode";        break;
      case SYMBOL_CALC_MODE_CFD                 :
         str="CFD mode";            break;
      case SYMBOL_CALC_MODE_CFDINDEX            :
         str="CFD index mode";      break;
      case SYMBOL_CALC_MODE_CFDLEVERAGE         :
         str="CFD Leverage mode";   break;
      case SYMBOL_CALC_MODE_EXCH_STOCKS         :
         str="Exchange mode";       break;
      case SYMBOL_CALC_MODE_EXCH_FUTURES        :
         str="Futures mode";        break;
      case SYMBOL_CALC_MODE_EXCH_FUTURES_FORTS  :
         str="FORTS Futures mode";  break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Returning string representation of the trade mode                |
//+------------------------------------------------------------------+
string TradeModeToString(long mode)
  {
   string str="-";
//---
   switch((ENUM_SYMBOL_TRADE_MODE)mode)
     {
      case SYMBOL_TRADE_MODE_DISABLED  :
         str="Trade is disabled for a given symbol";               break;
      case SYMBOL_TRADE_MODE_LONGONLY  :
         str="Only long positions are allowed";                    break;
      case SYMBOL_TRADE_MODE_SHORTONLY :
         str="Only short positions are allowed";                   break;
      case SYMBOL_TRADE_MODE_CLOSEONLY :
         str="Only position closing operations are allowed";       break;
      case SYMBOL_TRADE_MODE_FULL      :
         str="No trade restrictions";                              break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Returning string representation of the deal execution mode       |
//+------------------------------------------------------------------+
string TradeExeModeToString(long mode)
  {
   string str="-";
//---
   switch((ENUM_SYMBOL_TRADE_EXECUTION)mode)
     {
      case SYMBOL_TRADE_EXECUTION_REQUEST  :
         str="Request execution";         break;
      case SYMBOL_TRADE_EXECUTION_INSTANT  :
         str="Instant execution";         break;
      case SYMBOL_TRADE_EXECUTION_MARKET   :
         str="Market execution";          break;
      case SYMBOL_TRADE_EXECUTION_EXCHANGE :
         str="Exchange execution";        break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Returning string representation of the swap calculation model    |
//+------------------------------------------------------------------+
string SwapModeToString(long mode)
  {
   string str="-";
//---
   switch((ENUM_SYMBOL_SWAP_MODE)mode)
     {
      case SYMBOL_SWAP_MODE_DISABLED         :
         str="No swaps";                                                             break;
      case SYMBOL_SWAP_MODE_POINTS           :
         str="Swaps calculated in points";                                           break;
      case SYMBOL_SWAP_MODE_CURRENCY_SYMBOL  :
         str="Swaps calculated in base currency of the symbol";                      break;
      case SYMBOL_SWAP_MODE_CURRENCY_MARGIN  :
         str="Swaps calculated in margin currency of the symbol";                    break;
      case SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT :
         str="Swaps calculated in the client's deposit currency";                    break;
      case SYMBOL_SWAP_MODE_INTEREST_CURRENT :
         str="Swaps expressed as a percent per annum of the instrument price";       break;
      case SYMBOL_SWAP_MODE_INTEREST_OPEN    :
         str="Swaps expressed as a percent per annum of the position opening price"; break;
      case SYMBOL_SWAP_MODE_REOPEN_CURRENT   :
         str="Swaps based on position reopening (close price +/-)";                  break;
      case SYMBOL_SWAP_MODE_REOPEN_BID       :
         str="Swaps based on position reopening (bid price +/-)";                    break;
     }
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Returning string representation of the day of the week           |
//+------------------------------------------------------------------+
string WeekdayToString(long day)
  {
   string str="-";
//---
   switch((ENUM_DAY_OF_WEEK)day)
     {
      case SUNDAY    :
         str="Sunday";            break;
      case MONDAY    :
         str="Monday";            break;
      case TUESDAY   :
         str="Tuesday";           break;
      case WEDNESDAY :
         str="Wednesday";         break;
      case THURSDAY  :
         str="Thursday";          break;
      case FRIDAY    :
         str="Friday";            break;
      case SATURDAY  :
         str="Saturday";          break;
     }
//---
   return(str);
  }

Nas funções GetStringExpirationMode() e GetStringFillingMode() a representação de sequência é gerada em função da expiração da ordem e do volume de preenchimento dos modos disponíveis para o símbolo atual.

//+------------------------------------------------------------------+
//| Returning string representation of the order expiration modes    |
//+------------------------------------------------------------------+
string ExpirationModeToString()
  {
   string str="";    // For string generation
//--- Variables for checking the modes
   bool   gtc           =false; // The order is valid for an unlimited time until explicitly canceled
   bool   day           =false; // The order is valid until the end of the day
   bool   specified     =false; // The expiration time is specified in the order
   bool   specified_day =false; // The expiration date is specified in the order
//--- Check the modes
   gtc           =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_GTC);
   day           =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_DAY);
   specified     =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED);
   specified_day =IsExpirationTypeAllowed(_Symbol,SYMBOL_EXPIRATION_SPECIFIED_DAY);
//--- Generate a string of the modes available
   if(gtc)
     {
      StringAdd(str,"GTC");
      if(day || specified || specified_day)
         StringAdd(str," / ");
     }
//---
   if(day)
     {
      StringAdd(str,"Day");
      if(specified || specified_day)
         StringAdd(str," / ");
     }
//---
   if(specified)
     {
      StringAdd(str,"Specified");
      if(specified_day)
         StringAdd(str," / ");
     }
//---
   if(specified_day)
      StringAdd(str,"Specified Day");
//---
   return(str);
  }
//+------------------------------------------------------------------+
//| Returning string representation of the volume filling modes      |
//+------------------------------------------------------------------+
string FillingModeToString()
  {
//--- Variable for string generation
   string str="";

//--- Variables for checking the modes:

//    "Fill or Kill"   -  if the required order volume cannot be filled at the specified price,
//                      the order is canceled and the deal is not executed
   bool   fok=false;

//    "Immediate or Cancel" -  if the deal volume can only be partially filled at the price specified in the order, 
//                      the deal is executed to the extent of the volume available. The remaining volume of the order is canceled 
//                      and the new order is not placed
   bool   ioc=false;

//    "Return"       -  The deal is executed to the extent of the volume available at the price specified in the order.
//                      A new order is placed for the remaining volume at the same price
   bool   return_remainder=false;

//--- Check the modes
   fok   =IsFillingTypeAllowed(_Symbol,SYMBOL_FILLING_FOK);
   ioc   =IsFillingTypeAllowed(_Symbol,SYMBOL_FILLING_IOC);
//--- For "Market execution" and "Exchange execution" modes
   ENUM_SYMBOL_TRADE_EXECUTION symbol_trade_exemode=(ENUM_SYMBOL_TRADE_EXECUTION)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_EXEMODE);
   return_remainder=(symbol_trade_exemode==SYMBOL_TRADE_EXECUTION_MARKET || 
                     symbol_trade_exemode==SYMBOL_TRADE_EXECUTION_EXCHANGE) ? true : false;
//--- Generate a string of the modes available
   if(fok)
     {
      StringAdd(str,"Fill or Kill");
      if(ioc || return_remainder)
         StringAdd(str," / ");
     }
//---
   if(ioc)
     {
      StringAdd(str,"Immediate or Cancel");
      if(return_remainder)
         StringAdd(str," / ");
     }
//---
   if(return_remainder)
      StringAdd(str,"Return");
//---
   return(str);
  }

Uma vez que cada modo de disponibilidade precisa ser verificado separadamente, por conveniência, usamos as funções auxiliares IsExpirationTypeAllowed() e IsFillingTypeAllowed(), fornecidas nos exemplos de documentação:

//+------------------------------------------------------------------+
//| Checking if a given expiration mode is allowed                   |
//+------------------------------------------------------------------+
bool IsExpirationTypeAllowed(string symbol, int exp_type)
  {
//--- Get the value of the property describing the allowable expiration modes
   int expiration=(int)SymbolInfoInteger(symbol,SYMBOL_EXPIRATION_MODE);
//--- Return true if the exp_type mode is allowed
   return((expiration&exp_type)==exp_type);
  }
//+------------------------------------------------------------------+
//| Checking if a given filling mode is allowed                      |
//+------------------------------------------------------------------+
bool IsFillingTypeAllowed(string symbol, int fill_type)
  {
//--- Get the value of the property describing the filling mode
   int filling=(int)SymbolInfoInteger(symbol,SYMBOL_FILLING_MODE);
//--- Return true if the fill_type mode is allowed
   return((filling&fill_type)==fill_type);
  }

Assim, revisamos os valores da sequência das propriedades do símbolo. Vamos agora dar uma olhada na função GetColorSymbolInfoByIndex(). O código desta função é muito mais simples, devido ao fato de que nem todas as propriedades dependem do valor exibido:

//+------------------------------------------------------------------+
//| Returning the symbol property color by index                     |
//+------------------------------------------------------------------+
color GetColorSymbolInfoByIndex(int index)
  {
   double check_value =0.0;
   color  clr         =clrWhiteSmoke;
//---
   switch(index)
     {
      case 6  :
         clr=(SymbolInfoInteger(_Symbol,SYMBOL_TIME)>0) ? clrCornflowerBlue : clrWhiteSmoke;                      break;
         //---
      case 9  : clr=(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD_FLOAT)>0) ? clrGold : clrRed;                        break;
      //---
      case 13 :
         clr=(SymbolInfoInteger(_Symbol,SYMBOL_START_TIME)>0) ? clrCornflowerBlue : clrWhiteSmoke;                break;
      case 14 :
         clr=(SymbolInfoInteger(_Symbol,SYMBOL_EXPIRATION_TIME)>0) ? clrCornflowerBlue : clrWhiteSmoke;           break;
         //---
      case 15 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)>0) ? clrWhiteSmoke : clrRed;             break;
      case 16 : clr=(SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL)>0) ? clrWhiteSmoke : clrRed;            break;
      //---
      case 20 : clr=clrGold;                                                                                      break;
      case 21 : clr=clrGold;                                                                                      break;
      //---
      case 39 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT)>0) ? clrWhiteSmoke : clrGold;                  break;
      case 40 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_SWAP_LONG)>0) ? clrLime : clrRed;                            break;
      case 41 : clr=(SymbolInfoDouble(_Symbol,SYMBOL_SWAP_SHORT)>0) ? clrLime : clrRed;                           break;
      //---
      case 60 : clr=clrGold;                                                                                      break;
      case 61 : clr=clrGold;                                                                                      break;
      case 62 : clr=clrGold;                                                                                      break;
      //---
      case 68 :
         clr=(SeriesInfoInteger(_Symbol,_Period,SERIES_FIRSTDATE)>0) ? clrCornflowerBlue : clrWhiteSmoke;         break;
      case 69 :
         clr=(SeriesInfoInteger(_Symbol,_Period,SERIES_SERVER_FIRSTDATE)>0) ? clrCornflowerBlue : clrWhiteSmoke;  break;
      case 70 : clr=(!(bool)SeriesInfoInteger(_Symbol,_Period,SERIES_SYNCHRONIZED)) ? clrRed : clrGold;           break;
     }
//---
   return(clr);
  }

Se agora compilarmos o indicador e adicioná-lo ao gráfico, seremos capazes de ver a lista das propriedades do símbolo na sub-janela conforme mostra a imagem abaixo:

Fig. 1. Indicador anexo ao gráfico sem a barra de rolagem

Fig. 1. Indicador anexo ao gráfico sem a barra de rolagem

Tudo isso é um único objeto!

Além disso, vamos escrever as funções para trabalhar com a barra de rolagem vertical. Tal como já mencionado no início do artigo, a barra de rolagem será criada usando dois objetos gráficos OBJ_RECTANGLE_LABEL (Marcador de retângulo). Um vai ser usado como fundo e o outro será a caixa de rolagem. A barra de rolagem será localizada à direita da sub-janela indicadora.

CreateRectangleLable() - a função para criar uma etiqueta de retângulo:

//+------------------------------------------------------------------+
//| Creating a rectangle                                             |
//+------------------------------------------------------------------+
void CreateRectangleLable(long              chart_id,          // chart id
                          int               sub_window,        // window number
                          string            object_name,       // object name
                          int               x_distance,        // X-coordinate
                          int               y_distance,        // Y-coordinate
                          int               x_size,            // width
                          int               y_size,            // height
                          ENUM_BASE_CORNER  corner,            // chart corner
                          color             border_color,      // border color
                          color             background_color,  // background color
                          bool              selectable,        // cannot select the object if FALSE
                          bool              is_on_background)  // background position
  {
//--- If the object has been created successfully
   if(ObjectCreate(chart_id,object_name,OBJ_RECTANGLE_LABEL,sub_window,0,0))
     {
      // set its properties
      ObjectSetInteger(chart_id,object_name,OBJPROP_XDISTANCE,x_distance);
      ObjectSetInteger(chart_id,object_name,OBJPROP_YDISTANCE,y_distance);
      ObjectSetInteger(chart_id,object_name,OBJPROP_XSIZE,x_size);
      ObjectSetInteger(chart_id,object_name,OBJPROP_YSIZE,y_size);
      ObjectSetInteger(chart_id,object_name,OBJPROP_BORDER_TYPE,BORDER_FLAT);    // set the flat border style
      ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,border_color);
      ObjectSetInteger(chart_id,object_name,OBJPROP_CORNER,corner);
      ObjectSetInteger(chart_id,object_name,OBJPROP_BGCOLOR,background_color);
      ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,selectable);
      ObjectSetInteger(chart_id,object_name,OBJPROP_BACK,is_on_background);      // it will be used as a background if true
      ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,"\n");                // no tooltip if "\n"
     }
  }

Vamos escrever funções para criar e modificar a caixa de rolagem e os tamanhos de fundos da barra de rolagem: AdjustScrollbarThumb() e AdjustScrollbarBackground():

//+------------------------------------------------------------------+
//| Adding scroll box or adjusting its size                          |
//+------------------------------------------------------------------+
void AdjustScrollbarThumb()
  {
//--- Calculate the scroll box size relative to the subwindow height
   CalculateScrollbarThumbHeight();
//--- If the scroll box is already available in the chart, adjust its properties
   if(ObjectFind(0,scrollbar_thumb_name)>0)
     {
      //--- Set the height and X-coordinate
      ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_YSIZE,scrollbar_thumb_height);
      ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_XDISTANCE,chart_width-scrollbar_thumb_width);
      //--- Adjust the scroll box position along the Y-axis if you go below the subwindow bottom boundary
      if(scrollbar_thumb_y1+scrollbar_thumb_height>subwindow_height)
         ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE,subwindow_height-scrollbar_thumb_height);
     }
//--- Create the scroll box if it does not exist
   else
     {
      CreateRectangleLable(0,subwindow_number,scrollbar_thumb_name,
                           chart_width-scrollbar_thumb_width,0,scrollbar_thumb_width,scrollbar_thumb_height,
                           CORNER_LEFT_UPPER,clrSilver,clrSilver,false,false);
     }
  }
//+------------------------------------------------------------------+
//| Adding the scrollbar background or adjusting its size            |
//+------------------------------------------------------------------+
void AdjustScrollbarBackground()
  {
//--- If the scrollbar background is already available in the chart, adjust its properties
   if(ObjectFind(0,scrollbar_background_name)>0)
     {
      //--- Set the background size
      ObjectSetInteger(0,scrollbar_background_name,OBJPROP_YDISTANCE,0);
      ObjectSetInteger(0,scrollbar_background_name,OBJPROP_XDISTANCE,chart_width-scrollbar_background_width);
      ObjectSetInteger(0,scrollbar_background_name,OBJPROP_YSIZE,subwindow_height);
     }
//--- If there is no background, create it
   else
     {
      CreateRectangleLable(0,subwindow_number,scrollbar_background_name,
                           chart_width-scrollbar_background_width,0,scrollbar_background_width,subwindow_height,
                           CORNER_LEFT_UPPER,scrollbar_background_color,scrollbar_background_color,false,false);
     }
  }

A altura da caixa de rolagem é calculada no início da função AdjustScrollbarThumb(), na sequência destacada:

//+------------------------------------------------------------------+
//| Calculating the scroll box size relative to the subwindow height |
//+------------------------------------------------------------------+
void CalculateScrollbarThumbHeight()
  {
//--- If the subwindow height is greater than the list size, save the subwindow size
   if(subwindow_height>=list_height)
      scrollbar_thumb_height=subwindow_height-1;
//--- Otherwise calculate the scroll box size
   else
     {
      double height_temp=0.0;
      //--- Calculate the scroll box size relative to the subwindow height
      height_temp=subwindow_height-(((double)subwindow_height/100)*(100-((double)subwindow_height/list_height)*100));
      //--- Set the minimum size at 25% of the subwindow height
      if(height_temp/subwindow_height<0.25)
         height_temp=subwindow_height/4;
      //--- Save to the global variable
      scrollbar_thumb_height=(int)height_temp;
     }
  }

Lembre-se de apagar os objetos gráficos:

//+------------------------------------------------------------------+
//| Deleting the scrollbar                                           |
//+------------------------------------------------------------------+
void DeleteScrollbar()
  {
   DeleteObjectByName(scrollbar_thumb_name);
   DeleteObjectByName(scrollbar_background_name);
  }
//+------------------------------------------------------------------+
//| Deleting the object by name                                      |
//+------------------------------------------------------------------+
void DeleteObjectByName(string object_name)
  {
//--- If such object exists
   if(ObjectFind(0,object_name)>=0)
     {
      //--- If an error occurred when deleting, print the relevant message
      if(!ObjectDelete(0,object_name))
         Print("Error ("+IntegerToString(GetLastError())+") when deleting the object!");
     }
  }

Vamos agora prosseguir para a parte mais interessante: precisamos escrever funções que permitam arrastar a caixa de rolagem, fazendo com que a lista se mova também. Nós também precisamos implementar a mudança na cor da caixa de rolagem, quando o cursor passar sobre ela e quando a caixa de rolagem for clicada, para indicar que o controle foi passado à caixa de rolagem e que esta última possa ser agora arrastada. Para isso, a cor da caixa de rolagem será adicionalmente alterada após a clicagem.

A caixa de rolagem é bastante estreita em largura, por isso quando ela é movida para cima/para baixo, você pode experimentar o deslocamento lateral do cursor. Para corrigir esse problema, vamos passar o controle para a caixa de rolagem, enquanto o botão esquerdo do mouse está sendo pressionado.

Abaixo estão os códigos das funções acima descritas:

//+------------------------------------------------------------------+
//| Setting the scroll box color                                     |
//+------------------------------------------------------------------+
void SetScrollbarThumbColor(color thumb_color)
  {
   ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_COLOR,thumb_color);
   ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_BGCOLOR,thumb_color);
  }
//+------------------------------------------------------------------+
//| Setting the scroll box boundaries                                |
//+------------------------------------------------------------------+
void SetScrollbarThumbBoundaries()
  {
   scrollbar_thumb_x1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_XDISTANCE);
   scrollbar_thumb_y1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE);
   scrollbar_thumb_x2=scrollbar_thumb_x1+scrollbar_thumb_width;
   scrollbar_thumb_y2=scrollbar_thumb_y1+scrollbar_thumb_height;
  }
//+------------------------------------------------------------------+
//| Changing the color of the scroll box when the cursor hovers over |
//+------------------------------------------------------------------+
void ChangeScrollbarThumbColorOnHover(int x,int y)
  {
//--- If the cursor is within the scroll box area, make the color darker
   if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2)
      SetScrollbarThumbColor(scrollbar_thumb_color_on_hover);
//--- If the cursor is outside the scroll box boundaries
   else
     {
      //--- If the mouse button is released, set the standard scroll box color
      if(!mouse_button_state)
         SetScrollbarThumbColor(scrollbar_thumb_color);
     }
  }
//+------------------------------------------------------------------+
//| Determining the scroll box state                                 |
//+------------------------------------------------------------------+
void SetScrollbarThumbState(int x,int y)
  {
//--- If the mouse cursor is within the scroll box boundaries
   if(x>scrollbar_thumb_x1 && x<scrollbar_thumb_x2 && y>scrollbar_thumb_y1 && y<scrollbar_thumb_x2)
     {
      //--- If the mouse button is pressed, save it
      if(mouse_button_state)
         scrollbar_thumb_clicked=true;
     }
//--- If the cursor is outside the scroll box boundaries
   else
     {
      //--- If the mouse button is released, disable the scroll box control
      if(!mouse_button_state)
         ZeroScrollbarThumbVariables();
     }
  }
//+------------------------------------------------------------------+
//| Zeroing out variables related to scroll box movement             |
//+------------------------------------------------------------------+
void ZeroScrollbarThumbVariables()
  {
   scrollbar_thumb_clicked       =false;
   scrollbar_fix_point           =0;
   scrollbar_fix_point_y_offest  =0;
  }

Estas não são todas as funções necessárias para fazer com que a caixa de rolagem se mova. Por falar nisso, a mobilidade da caixa de rolagem é baseada em eventos. Em outras palavras, uma determinada ação seria aplicada se o botão do mouse fosse pressionado com o cursor estando na área do gráfico controlada, seguido de uma mudança do cursor pelo número especificado de pixels com o botão do mouse ainda pressionado. No nosso caso, esta seria a mudança na posição da caixa de rolagem e, consequentemente, da lista das propriedades do símbolo. É muito simples.

Abaixo você pode ver a função MoveThumb(), UpdateListAndScrollbarThumb() e ThumbYCoordinateToPercent() que são utilizadas para implementar as ações acima:

//+------------------------------------------------------------------+
//| Moving the scroll box vertically to the specified coordinate     |
//+------------------------------------------------------------------+
void MoveThumb(int y)
  {
   int  threshold   =1; // Threshold in pixels for recalculation
   int  new_y_point =0; // New Y-coordinate
//--- If the mouse button is pressed
   if(mouse_button_state)
     {
      //--- Set the clicked scroll box color
      SetScrollbarThumbColor(scrollbar_thumb_color_on_click);
      //--- Save the current Y-coordinate of the cursor
      if(scrollbar_fix_point==0)
         scrollbar_fix_point=y;
      //--- Save the distance from the scroll box top to the cursor
      if(scrollbar_fix_point_y_offest==0)
         scrollbar_fix_point_y_offest=scrollbar_thumb_y1-scrollbar_fix_point;
     }
//--- If you scrolled down below the threshold value, while keeping the button pressed
   if(y-scrollbar_fix_point>=threshold)
     {
      //--- If you are still within the subwindow boundaries
      if(scrollbar_thumb_y1+scrollbar_thumb_height+threshold<subwindow_height)
         new_y_point=y+scrollbar_fix_point_y_offest;
      else
        {
         scrollbar_fix_point_y_offest=0;
         new_y_point=int(subwindow_height-scrollbar_thumb_height)-1;
        }
      //--- Refresh the list and the scroll box
      UpdateListAndScrollbarThumb(new_y_point);
      return;
     }
//--- If you scrolled up above the threshold value, while keeping the button pressed
   if(y-scrollbar_fix_point<=-(threshold))
     {
      //--- If you are still within the subwindow boundaries
      if(y-fabs(scrollbar_fix_point_y_offest)>=0)
         new_y_point=y-fabs(scrollbar_fix_point_y_offest);
      else
        {
         new_y_point=0;
         scrollbar_fix_point_y_offest=0;
        }
      //--- Refresh the list and the scroll box
      UpdateListAndScrollbarThumb(new_y_point);
      return;
     }
  }
//+------------------------------------------------------------------+
//| Refreshing the list and the scroll box position                  |
//+------------------------------------------------------------------+
void UpdateListAndScrollbarThumb(int new_point)
  {
//--- Set the new Y-coordinate for the scroll box
   ObjectSetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE,new_point);
//--- Refresh the list of the symbol properties relative to the current scroll box position
   ShowSymbolInfo(ThumbYCoordinateToPercent(new_point));
//--- Zero out the fix point
   scrollbar_fix_point=0;
  }
//+------------------------------------------------------------------+
//| Converting the Y-coordinate of the scroll box to percentage      |
//+------------------------------------------------------------------+
double ThumbYCoordinateToPercent(long y)
  {
   if(subwindow_height<=0)
      subwindow_height=1;
//---
   return(((double)y/subwindow_height)*100);
  }

Agora, todas as funções devem ser colocadas em uma certa ordem para permitir que o programa funcione como concebido inicialmente. Na função OnChartEvent() precisamos lidar com o eventos que ajudam o usuário a interagir com a sub-janela indicadora, bem como a lista e a barra de rolagem localizadas na sub-janela.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Track mouse clicks on the chart
   if(id==CHARTEVENT_CLICK)
     {
      //--- Zero out variables related to the scroll box movement
      ZeroScrollbarThumbVariables();
      //--- Refresh the chart
      ChartRedraw();
      return;
     }
//--- Track the cursor movement and the state of the left mouse button
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      int      x      =(int)lparam; // X-coordinate
      int      y      =(int)dparam; // Y-coordinate
      int      window =WRONG_VALUE; // Number of the window where the cursor is located
      datetime time   =NULL;        // Time corresponding to the X-coordinate
      double   price  =0.0;         // Price corresponding to the Y-coordinate
 
      //--- Set subwindow properties
      SetSubwindowProperties();
      //--- Check and save the state of the mouse button
      CheckMouseButtonState(sparam);
      //--- Get the position of the cursor
      if(ChartXYToTimePrice(0,x,y,window,time,price))
        {
         //--- If the cursor is within the subwindow boundaries
         if(window==subwindow_number)
           {
            //--- Disable scrolling of the price chart
            ChartSetInteger(0,CHART_MOUSE_SCROLL,false);
            //--- Recalculate the Y coordinate relative to the indicator subwindow
            ChartYToSubwindowY(y);
            //--- Determine the scroll box boundaries
            SetScrollbarThumbBoundaries();
            //--- Change the scroll box color when the cursor moves over it
            ChangeScrollbarThumbColorOnHover(x,y);
            //--- Determine the scroll box state
            SetScrollbarThumbState(x,y);
            //--- If the control is passed to the scroll box, drag it and refresh the list
            if(scrollbar_thumb_clicked)
               MoveThumb(y);
           }
         //--- If the cursor is outside the subwindow boundaries
         else
           {
            //--- Enable scrolling of the price chart
            ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
            //--- If the mouse button is released, set the standard scroll box color
            if(!scrollbar_thumb_clicked)
               SetScrollbarThumbColor(scrollbar_thumb_color);
           }
        }
      //--- If the position of the cursor could not be determined
      else
        {
         //--- If the control has not been passed to the scroll box, set the standard scroll box color
         if(!scrollbar_thumb_clicked)
            SetScrollbarThumbColor(scrollbar_thumb_color);
        }
      //--- Refresh the chart
      ChartRedraw();
      return;
     }
//--- Track the change of properties and size of the chart
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Set subwindow properties
      SetSubwindowProperties();
      //--- Get the Y-coordinate of the scroll box
      scrollbar_thumb_y1=(int)ObjectGetInteger(0,scrollbar_thumb_name,OBJPROP_YDISTANCE);
      //--- If the subwindow size is zero, exit
      if(subwindow_height<=0)
         return;
      //--- Set the new canvas size
      ResizeCanvas();
      //--- Refresh the scrollbar background
      AdjustScrollbarBackground();
      //--- Refresh the scroll box
      AdjustScrollbarThumb();
      //--- Refresh the data on the canvas
      ShowSymbolInfo(ThumbYCoordinateToPercent(scrollbar_thumb_y1));
      //---
      return;
     }
  }

As funções destacadas no código acima são auxiliares. Você pode facilmente entender o seu propósito nos comentários fornecidos.

//+-------------------------------------------------------------------+
//| Checking the mouse button state                                   |
//+-------------------------------------------------------------------+
void CheckMouseButtonState(string state)
  {
//--- Left mouse button is pressed
   if(state=="1")
      mouse_button_state=true;
//--- Left mouse button is released
   if(state=="0")
     {
      //--- Zero out variables
      ZeroScrollbarThumbVariables();
      mouse_button_state=false;
     }
  }
//+-------------------------------------------------------------------+
//| Recalculating the Y-coordinate relative to the indicator subwindow|
//+-------------------------------------------------------------------+
void ChartYToSubwindowY(int &y)
  {
//--- Get the distance from the chart top to the indicator subwindow
   chart_y_offset=(int)ChartGetInteger(0,CHART_WINDOW_YDISTANCE,subwindow_number);
//--- Recalculate the Y-coordinate relative to the indicator subwindow
   y-=chart_y_offset;
  }

Como a tela, a barra de rolagem deve ser adicionada à sub-janela indicadora durante a inicialização.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Enable tracking of mouse events
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true);
//--- Set the short name
   IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname);
//--- Set sizes of arrays of symbol properties and their colors
   ArrayResize(symbol_property_colors,LIST_SIZE);
   ArrayResize(symbol_property_values,LIST_SIZE);
//--- Set subwindow properties
   SetSubwindowProperties();
//--- Set the font for displaying on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Save the text size (height) for calculations
   text_height=canvas.TextHeight("A")-1;
//--- Calculate the height of the entire list
   list_height=text_height*LIST_SIZE;
//--- Add the canvas to the chart
   AddCanvas();
//--- Add the scrollbar: background and scroll box
   AdjustScrollbarBackground();
   AdjustScrollbarThumb();
//--- Display the list of symbol properties
   ShowSymbolInfo();
//--- Refresh the chart
   ChartRedraw();
//--- Everything completed successfully
   return(INIT_SUCCEEDED);
  }

Não se esqueça de "limpar" a função OnDeinit(). Dependendo do motivo de deinicialização, o programa pode ser configurado com mais precisão.

//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(reason==REASON_REMOVE      || // If the indicator has been removed from the chart or
      reason==REASON_CHARTCHANGE || // the symbol or time frame has been modified or
      reason==REASON_RECOMPILE   || // the program has been recompiled or
      reason==REASON_CHARTCLOSE  || // the chart has been closed or
      reason==REASON_CLOSE)         // the terminal has been closed
     {
      //--- Delete the scrollbar
      DeleteScrollbar();
      //--- Delete the canvas
      DeleteCanvas();
      //--- Enable scrolling of the price chart
      ChartSetInteger(0,CHART_MOUSE_SCROLL,true);
      //--- Disable cursor tracking
      ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,false);
      //--- Refresh the chart
      ChartRedraw();
     }
  }

E, finalmente, para permitir que algumas propriedades de um símbolo sejam atualizadas no modo em tempo real, é preciso acrescentar um par de sequências do código à função OnCalculate():

Agora, está tudo pronto. O código-fonte está anexo ao artigo para sua consideração no MetaEditor 5 e está disponível para download. A operação das funções lidadas neste artigo é mostrada no vídeo fornecido abaixo.

 

Conclusão

Acabamos de concluir a revisão do controle da barra de rolagem. O artigo demonstrou como uma barra de rolagem pode ser feita de objetos gráficos separados, que estão localizados em uma tela. Em um dos artigos futuros, tentaremos implementar toda a funcionalidade usando métodos desta classe.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/751

Arquivos anexados |
testscrollbar.mq5 (57.23 KB)
Guia prático do MQL5: Desenvolvimento de um indicador de símbolos múltiplos no MQL5 Guia prático do MQL5: Desenvolvimento de um indicador de símbolos múltiplos no MQL5
Neste artigo, vamos considerar o desenvolvimento de um indicador de volatilidade de símbolos múltiplos. O desenvolvimento de indicadores de símbolos múltiplos pode apresentar algumas dificuldades para os desenvolvedores novatos do MQL5, as quais este artigo ajuda a esclarecer. As principais questões que surgem no curso do desenvolvimento de um indicador de símbolos múltiplos têm a ver com sincronização de dados de outros símbolos em relação ao símbolo atual, a falta de alguns dados de indicadores e a identificação de início de barras 'reais' de um determinado período de tempo. Todas essas questões serão atentamente consideradas no artigo.
Guia prático do MQL5: Controles de sub-janela indicadora - botões Guia prático do MQL5: Controles de sub-janela indicadora - botões
Neste artigo, vamos considerar um exemplo de desenvolvimento de uma interface de usuário com controles de botão. Para transmitir a ideia de interatividade ao usuário, os botões mudarão suas cores quando o cursor passar sobre eles. Com o cursor sobre um botão, a cor do botão será ligeiramente escurecida, ficando significativamente mais escura quando o botão for clicado. Além disso, adicionaremos janelas pop-up em cada botão, criando assim, uma interface intuitiva.
Criando um Expert Advisor multissistema e multimoeda Criando um Expert Advisor multissistema e multimoeda
O artigo apresenta uma estrutura para um Expert Advisor que negocia múltiplos símbolos e utiliza vários sistemas de negociação simultaneamente. Se você já identificou os parâmetros de entrada ideais para todos os seus EAs e obteve bons resultados de simulação para cada um deles separadamente, pergunte-se quais os resultados que você obteria se testasse todos os EAs simultaneamente, com todas as suas estratégias juntas.
Guia prático do MQL5: Monitoramento de múltiplos períodos de tempo em uma única janela Guia prático do MQL5: Monitoramento de múltiplos períodos de tempo em uma única janela
Há 21 períodos de tempo disponíveis no MetaTrader 5 para análise. Você pode tirar proveito de objetos gráficos especiais, os quais você pode colocar no gráfico existente e definir o símbolo, período de tempo e algumas outras propriedades ali mesmo. Este artigo fornecerá informações detalhadas sobre tais objetos gráficos do gráfico: vamos criar um indicador com os controles (botões), que nos permitirão definir vários objetos gráficos em uma sub-janela ao mesmo tempo. Além disso, os objetos gráficos se encaixarão com precisão na sub-janela e serão ajustados automaticamente quando o gráfico principal ou a janela do terminal forem redimensionados.