English 中文 Español Deutsch 日本語 Português
Графические интерфейсы X: Обновления для нарисованной таблицы и оптимизация кода (build 10)

Графические интерфейсы X: Обновления для нарисованной таблицы и оптимизация кода (build 10)

MetaTrader 5Примеры | 1 марта 2017, 08:37
3 268 19
Anatoli Kazharski
Anatoli Kazharski

Содержание

 

Введение

О том, для чего предназначена эта библиотека, более подробно можно прочитать в самой первой статье: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками. Там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.

Продолжим дополнять новыми возможностями нарисованную таблицу (CCanvasTable). На этот раз добавим следующие возможности.

  • Подсветку строк таблицы при наведении курсора мыши.
  • Возможность добавлять массив картинок для каждой ячейки и метод для их переключения.
  • Возможность задавать и изменять текст в ячейках во время выполнения программы. 

Кроме этого, оптимизируем код и некоторые алгоритмы, чтобы таблица перерисовывалась гораздо быстрее. 

 

Относительные координаты курсора на указанном холсте для рисования

Чтобы исключить повторяемый код во многих методах и классах для вычисления относительных координат на холсте для рисования, в класс CMouse добавлены дополнительные методы CMouse::RelativeX() и CMouse::RelativeY() для их получения. В эти методы нужно передать ссылку на объект типа CRectCanvas, чтобы рассчитать относительную координату с учётом текущего смещения области видимости холста для рисования.

//+------------------------------------------------------------------+
//| Класс для получения параметров мыши                              |
//+------------------------------------------------------------------+
class CMouse
  {
public:
   //--- Возвращает относительные координаты курсора мыши от переданного объекта-холста для рисования
   int               RelativeX(CRectCanvas &object);
   int               RelativeY(CRectCanvas &object);
  };
//+------------------------------------------------------------------+
//| Возвращает относительную X-координату курсора мыши               |
//| от переданного объекта-холста для рисования                      |
//+------------------------------------------------------------------+
int CMouse::RelativeX(CRectCanvas &object)
  {
   return(m_x-object.X()+(int)object.GetInteger(OBJPROP_XOFFSET));
  }
//+------------------------------------------------------------------+
//| Возвращает относительную Y-координату курсора мыши               |
//| от переданного объекта-холста для рисования                      |
//+------------------------------------------------------------------+
int CMouse::RelativeY(CRectCanvas &object)
  {
   return(m_y-object.Y()+(int)object.GetInteger(OBJPROP_YOFFSET));
  }

В дальнейшем развитии библиотеки эти методы будут использоваться для получения относительных координат у всех нарисованных элементов управления. 

 

Изменения в структуре таблицы

Чтобы максимально оптимизировать выполнение программного кода нарисованной таблицы, нужно было немного изменить и дополнить структуру таблицы типа CTOptions, а также добавить новые структуры, дающие возможность строить многомерные массивы. Наша задача — сделать так, чтобы некоторые фрагменты таблицы перерисовывались по уже рассчитанным ранее значениям. К примеру, это могут быть координаты границ столбцов и строк таблицы.

Например, рассчитывать и сохранять X-координаты границ столбцов имеет смысл в методе CCanvasTable::DrawGrid(), который используется для рисования сетки и только при рисовании всей таблицы. А когда пользователь выделяет ту или иную строку таблицы, то можно воспользоваться уже готовыми значениями. То же самое относится и к подсветке строк таблицы при наведении курсора мыши (это мы рассмотрим далее в статье). 

Для хранения Y-координат строк таблицы, а в будущем — возможно, и других свойств строк, создадим отдельную структуру (CTRowOptions) и объявим массив её экземпляров. Y-координаты строк рассчитываются в методе CCanvasTable::DrawRows(), который предназначен для рисования фона строк. Так как этот метод вызывается перед тем, как нарисовать сетку, то в методе CCanvasTable::DrawGrid() используются уже рассчитанные значения из структуры CTRowOptions

Для хранения свойств ячеек таблицы создадим отдельную структуру типа CTCell. Именно с этим типом объявляется массив экземпляров в структуре CTRowOptions, как массив строк таблицы. В этой структуре будут сохраняться:

  • Массив изображений
  • Массивы размеров изображений
  • Индекс выбранной (отображаемой) картинки в ячейке
  • Полный текст
  • Сокращённый текст
  • Цвет текста

Так как каждая картинка представляет собой массив пикселей, то понадобится отдельная структура (CTImage) с динамическим массивом для их хранения. С кодом перечисленных структур можно ознакомиться в листинге ниже:

class CCanvasTable : public CElement
  {
private:
   //--- Массив пикселей картинки
   struct CTImage { uint m_image_data[]; };
   //--- Свойства ячеек таблицы
   struct CTCell
     {
      CTImage           m_images[];       // Массив изображений
      uint              m_image_width[];  // Массив ширины изображений
      uint              m_image_height[]; // Массив высоты изображений
      int               m_selected_image; // Индекс выбранной (отображаемой) картинки
      string            m_full_text;      // Полный текст
      string            m_short_text;     // Сокращённый текст
      color             m_text_color;     // Цвет текста
     };
   //--- Массив строк и свойства столбцов таблицы
   struct CTOptions
     {
      int               m_x;             // X-координата левого края столбца
      int               m_x2;            // X-координата правого края столбца

      int               m_width;         // Ширина столбца
      ENUM_ALIGN_MODE   m_text_align;    // Способ выравнивания текста в ячейках столбца
      int               m_text_x_offset; // Отступ текста
      string            m_header_text;   // Текст заголовка столбца
      CTCell            m_rows[];        // Массив строк таблицы
     };
   CTOptions         m_columns[];
   //--- Массив свойств строк таблицы
   struct CTRowOptions
     {
      int               m_y;  // Y-координата верхнего края строки
      int               m_y2; // Y-координата нижнего края строки
     };
   CTRowOptions      m_rows[];

  };

Соответствующие исправления были внесены во все методы, где используются эти типы данных. 

 

Определение диапазона строк в области видимости

Поскольку строк у таблицы может быть очень много, то поиск фокуса на той или иной строке с последующей перерисовкой таблицы может существенно тормозить процесс. То же касается и выделения строки, и корректировки длины текста при ручном изменении ширины столбца. Чтобы избежать затормаживания, нужно определить первый и последний индекс в видимой области таблицы и организовывать цикл перебора уже только в этом диапазоне. Для этих целей реализован метод CCanvasTable::VisibleTableIndexes(). В нём сначала определяются границы видимой области. Верхняя граница — это смещение области видимости по оси Y, а нижняя граница определяется как верхняя + размер видимой области по оси Y.

Для определения индексов верхней и нижней строки видимой области таблицы теперь достаточно разделить полученные значения границ на установленную в свойствах таблицы высоту строки. В конце метода осуществляется корректировка в случае выхода за пределы последней строки таблицы.

class CCanvasTable : public CElement
  {
private:
   //--- Для определения индексов видимой части таблицы
   int               m_visible_table_from_index;
   int               m_visible_table_to_index;
   //---
private:
   //--- Определение индексов видимой области таблицы
   void              VisibleTableIndexes(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_visible_table_from_index(WRONG_VALUE),
                                   m_visible_table_to_index(WRONG_VALUE)
  {
...
  }
//+------------------------------------------------------------------+
//| Определение индексов видимой области таблицы                     |
//+------------------------------------------------------------------+
void CCanvasTable::VisibleTableIndexes(void)
  {
//--- Определяем границы с учётом смещения видимой области таблицы
   int yoffset1 =(int)m_table.GetInteger(OBJPROP_YOFFSET);
   int yoffset2 =yoffset1+m_table_visible_y_size;
//--- Определяем первый и последний индексы видимой области таблицы
   m_visible_table_from_index =int(double(yoffset1/m_cell_y_size));
   m_visible_table_to_index   =int(double(yoffset2/m_cell_y_size));

//--- Нижний индекс на один больше, если не выходим из диапазона
   m_visible_table_to_index=(m_visible_table_to_index+1>m_rows_total)? m_rows_total : m_visible_table_to_index+1;
  }

Определение индексов будет осуществляться в методе CCanvasTable::DrawTable(). В этот метод теперь можно передать аргумент для указания на то, что нужно перерисовать только видимую часть таблицы. По умолчанию значение аргумента равно false, что указывает на перерисовку всей таблицы. В листинге ниже показана сокращённая версия этого метода.

//+------------------------------------------------------------------+
//| Рисует таблицу                                                   |
//+------------------------------------------------------------------+
void CCanvasTable::DrawTable(const bool only_visible=false)
  {
//--- Если не указано перерисовать только видимую часть таблицы
   if(!only_visible)
     {
      //--- Установим индексы строк всей таблицы от самого начала и до конца
      m_visible_table_from_index =0;
      m_visible_table_to_index   =m_rows_total;

     }
//--- Получим индексы строк видимой части таблицы
   else
      VisibleTableIndexes();
//--- Нарисовать фон строк таблицы
//--- Нарисовать выделенную строку
//--- Нарисовать сетку
//--- Нарисовать картинку
//--- Нарисовать текст
//--- Отобразить последние нарисованные изменения
//--- Обновить заголовки, если они включены
//--- Корректировка таблицы относительно полос прокрутки
  }

Вызов метода CCanvasTable::VisibleTableIndexes() также необходим в методе для определения фокуса на строках таблицы: 

//+------------------------------------------------------------------+
//| Проверка фокуса на строках таблицы                                 |
//+------------------------------------------------------------------+
int CCanvasTable::CheckRowFocus(void)
  {
   int item_index_focus=WRONG_VALUE;
//--- Получим относительную Y-координату под курсором мыши
   int y=m_mouse.RelativeY(m_table);
///--- Получим индексы локальной области таблицы
   VisibleTableIndexes();
//--- Ищем фокус
   for(int i=m_visible_table_from_index; i<m_visible_table_to_index; i++)
     {
      //--- Если фокус строки изменился
      if(y>m_rows[i].m_y && y<=m_rows[i].m_y2)
        {
         item_index_focus=i;
         break;
        }
     }
//--- Вернём индекс строки в фокусе
   return(item_index_focus);
  }

 

 

Картинки в ячейках таблицы

За каждой ячейкой можно закрепить несколько изображений, которые в процессе выполнения программы будет возможно переключать. Добавим поля и методы для установки отступов картинки от верхнего и левого края ячейки:

class CCanvasTable : public CElement
  {
private:
   //--- Отступы для картинок от краёв ячеек
   int               m_image_x_offset;
   int               m_image_y_offset;
   //---
public:
   //--- Отступы картинок от краёв ячеек
   void              ImageXOffset(const int x_offset)     { m_image_x_offset=x_offset;       }
   void              ImageYOffset(const int y_offset)     { m_image_y_offset=y_offset;       }
  };

Для установки картинок в указанную ячейку нужно передать массив с их расположением в локальной директории терминала. Перед этим  они должны быть подключенными к MQL-приложению, как ресурсы (#resource). Для этого предназначен метод CCanvasTable::SetImages(). Здесь, если передан пустой массив или обнаружен выход за пределы массива, программа выходит из метода.

Если проверки пройдены, то массивам ячейки устанавливается новый размер и затем, в цикле с помощью метода ::ResourceReadImage() считываем содержимое картинки в одномерный массив, сохраняя в него цвета всех пикселей. В соответствующие массивы сохраняются размеры картинки. Они понадобятся для организации циклов, в которых картинки будут рисоваться на холсте. По умолчанию в ячейке будет выбрана первая картинка в массиве.

class CCanvasTable : public CElement
  {
public:
   //--- Устанавливает картинки в указанную ячейку
   void              SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[]);
  };
//+------------------------------------------------------------------+
//| Устанавливает картинки в указанную ячейку                        |
//+------------------------------------------------------------------+
void CCanvasTable::SetImages(const uint column_index,const uint row_index,const string &bmp_file_path[])
  {
   int total=0;
//--- Выйти, если передан массив нулевого размера
   if((total=CheckArraySize(bmp_file_path))==WRONG_VALUE)
      return;
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Установить новый размер массивам
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_images,total);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_width,total);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_image_height,total);
//---
   for(int i=0; i<total; i++)
     {
      //--- По умолчанию выбрана первая картинка массива
      m_columns[column_index].m_rows[row_index].m_selected_image=0;
      //--- Записать переданную картинку в массив и запомнить её размеры
      if(!ResourceReadImage(bmp_file_path[i],m_columns[column_index].m_rows[row_index].m_images[i].m_image_data,
         m_columns[column_index].m_rows[row_index].m_image_width[i],
         m_columns[column_index].m_rows[row_index].m_image_height[i]))

        {
         Print(__FUNCTION__," > error: ",GetLastError());
         return;
        }
     }
  }

Чтобы узнать, сколько картинок в той или иной ячейке, воспользуйтесь методом CCanvasTable::ImagesTotal():

class CCanvasTable : public CElement
  {
public:
   //--- Возвращает количество картинок в указанной ячейке
   int               ImagesTotal(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| Возвращает количество картинок в указанной ячейке                |
//+------------------------------------------------------------------+
int CCanvasTable::ImagesTotal(const uint column_index,const uint row_index)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return(WRONG_VALUE);
//--- Вернём размер массива картинок
   return(::ArraySize(m_columns[column_index].m_rows[row_index].m_images));
  }

Теперь рассмотрим методы, которые будут применяться для рисования картинок. Прежде всего, в класс CColors добавлен новый метод CColors::BlendColors(), который позволит корректно смешивать верхний и нижний цвета с учётом прозрачности наслаиваемого сверху изображения. А также вспомогательный метод CColors::GetA() для получения значения прозрачности переданного цвета.

В методе CColors::BlendColors() переданные цвета сначала разбиваются на RGB-компоненты, а из верхнего цвета извлекается альфа-канал. Альфа-канал преобразуется в значение от нуля до единицы. Если прозрачности в переданном цвете нет, то смешивание не производится. В случае же, если прозрачность есть, то каждая компонента двух переданных цветов смешивается с учётом прозрачности верхнего цвета. После этого значения полученных компонент корректируются, в случае выхода из диапазона (255). 

//+------------------------------------------------------------------+
//| Класс для работы с цветом                                        |
//+------------------------------------------------------------------+
class CColors
  {
public:
   double            GetA(const color aColor);
   color             BlendColors(const uint lower_color,const uint upper_color);
  };
//+------------------------------------------------------------------+
//| Получение значения компонента A                                  |
//+------------------------------------------------------------------+
double CColors::GetA(const color aColor)
  {
   return(double(uchar((aColor)>>24)));
  }
//+------------------------------------------------------------------+
//| Смешивание двух цветов с учётом прозрачности верхнего            |
//+------------------------------------------------------------------+
color CColors::BlendColors(const uint lower_color,const uint upper_color)
  {
   double r1=0,g1=0,b1=0;
   double r2=0,g2=0,b2=0,alpha=0;
   double r3=0,g3=0,b3=0;
//--- Конвертация цвета в формат ARGB
   uint pixel_color=::ColorToARGB(upper_color);
//--- Получим компоненты нижнего и верхнего цветов
   ColorToRGB(lower_color,r1,g1,b1);
   ColorToRGB(pixel_color,r2,g2,b2);
//--- Получим процент прозрачности от 0.00 до 1.00
   alpha=GetA(upper_color)/255.0;
//--- Если есть прозрачность
   if(alpha<1.0)
     {
      //--- Смешиваем компоненты с учётом альфа-канала
      r3=(r1*(1-alpha))+(r2*alpha);
      g3=(g1*(1-alpha))+(g2*alpha);
      b3=(b1*(1-alpha))+(b2*alpha);

      //--- Коррекция полученных значений
      r3=(r3>255)? 255 : r3;
      g3=(g3>255)? 255 : g3;
      b3=(b3>255)? 255 : b3;
     }
   else
     {
      r3=r2;
      g3=g2;
      b3=b2;
     }
//--- Соединить полученные компоненты и вернуть цвет
   return(RGBToColor(r3,g3,b3));
  }

Теперь несложно написать метод для рисования картинки. В листинге ниже представлен код метода CCanvasTable::DrawImage(). В него нужно передать индексы ячейки таблицы, в которой нужно нарисовать картинку. В начале метода получаем координаты картинки с учётом отступов, а также индекс выбранной ячейки и её размеры. Далее в двойном цикле попиксельно выводится изображение. Если указанный пиксель пустой, то есть не имеет цвета, то осуществляется переход к следующему. Если же цвет есть, то определяем цвет фона ячейки и цвет текущего пикселя, а затем смешиваем эти цвета с учётом прозрачности наслаиваемого цвета и рисуем полученный цвет на холсте.

class CCanvasTable : public CElement
  {
private:
   //--- Рисует изображение в указанной ячейке
   void              DrawImage(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Рисует изображение в указанной ячейке                            |
//+------------------------------------------------------------------+
void CCanvasTable::DrawImage(const int column_index,const int row_index)
  {
//--- Расчёт координат
   int x =m_columns[column_index].m_x+m_image_x_offset;
   int y =m_rows[row_index].m_y+m_image_y_offset;
//--- Выбранная картинка в ячейке и её размеры
   int  selected_image =m_columns[column_index].m_rows[row_index].m_selected_image;
   uint image_height   =m_columns[column_index].m_rows[row_index].m_image_height[selected_image];
   uint image_width    =m_columns[column_index].m_rows[row_index].m_image_width[selected_image];
//--- Рисуем
   for(uint ly=0,i=0; ly<image_height; ly++)
     {
      for(uint lx=0; lx<image_width; lx++,i++)
        {
         //--- Если нет цвета, перейти к следующему пикселю
         if(m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]<1)
            continue;

         //--- Получаем цвет нижнего слоя (фона ячейки) и цвет указанного пикселя картинки
         uint background  =(row_index==m_selected_item)? m_selected_row_color : m_table.PixelGet(x+lx,y+ly);
         uint pixel_color =m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i];
         //--- Смешиваем цвета
         uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
         //--- Рисуем пиксель наслаиваемого изображения
         m_table.PixelSet(x+lx,y+ly,foreground);
        }
     }
  }

Для рисования всех картинок таблицы сразу, с учётом, когда нужно нарисовать только видимую область таблицы, предназначен метод CCanvasTable::DrawImages(). В текущей версии таблицы картинки можно нарисовать, только если текст в столбце выравнен по левому краю. Кроме этого, на каждой итерации проверяется, установлена ли в ячейку картинка, а также не пуст ли массив её пикселей. Если все проверки пройдены, то вызывается метод CCanvasTable::DrawImage() для рисования картинки

class CCanvasTable : public CElement
  {
private:
   //--- Рисует все изображения таблицы
   void              DrawImages(void);
  };
//+------------------------------------------------------------------+
//| Рисует все изображения таблицы                                   |
//+------------------------------------------------------------------+
void CCanvasTable::DrawImages(void)
  {
//--- Для расчёта координат
   int x=0,y=0;
//--- Столбцы
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Если выравнивание текста не по левому краю, перейти к следующему столбцу
      if(m_columns[c].m_text_align!=ALIGN_LEFT)
         continue;
      //--- Строки
      for(int r=m_visible_table_from_index; r<m_visible_table_to_index; r++)
        {
         //--- Перейти к следующему, если в этой ячейке нет картинок
         if(ImagesTotal(c,r)<1)
            continue;
         //--- Выбранная картинка в ячейке (по умолчанию выбрана первая [0])
         int selected_image=m_columns[c].m_rows[r].m_selected_image;
         //--- Перейти к следующему, если массив пикселей пуст
         if(::ArraySize(m_columns[c].m_rows[r].m_images[selected_image].m_image_data)<1)
            continue;
         //--- Нарисовать картинку
         DrawImage(c,r);
        }
     }
  }

На скриншоте ниже показан пример, как выглядит таблица с картинками в ячейках:

 Рис. 1. Таблица с картинками в ячейках.

Рис. 1. Таблица с картинками в ячейках. 


 

Подсветка строки таблицы при наведении курсора мыши

Чтобы строки нарисованной таблицы подсвечивались при наведении курсора мыши, нам понадобятся дополнительные поля и методы. Для включения режима подсветки используйте метод CCanvasTable::LightsHover(). Цвет строки можно установить с помощью метода CCanvasTable::CellColorHover().

class CCanvasTable : public CElement
  {
private:
   //--- Цвет ячеек в разных состояниях
   color             m_cell_color;
   color             m_cell_color_hover;
   //--- Режим подсветки строки при наведении курсора мыши
   bool              m_lights_hover;
   //---
public:
   //--- Цвет ячеек в разных состояниях
   void              CellColor(const color clr)           { m_cell_color=clr;                }
   void              CellColorHover(const color clr)      { m_cell_color_hover=clr;          }
   //--- Режим подсветки строк при наведении курсора мыши
   void              LightsHover(const bool flag)         { m_lights_hover=flag;             }
  };

Чтобы подсветить строку, необязательно снова и снова перерисовывать всю таблицу при перемещении курсора. Более того, этого настоятельно не рекомендуется делать, потому что это сильно затормозит приложение и займет слишком много ресурсов процессора. При первом/новом входе курсора мыши в область таблицы искать фокус строки достаточно только один раз (в цикле во всём массиве строк). Для этого используется метод CCanvasTable::CheckRowFocus(). После того, как фокус найден и индекс строки сохранён, при перемещении курсора нужно просто проверять, не изменился ли фокус на строке с сохраненным индексом. Описанный алгоритм реализован в методе CCanvasTable::ChangeRowsColor(), код которого представлен ниже. Для изменения цвета строки используется метод CCanvasTable::RedrawRow(), с кодом которого мы познакомимся чуть позже. Метод CCanvasTable::ChangeRowsColor() вызывается в методе CCanvasTable::ChangeObjectsColor() для изменения цвета объектов таблицы. 

class CCanvasTable : public CElement
  {
private:
   //--- Для определения фокуса строки
   int               m_item_index_focus;
   //--- Для определение момента перехода курсора мыши с одной строки на другую
   int               m_prev_item_index_focus;
   //---
private:
   //--- Изменение цвета строк при наведении курсора мыши
   void              ChangeRowsColor(void);
  };
//+------------------------------------------------------------------+
//| Изменение цвета строк при наведении курсора мыши                 |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeRowsColor(void)
  {
//--- Выйти, если отключена подсветка строк при наведении
   if(!m_lights_hover)
      return;
//--- Если не в фокусе
   if(!m_table.MouseFocus())
     {
      //--- Если ещё не отмечено, что не в фокусе
      if(m_prev_item_index_focus!=WRONG_VALUE)
        {
         m_item_index_focus=WRONG_VALUE;
         //--- Изменить цвет
         RedrawRow();
         m_table.Update();
         //--- Сбросить фокус
         m_prev_item_index_focus=WRONG_VALUE;
        }
     }
//--- Если в фокусе
   else
     {
      //--- Проверить фокус над строками
      if(m_item_index_focus==WRONG_VALUE)
        {
         //--- Получим индекс строки в фокусе
         m_item_index_focus=CheckRowFocus();
         //--- Изменить цвет строки
         RedrawRow();
         m_table.Update();
         //--- Сохранить как предыдущий индекс в фокусе
         m_prev_item_index_focus=m_item_index_focus;
         return;
        }
      //--- Получим относительную Y-координату под курсором мыши
      int y=m_mouse.RelativeY(m_table);
      //--- Проверка фокуса
      bool condition=(y>m_rows[m_item_index_focus].m_y && y<=m_rows[m_item_index_focus].m_y2);
      //--- Если фокус изменился
      if(!condition)
        {
         //--- Получим индекс строки в фокусе
         m_item_index_focus=CheckRowFocus();
         //--- Изменить цвет строки
         RedrawRow();
         m_table.Update();
         //--- Сохранить как предыдущий индекс в фокусе
         m_prev_item_index_focus=m_item_index_focus;
        }
     }
  }

Метод для быстрой перерисовки строки таблицы CCanvasTable::RedrawRow() работает в двух режимах:

  •  при выделении строки
  •  в режиме подсветки строки при наведении курсора мыши.

Для указания режима нужно передать соответствующее значение аргумента метода. По умолчанию аргументу присвоено значение false, что означает использование метода в режиме подсветки строк таблицы. Для обоих режимов в классе есть специальные поля для определения текущей и предыдущей выделенной/подсвеченной строки таблицы. Таким образом, чтобы отметить другую строку, перерисовываются только предыдущая и текущая строки, а не вся таблица.

Программа выходит из метода, если индексы не определены (WRONG_VALUE). Далее нужно определить, сколько индексов определено. Если это первый заход в таблицу и определён только один индекс (текущий), то цвет будет изменён только, соответственно, у одной, текущей строки. Если заходим повторно, то цвет будет изменён у двух строк (текущей и предыдущей). 

Теперь нужно определить, в какой последовательности будет изменяться цвет строк. Если индекс текущей строки больше предыдущей, то это значит, что курсор переместился вниз. Тогда мы сначала изменяем цвет в предыдущем индексе, а потом в текущем. В обратной ситуации поступаем наоборот. Здесь учитывается также момент выхода из области таблицы, когда индекс текущей строки не определён, а индекс предыдущей строки ещё есть.

После того, как все локальные переменные для работы инициализированы, далее в строгой последовательности рисуется фон строк, сетка, картинки и текст.

class CCanvasTable : public CElement
  {
private:
   //--- Перерисовывает указанную строку таблицы по указанному режиму
   void              RedrawRow(const bool is_selected_row=false);
  };
//+------------------------------------------------------------------+
//| Перерисовывает указанную строку таблицы по указанному режиму     |
//+------------------------------------------------------------------+
void CCanvasTable::RedrawRow(const bool is_selected_row=false)
  {
//--- Текущий и предыдущий индексы строк
   int item_index      =WRONG_VALUE;
   int prev_item_index =WRONG_VALUE;
//--- Инициализация индексов строк относительно указанного режима
   if(is_selected_row)
     {
      item_index      =m_selected_item;
      prev_item_index =m_prev_selected_item;
     }

   else
     {
      item_index      =m_item_index_focus;
      prev_item_index =m_prev_item_index_focus;
     }

//--- Выйти, если индексы не определены
   if(prev_item_index==WRONG_VALUE && item_index==WRONG_VALUE)
      return;
//--- Количество строк и столбцов для рисования
   int rows_total    =(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE)? 2 : 1;
   int columns_total =m_columns_total-1;
//--- Координаты
   int x1=1,x2=m_table_x_size;
   int y1[2]={0},y2[2]={0};
//--- Массив для значений в определённой последовательности
   int indexes[2];
//--- Если (1) курсор мыши сдвинулся вниз или (2) в первый раз здесь
   if(item_index>m_prev_item_index_focus || item_index==WRONG_VALUE)
     {
      indexes[0]=(item_index==WRONG_VALUE || prev_item_index!=WRONG_VALUE)? prev_item_index : item_index;
      indexes[1]=item_index;
     }
//--- Если курсор мыши сдвинулся вверх
   else
     {
      indexes[0]=item_index;
      indexes[1]=prev_item_index;
     }
//--- Рисуем фон строк
   for(int r=0; r<rows_total; r++)
     {
      //--- Расчёт координат верхней и нижней границ строки
      y1[r]=m_rows[indexes[r]].m_y+1;
      y2[r]=m_rows[indexes[r]].m_y2-1;
      //--- Определим фокус на строке относительно режима подсветки
      bool is_item_focus=false;
      if(!m_lights_hover)
         is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE);
      else
         is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index);
      //--- Нарисовать фон строки
      m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],is_item_focus));
     }
//--- Цвет сетки
   uint clr=::ColorToARGB(m_grid_color);
//--- Рисуем границы
   for(int r=0; r<rows_total; r++)
     {
      for(int c=0; c<columns_total; c++)
         m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],clr);
     }
//--- Рисуем картинки
   for(int r=0; r<rows_total; r++)
     {
      for(int c=0; c<m_columns_total; c++)
        {
         //--- Рисуем картинку, если (1) она есть в этой ячейке и (2) в этом столбце выравнивание текста по левому краю
         if(ImagesTotal(c,r)>0 && m_columns[c].m_text_align==ALIGN_LEFT)
            DrawImage(c,indexes[r]);
        }
     }
//--- Для расчёта координат
   int x=0,y=0;
//--- Способ выравнивания текста
   uint text_align=0;
//--- Рисуем текст
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Получим (1) X-координату текста и (2) способ выравнивания текста
      x          =TextX(c);
      text_align =TextAlign(c,TA_TOP);
      //---
      for(int r=0; r<rows_total; r++)
        {
         //--- (1) Рассчитать координату и (2) нарисовать текст
         y=m_rows[indexes[r]].m_y+m_text_y_offset;
         m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align);
        }
     }
  }

В итоге получаем такой результат:

 Рис. 2. Демонстрация подсветки строк таблицы при наведении курсора мыши.

Рис. 2. Демонстрация подсветки строк таблицы при наведении курсора мыши. 

 

 

Методы для быстрой перерисовки ячейки таблицы

Мы рассмотрели методы для быстрой перерисовки строк таблицы. Теперь я продемонстрирую методы для быстрой перерисовки ячейки. Например, если нужно изменить текст, его цвет или картинку в какой-либо ячейке таблицы, то достаточно перерисовать только её, а не всю таблицу. Для этого используется приватный метод CCanvasTable::RedrawCell(). Перерисовываться будет только содержимое ячейки, а ее рамка обновляться не будет. Цвет фона определяется с учётом режима подсветки, если он включен. После определения значений и инициализации локальных переменных в ячейке рисуется фон, картинка (если установлена и выравнивание текста по левому краю) и текст.

class CCanvasTable : public CElement
  {
private:
   //--- Перерисовывает указанную ячейку таблицы
   void              RedrawCell(const int column_index,const int row_index);
  };
//+------------------------------------------------------------------+
//| Перерисовывает указанную ячейку таблицы                          |
//+------------------------------------------------------------------+
void CCanvasTable::RedrawCell(const int column_index,const int row_index)
  {
//--- Координаты
   int x1=m_columns[column_index].m_x+1;
   int x2=m_columns[column_index].m_x2-1;
   int y1=m_rows[row_index].m_y+1;
   int y2=m_rows[row_index].m_y2-1;
//--- Для расчёта координат
   int  x=0,y=0;
//--- Для проверки фокуса
   bool is_row_focus=false;
//--- Если включен режим подсветки строк таблицы
   if(m_lights_hover)
     {
      //--- (1) Получим относительную Y-координату курсора мыши и (2) фокус на указанной строке таблицы
      y=m_mouse.RelativeY(m_table);
      is_row_focus=(y>m_rows[row_index].m_y && y<=m_rows[row_index].m_y2);
     }

//--- Нарисовать фон ячейки
   m_table.FillRectangle(x1,y1,x2,y2,RowColorCurrent(row_index,is_row_focus));
//--- Рисуем картинку, если (1) она есть в этой ячейке и (2) в этом столбце выравнивание текста по левому краю
   if(ImagesTotal(column_index,row_index)>0 && m_columns[column_index].m_text_align==ALIGN_LEFT)
      DrawImage(column_index,row_index);
//--- Получим способ выравнивания текста
   uint text_align=TextAlign(column_index,TA_TOP);
//--- Рисуем текст
   for(int c=0; c<m_columns_total; c++)
     {
      //--- Получим X-координату текста
      x=TextX(c);
      //--- Остановим цикл
      if(c==column_index)
         break;
     }
//--- (1) Рассчитать Y-координату и (2) нарисовать текст
   y=y1+m_text_y_offset-1;
   m_table.TextOut(x,y,m_columns[column_index].m_rows[row_index].m_short_text,TextColor(column_index,row_index),text_align);
  }

Теперь рассмотрим методы, с помощью которых можно изменять текст, цвет текста и картинку (выбор из установленных) в ячейке. Для установки текста и его цвета нужно использовать публичные методы CCanvasTable::SetValue() и CCanvasTable::TextColor(). В эти методы передаются индексы ячейки (столбец и строка) и значение, которое нужно установить. Для метода CCanvasTable::SetValue() это строковое значение, которое будет отображаться в ячейке. Здесь в соответствующие поля структуры таблицы (CTCell) сохраняются полная переданная строка и сокращённая её версия, если вся строка не помещается в ячейку по ширине. Для метода CCanvasTable::TextColor() нужно передать цвет текста. В обоих методах в качестве четвёртого параметра можно указать, нужно ли сразу же перерисовать ячейку или это будет сделано позже, вызовом метода CCanvasTable::UpdateTable().

class CCanvasTable : public CElement
  {
private:
   //--- Устанавливает значение в указанную ячейку таблицы
   void              SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false);
   //--- Устанавливает цвет текста в указанную ячейку таблицы
   void              TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Заполняет массив по указанным индексам                           |
//+------------------------------------------------------------------+
void CCanvasTable::SetValue(const uint column_index,const uint row_index,const string value,const bool redraw=false)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Установить значение в массив
   m_columns[column_index].m_rows[row_index].m_full_text=value;
//--- Скорректировать и сохранить текст, если не помещается в ячейке
   m_columns[column_index].m_rows[row_index].m_short_text=CorrectingText(column_index,row_index);
//--- Перерисовать ячейку, если указано
   if(redraw)
      RedrawCell(column_index,row_index);

  }
//+------------------------------------------------------------------+
//| Заполняет массив цветом текста                                   |
//+------------------------------------------------------------------+
void CCanvasTable::TextColor(const uint column_index,const uint row_index,const color clr,const bool redraw=false)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Установить цвет текста в общий массив
   m_columns[column_index].m_rows[row_index].m_text_color=clr;
//--- Перерисовать ячейку, если указано
   if(redraw)
      RedrawCell(column_index,row_index);

  }

Измененить картинку в ячейке можно методом CCanvasTable::ChangeImage(). В качестве третьего параметра здесь нужно указать индекс картинки, на которую нужно переключиться. Здесь, как и в предыдущих описанных методах для изменения свойств ячейки, можно указать перерисовку сейчас или позже

class CCanvasTable : public CElement
  {
private:
   //--- Изменяет картинку в указанной ячейке
   void              ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Изменяет картинку в указанной ячейке                             |
//+------------------------------------------------------------------+
void CCanvasTable::ChangeImage(const uint column_index,const uint row_index,const uint image_index,const bool redraw=false)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Получим количество картинок ячейки
   int images_total=ImagesTotal(column_index,row_index);
//--- Выйти, если (1) картинок нет или (2) выходим из диапазона
   if(images_total==WRONG_VALUE || image_index>=(uint)images_total)
      return;
//--- Выйти, если указанная картинка совпадает с выбранной
   if(image_index==m_columns[column_index].m_rows[row_index].m_selected_image)
      return;
//--- Сохранить индекс выбранной картинки ячейки
   m_columns[column_index].m_rows[row_index].m_selected_image=(int)image_index;
//--- Перерисовать ячейку, если указано
   if(redraw)
      RedrawCell(column_index,row_index);

  }

Ещё один публичный метод понадобится для перерисовки всей таблицы — CCanvasTable::UpdateTable(). Его можно вызывать в двух режимах: 

  1. Когда нужно просто обновить таблицу для отображения последних изменений вышеописанными методами.
  2. Когда нужно полностью перерисовать таблицу, если были внесены изменения.

По умолчанию единственному аргументу метода установлено значение false, что означает обновление без перерисовки. 

class CCanvasTable : public CElement
  {
private:
   //--- Обновление таблицы
   void              UpdateTable(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Обновление таблицы                                               |
//+------------------------------------------------------------------+
void CCanvasTable::UpdateTable(const bool redraw=false)
  {
//--- Перерисовать таблицу, если указано
   if(redraw)
      DrawTable();
//--- Обновить таблицу
   m_table.Update();
  }

Ниже показан результат проделанной работы:

 Рис. 3. Демонстрация новых возможностей нарисованной таблицы.

Рис. 3. Демонстрация новых возможностей нарисованной таблицы.


Эксперта с демонстрацией этого результата можно скачать в приложенных к статье файлах. Во время выполнения программы картинки во всех ячейках таблицы (5 столбцов и 30 строк) будут изменяться с частотой 100 миллисекунд. На скриншоте ниже показана нагрузка на процессор без взаимодействия пользователя с графическим интерфейсом MQL-приложения. Нагрузка на процессор с частотой обновления 100 миллисекунд не превышает 3%.

 Рис. 4. Нагрузка на процессор во время выполнения тестового MQL-приложения.

Рис. 4. Нагрузка на процессор во время выполнения тестового MQL-приложения. 

 

 

Приложение для теста элемента

Текущая версия нарисованной таблицы уже достаточно "умна", чтобы создать такие же таблицы, как, например, в окне «Обзор рынка». Попробуем это продемонстрировать. Для примера создадим таблицу из 5 столбцов и 25 строк. Это будут 25 символов, которые есть на сервере MetaQuotes-Demo. Данные в таблице будут следующими:

  • Symbol – финансовые инструменты (валютные пары).
  • Bid – цены Bid.
  • Ask – цены Ask.
  • Spread (!) – разница между ценами Bid и Ask.
  • Time – время прихода последней котировки.

Для обозначения последнего направления в изменении цены подготовим такие же изображения, как и в таблице окна "Обзор рынка". Первая инициализация ячеек таблицы будет сразу же в методе создания элемента и осуществляется вызовом вспомогательного метода пользовательского класса CProgram::InitializingTable(). 

//+------------------------------------------------------------------+
//| Класс для создания приложения                                    |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Инициализация таблицы
   void              InitializingTable(void);
  };
//+------------------------------------------------------------------+
//| Инициализация таблицы                                            |
//+------------------------------------------------------------------+
void CProgram::InitializingTable(void)
  {
//--- Массив названий заголовков
   string text_headers[COLUMNS1_TOTAL]={"Symbol","Bid","Ask","!","Time"};
//--- Массив символов
   string text_array[25]=
     {
      "AUDUSD","GBPUSD","EURUSD","USDCAD","USDCHF","USDJPY","NZDUSD","USDSEK","USDHKD","USDMXN",
      "USDZAR","USDTRY","GBPAUD","AUDCAD","CADCHF","EURAUD","GBPCHF","GBPJPY","NZDJPY","AUDJPY",
      "EURJPY","EURCHF","EURGBP","AUDCHF","CHFJPY"
     };
//--- Массив картинок
   string image_array[3]=
     {
      "::Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp",
      "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp",
      "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp"
     };
//---
   for(int c=0; c<COLUMNS1_TOTAL; c++)
     {
      //--- Установим названия заголовков
      m_canvas_table.SetHeaderText(c,text_headers[c]);
      //---
      for(int r=0; r<ROWS1_TOTAL; r++)
        {
         //--- Установим картинки
         m_canvas_table.SetImages(c,r,image_array);
         //--- Установим названия символов
         if(c<1)
            m_canvas_table.SetValue(c,r,text_array[r]);
         //--- Для всех ячеек значение по умолчанию
         else
            m_canvas_table.SetValue(c,r,"-");
        }
     }
  }

Значения ячеек нашей таблицы в процессе работы будут обновляться по таймеру каждые 16 миллисекунд. Для этого создан ещё один вспомогательный метод CProgram::UpdateTable(). Здесь программа выходит из метода, если это выходные дни (суббота или воскресенье). Затем в двойном цикле организован проход по всем столбцам и строкам таблицы, где для каждого символа получаем два последних тика и, анализируя изменения цен, устанавливаем соответствующие значения. 

class CProgram : public CWndEvents
  {
private:
   //--- Инициализация таблицы
   void              InitializingTable(void);
  };
//+------------------------------------------------------------------+
//| Обновление значений таблицы                                      |
//+------------------------------------------------------------------+
void CProgram::UpdateTable(void)
  {
   MqlDateTime check_time;
   ::TimeToStruct(::TimeTradeServer(),check_time);
//--- Выйти, если это суббота или воскресенье
   if(check_time.day_of_week==0 || check_time.day_of_week==6)
      return;

//---
   for(int c=0; c<m_canvas_table.ColumnsTotal(); c++)
     {
      for(int r=0; r<m_canvas_table.RowsTotal(); r++)
        {
         //--- Для какого символа получаем данные
         string symbol=m_canvas_table.GetValue(0,r);
         //--- Получаем данные двух последних тиков
         MqlTick ticks[];
         if(::CopyTicks(symbol,ticks,COPY_TICKS_ALL,0,2)<2)
            continue;
         //--- Установить массив, как таймсерии
         ::ArraySetAsSeries(ticks,true);
         //--- Столбец символов - Symbol. Определим направление цены.
         if(c==0)
           {
            int index=0;
            //--- Если цены не изменились
            if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid)
               index=0;
            //--- Если цена Bid изменилась вверх
            else if(ticks[0].bid>ticks[1].bid)
               index=1;
            //--- Если цена Bid изменилась вниз
            else if(ticks[0].bid<ticks[1].bid)
               index=2;
            //--- Установим соответствующую картинку
            m_canvas_table.ChangeImage(c,r,index,true);
           }
         else
           {
            //--- Столбец разницы цен - Spread (!)
            if(c==3)
              {
               //--- Получим и установим размер спреда в пунктах
               int spread=(int)::SymbolInfoInteger(symbol,SYMBOL_SPREAD);
               m_canvas_table.SetValue(c,r,string(spread),true);
               continue;
              }
            //--- Получим количество знаков после запятой
            int digit=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS);
            //--- Столбец цен Bid
            if(c==1)
              {
               m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].bid,digit));
               //--- Если цена изменилась, установим соответствующий направлению цвет
               if(ticks[0].bid!=ticks[1].bid)
                  m_canvas_table.TextColor(c,r,(ticks[0].bid<ticks[1].bid)? clrRed : clrBlue,true);
               //---
               continue;
              }
            //--- Столбец цен Ask
            if(c==2)
              {
               m_canvas_table.SetValue(c,r,::DoubleToString(ticks[0].ask,digit));
               //--- Если цена изменилась установим соответствующий направлению цвет
               if(ticks[0].ask!=ticks[1].ask)
                  m_canvas_table.TextColor(c,r,(ticks[0].ask<ticks[1].ask)? clrRed : clrBlue,true);
               //---
               continue;
              }
            //--- Столбец времени последнего прихода цен символа
            if(c==4)
              {
               long   time     =::SymbolInfoInteger(symbol,SYMBOL_TIME);
               string time_msc =::IntegerToString(ticks[0].time_msc);
               int    length   =::StringLen(time_msc);
               string msc      =::StringSubstr(time_msc,length-3,3);
               string str      =::TimeToString(time,TIME_MINUTES|TIME_SECONDS)+"."+msc;
               //---
               color clr=clrBlack;
               //--- Если цены не изменились
               if(ticks[0].ask==ticks[1].ask && ticks[0].bid==ticks[1].bid)
                  clr=clrBlack;
               //--- Если цена Bid изменилась вверх
               else if(ticks[0].bid>ticks[1].bid)
                  clr=clrBlue;
               //--- Если цена Bid изменилась вниз
               else if(ticks[0].bid<ticks[1].bid)
                  clr=clrRed;
               //--- Установим значение и цвет текста
               m_canvas_table.SetValue(c,r,str);
               m_canvas_table.TextColor(c,r,clr,true);
               continue;
              }
           }
        }
     }
//--- Обновить таблицу
   m_canvas_table.UpdateTable();
  }

Результат у нас получился вот такой:

Рис. 5. Сравнение данных в окне "Обзор рынка" и пользовательского аналога.

Рис. 5. Сравнение данных в окне "Обзор рынка" и пользовательского аналога. 


В конце статьи Вы можете загрузить к себе на компьютер это тестовое приложение для более подробного изучения. 

 

Заключение

На текущем этапе разработки библиотеки для создания графических интерфейсов её общая схема выглядит так, как показано на рисунке ниже.

Рис. 6. Структура библиотеки на текущей стадии разработки. 

Рис. 6. Структура библиотеки на текущей стадии разработки.


Ниже вы можете загрузить к себе на компьютер последнюю версию библиотеки и файлы для тестов.

При возникновении вопросов по использованию материала, предоставленного в этих файлах, вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей этой серии или задать вопрос в комментариях к статье. 


Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (19)
Rashid Umarov
Rashid Umarov | 9 мар. 2017 в 10:40

Автоматическая справка по текущей версии

Mikhail Dovbakh
Mikhail Dovbakh | 9 мар. 2017 в 11:12
Просто супер!
Это вообще очень своевременно.
И для подобных библиотек просто необходимо.
Выходим на новый уровень стандартизации... )
Спасибо Rashid!
Alexander Fedosov
Alexander Fedosov | 10 мар. 2017 в 13:36
Rashid Umarov:

Автоматическая справка по текущей версии


Спасибо. Нужная вещь.
IuriiPrugov
IuriiPrugov | 3 июн. 2018 в 04:46
В Вашем случае на время разработки своего MQL-приложения в файле "MetaTrader 5\Config\metaeditor.ini" ставьте параметру Optimize нулевое значение, как показано ниже:
...
[Experts]
Author=Copyright 2015, MetaQuotes Software Corp.
Address=http://www.mql5.com
Optimize=0
...

/‌/---

Тогда всё будет компилироваться быстро: 

0 error(s), 0 warning(s), compile time: 351 msec                1       1

‌//---


Вопрос:  где найти этот параметр Optimize, у меня 1755 и 1816 версии на разных компах , но нет там такого параметра?

Anatoli Kazharski
Anatoli Kazharski | 3 июн. 2018 в 08:11
IuriiPrugov:

...

Вопрос:  где найти этот параметр Optimize, у меня 1755 и 1816 версии на разных компах , но нет там такого параметра?

Быстро в блокноте найти можно вот так:


Рецепты MQL5 - Торговые сигналы пивотов Рецепты MQL5 - Торговые сигналы пивотов
В статье представлен процесс разработки и реализации класса-сигнальщика на основе пивотов — разворотных уровней. На базе этого класса строится стратегия с использованием Стандартной библиотеки. Рассматриваются возможности развития стратегии пивотов посредством добавления фильтров.
Универсальный тренд с графическим интерфейсом Универсальный тренд с графическим интерфейсом
В статье на основе ряда стандартных индикаторов создается универсальный трендовый индикатор. Разрабатывается графический интерфейс для выбора типа индикатора и настройки его параметров. Индикатор отображается в отдельном окне с рядами разноцветных значков.
Готовые советники из Мастера MQL5 работают в MetaTrader 4 Готовые советники из Мастера MQL5 работают в MetaTrader 4
В статье предлагается простой эмулятор торгового окружения MetaTrader 5 для MetaTrader 4. С его помощью выполняются перенос и адаптация торговых классов стандартной библиотеки. В результате советники, генерируемые в Мастере MetaTrader 5, могут компилироваться и запускаться без изменений в MetaTrader 4.
Паттерны, доступные при торговле корзинами валют. Часть II Паттерны, доступные при торговле корзинами валют. Часть II
Продолжение разговора о паттернах, которые может обнаружить трейдер при торговле корзинами валютных пар. В этой части рассмотрены паттерны, образующиеся при использовании объединенных трендовых индикаторов. В качестве инструмента анализа применены индикаторы, построенные на основе индекса валюты.