Скачать MetaTrader 5

Графические интерфейсы XI: Рефакторинг кода библиотеки (build 14.1)

27 июня 2017, 17:34
Anatoli Kazharski
38
1 528

Содержание


Введение

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

Целью последнего обновления библиотеки было оптимизировать код, уменьшить его объем и сделать реализацию еще более объектно-ориентированной. Все это сделает код более понятным для изучения. Подробное описание внесенных изменений позволит читателю самостоятельно дорабатывать библиотеку под собственные задачи, потратив на это минимум времени. 

Ввиду большого объема материала, описание последнего обновления библиотеки я разделил на две статьи. Вашему вниманию предлагается первая из них.


Общие свойства элементов

В первую очередь, изменения коснулись общих свойств элементов библиотеки. Раньше поля и методы класса некоторых свойств (цвет фона, рамки, текста и т.д) хранились в производных классах каждого отдельного элемента. С точки зрения объектно-ориентированного программирования это перегруженность. Теперь, когда все необходимые элементы графического интерфейса реализованы, можно легко определить часто повторяющиеся из класса в класс поля и методы для настройки общих свойств и перенести их в базовый класс. 

Определим полный список свойств, которые присущи всем элементам библиотеки и могут быть размещены в базовом классе.

  • Изображение (ярлык), загружаемое из файла или нарисованное программно.
  • Отступы для изображения по осям X и Y.
  • Цвет фона.
  • Цвет рамки.
  • Цвет текста.
  • Текст описания.
  • Отступы текста описания.

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

В иерархии классов каждого элемента управления есть два класса: CElementBase и CElement. В базовом классе CElementBase разместим поля и методы для следующих свойств: координаты, размеры, идентификаторы, индексы, а также режимы, свойственные каждому элементу из списка. В производном классе CElement расположим поля и методы для управления свойствами, которые относятся к внешнему виду элементов.

Кроме этого, в класс CElementBase добавлены методы для формирования имени графического объекта элементов. Ранее имя формировалось в методах создания элементов, но теперь, когда каждый элемент рисуется на одном отдельном объекте, появилась возможность сделать универсальные методы, которые могут находиться в базовом классе. 

Для установки и получения части имени, которая обозначает тип элемента, предназначены методы CElementBase::NamePart().

//+------------------------------------------------------------------+
//| Базовый класс элемента управления                                |
//+------------------------------------------------------------------+
class CElementBase
  {
protected:
   //--- Часть имени (тип элемента)
   string            m_name_part;
   //---
public:
   //--- (1) Сохраняет и (2) возвращает часть имени элемента
   void              NamePart(const string name_part)                { m_name_part=name_part;                }
   string            NamePart(void)                            const { return(m_name_part);                  }
  };

Для формирования полного имени графического объекта используется метод CElementBase::ElementName(). В метод нужно передать часть имени, которая обозначает тип элемента. Если окажется, что часть имени была уже установлена ранее, то переданное значение использоваться не будет. В связи с последними изменениями в библиотеке (см. ниже), такой подход используется в тех случаях, когда один элемент является производным от другого и часть имени нужно переопределить.

class CElementBase
  {
protected:
   //--- Имя элемента
   string            m_element_name;
   //---
public:
   //--- Формирование имени объекта
   string            ElementName(const string name_part="");
  };
//+------------------------------------------------------------------+
//| Возвращает сформированное имя элемента                           |
//+------------------------------------------------------------------+
string CElementBase::ElementName(const string name_part="")
  {
   m_name_part=(m_name_part!="")? m_name_part : name_part;
//--- Формирование имени объекта
   string name="";
   if(m_index==WRONG_VALUE)
      name=m_program_name+"_"+m_name_part+"_"+(string)CElementBase::Id();
   else
      name=m_program_name+"_"+m_name_part+"_"+(string)CElementBase::Index()+"__"+(string)CElementBase::Id();
//---
   return(name);
  }

При обработке клика мышью на том или ином элементе нужно проверять имя графического объекта, на котором было нажатие. Такая проверка часто повторялась для многих элементов, поэтому в базовом классе был размещен и специальный метод CElementBase::CheckElementName():

class CElementBase
  {
public:
   //--- Проверка строки на содержание значимой части имени элемента
   bool              CheckElementName(const string object_name);
  };
//+------------------------------------------------------------------+
//| Возвращает сформированное имя элемента                           |
//+------------------------------------------------------------------+
bool CElementBase::CheckElementName(const string object_name)
  {
//--- Если нажатие было не на этом элементе
   if(::StringFind(object_name,m_program_name+"_"+m_name_part+"_")<0)
      return(false);
//---
   return(true);
  }

Что касается остальных свойств, то подробнее имеет смысл остановиться только на описании методов для работы с изображениями.


Класс для работы с данными изображения

Для работы с изображениями реализован класс CImage, в котором можно хранить данные изображения:

  • массив пикселей изображения;
  • размеры изображения (ширина и высота);
  • путь к файлу.

Чтобы получить значения этих свойств, понадобятся соответствующие методы:

//+------------------------------------------------------------------+
//| Класс для хранения данных изображения                            |
//+------------------------------------------------------------------+
class CImage
  {
protected:
   uint              m_image_data[]; // Массив пикселей картинки
   uint              m_image_width;  // Ширина изображения
   uint              m_image_height; // Высота изображения
   string            m_bmp_path;     // Путь к файлу изображения
   //---
public:
                     CImage(void);
                    ~CImage(void);
   //--- (1) Размер массива данных, (2) установить/вернуть данные (цвет пикселя)
   uint              DataTotal(void)                             { return(::ArraySize(m_image_data)); }
   uint              Data(const uint data_index)                 { return(m_image_data[data_index]);  }
   void              Data(const uint data_index,const uint data) { m_image_data[data_index]=data;     }
   //--- Установить/вернуть ширину изображения
   void              Width(const uint width)                     { m_image_width=width;               }
   uint              Width(void)                                 { return(m_image_width);             }
   //--- Установить/вернуть высоту изображения
   void              Height(const uint height)                   { m_image_height=height;             }
   uint              Height(void)                                { return(m_image_height);            }
   //--- Установить/вернуть путь к изображению
   void              BmpPath(const string bmp_file_path)         { m_bmp_path=bmp_file_path;          }
   string            BmpPath(void)                               { return(m_bmp_path);                }
  };

Чтобы прочесть изображение и сохранить его данные, используется метод CImage::ReadImageData(), в который нужно передать путь к файлу изображения:

class CImage
  {
public:
   //--- Читает и сохраняет данные переданного изображения
   bool              ReadImageData(const string bmp_file_path);
  };
//+------------------------------------------------------------------+
//| Сохраняет переданную картинку в массив                           |
//+------------------------------------------------------------------+
bool CImage::ReadImageData(const string bmp_file_path)
  {
//--- Выйти, если пустая строка
   if(bmp_file_path=="")
      return(false);
//--- Сохраним путь к изображению
   m_bmp_path=bmp_file_path;
//--- Сбросить последнюю ошибку
   ::ResetLastError();
//--- Прочитать и сохранить данные изображения
   if(!::ResourceReadImage("::"+m_bmp_path,m_image_data,m_image_width,m_image_height))
     {
      ::Print(__FUNCTION__," > Ошибка при чтении изображения ("+m_bmp_path+"): ",::GetLastError());
      return(false);
     }
//---
   return(true);
  }

Иногда нужно скопировать данные переданного изображения. Для этого служит метод CImage::CopyImageData().  В этот метод по ссылке передаётся объект типа CImage, данные массива которого нужно скопировать. Здесь сначала получаем размер массива-источника и устанавливаем такой же массиву-приёмнику. Затем, в цикле с помощью метода CImage::Data() получаем данные переданного массива, сохраняя их в массиве-приёмнике.

class CImage
  {
public:
   //--- Копирует данные переданного изображения
   void              CopyImageData(CImage &array_source);
  };
//+------------------------------------------------------------------+
//| Копирует данные переданного изображения                          |
//+------------------------------------------------------------------+
void CImage::CopyImageData(CImage &array_source)
  {
//--- Получим размер массива-источника
   uint source_data_total =array_source.DataTotal();
//--- Изменить размер массива-приёмника
   ::ArrayResize(m_image_data,source_data_total);
//--- Копируем данные
   for(uint i=0; i<source_data_total; i++)
      m_image_data[i]=array_source.Data(i);
  }

Чтобы удалить данные изображения, используется метод CImage::DeleteImageData():

class CImage
  {
public:
   //--- Удаляет данные изображения
   void              DeleteImageData(void);
  };
//+------------------------------------------------------------------+
//| Удаляет данные изображения                                       |
//+------------------------------------------------------------------+
void CImage::DeleteImageData(void)
  {
   ::ArrayFree(m_image_data);
   m_image_width  =0;
   m_image_height =0;
   m_bmp_path     ="";
  }

Класс CImage содержится в файле Objects.mqh. Теперь все элементы будут рисоваться, поэтому классы для создания графических объектов-примитивов теперь не нужны. Из файла Objects.mqh они были удалены. Исключение сделано только для объекта-графика, который позволяет создавать графики, аналогичные главному графику символа. В его окне располагаются все типы MQL-приложений и, соответственно, создаётся графический интерфейс.

//+------------------------------------------------------------------+
//|                                                      Objects.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Enums.mqh"
#include "Defines.mqh"
#include "Fonts.mqh"
#include "Canvas\Charts\LineChart.mqh"
#include <ChartObjects\ChartObjectSubChart.mqh>
//--- Список классов в файле для быстрого перехода (Alt+G)
class CImage;
class CRectCanvas;
class CLineChartObject;
class CSubChart;
...


Методы для работы с изображениями

Для работы с изображениями элементов реализовано несколько методов. Все они размещены в классе CElement. Теперь есть возможность установить для того или иного элемента необходимое количество групп (массивов) изображений. Это позволяет делать внешний вид элементов более информативным. Сколько ярлыков будет отображаться в элементе управления, решает сам разработчик MQL-приложения. 

Для этого в классе CElement создана структура EImagesGroup и объявлен динамический массив её экземпляров. В ней будут храниться свойства групп изображений (отступы и выбранное для показа изображение), а также сами изображения, для которых предназначен динамический массив типа CImage

//+------------------------------------------------------------------+
//| Производный класс элемента управления                            |
//+------------------------------------------------------------------+
class CElement : public CElementBase
  {
protected:
   //--- Группы изображений
   struct EImagesGroup
     {
      //--- Массив изображений
      CImage            m_image[];
      //--- Отступы ярлыка
      int               m_x_gap;
      int               m_y_gap;
      //--- Выбранное для показа изображение в группе
      int               m_selected_image;
     };
   EImagesGroup      m_images_group[];
  };

Чтобы добавить изображение в элемент управления, нужно сначала добавить группу. Это можно сделать с помощью метода CElement::AddImagesGroup(). В качестве аргументов в него нужно передать отступы для изображений в этой группе от верхней левой точки элемента. По умолчанию для показа будет выбрано первое изображение в группе.

class CElement : public CElementBase
  {
public:
   //--- Добавление группы изображений
   void              AddImagesGroup(const int x_gap,const int y_gap);
  };
//+------------------------------------------------------------------+
//| Добавление группы изображений                                    |
//+------------------------------------------------------------------+
void CElement::AddImagesGroup(const int x_gap,const int y_gap)
  {
//--- Получим размер массива групп изображений
   uint images_group_total=::ArraySize(m_images_group);
//--- Добавим одну группу
   ::ArrayResize(m_images_group,images_group_total+1);
//--- Установить отступы для изображений
   m_images_group[images_group_total].m_x_gap=x_gap;
   m_images_group[images_group_total].m_y_gap=y_gap;
//--- Изображение по умолчанию
   m_images_group[images_group_total].m_selected_image=0;
  }

Для добавления изображения в группу предназначен метод CElement::AddImage(), указав в аргументах индекс группы и путь к файлу изображения. Изображение не будет добавлено, если нет ни одной группы. Также здесь есть корректировка для предотвращения выхода из диапазона. В случае выхода изображение будет добавлено в последнюю группу.

class CElement : public CElementBase
  {
public:
   //--- Добавление изображения в указанную группу
   void              AddImage(const uint group_index,const string file_path);
  };
//+------------------------------------------------------------------+
//| Добавление изображения в указанную группу                        |
//+------------------------------------------------------------------+
void CElement::AddImage(const uint group_index,const string file_path)
  {
//--- Получим размер массива групп изображений
   uint images_group_total=::ArraySize(m_images_group);
//--- Выйти, если нет ни одной группы
   if(images_group_total<1)
     {
      Print(__FUNCTION__,
      " > Добавить группу изображений можно с помощью методов CElement::AddImagesGroup()");
      return;
     }
//--- Предотвращение выхода из диапазона
   uint check_group_index=(group_index<images_group_total)? group_index : images_group_total-1;
//--- Получим размер массива изображений
   uint images_total=::ArraySize(m_images_group[check_group_index].m_image);
//--- Увеличим размер массива на один элемент
   ::ArrayResize(m_images_group[check_group_index].m_image,images_total+1);
//--- Добавим изображение
   m_images_group[check_group_index].m_image[images_total].ReadImageData(file_path);
  }

Можно добавить группу сразу с массивом изображений, воспользовавшись второй версией метода CElement::AddImagesGroup(). Здесь в качестве аргументов, кроме отступов, нужно передать массив, в котором указаны пути к файлам. Увеличив размер массива групп изображений на один элемент, программа в цикле добавит весь массив переданных изображений с помощью метода CElement::AddImage() (см. выше).

class CElement : public CElementBase
  {
public:
   //--- Добавление группы изображений с массивом изображений
   void              AddImagesGroup(const int x_gap,const int y_gap,const string &file_pathways[]);
  };
//+------------------------------------------------------------------+
//| Добавление группы изображений с массивом изображений             |
//+------------------------------------------------------------------+
void CElement::AddImagesGroup(const int x_gap,const int y_gap,const string &file_pathways[])
  {
//--- Получим размер массива групп изображений
   uint images_group_total=::ArraySize(m_images_group);
//--- Добавим одну группу
   ::ArrayResize(m_images_group,images_group_total+1);
//--- Установить отступы для изображений
   m_images_group[images_group_total].m_x_gap =x_gap;
   m_images_group[images_group_total].m_y_gap =y_gap;
//--- Изображение по умолчанию
   m_images_group[images_group_total].m_selected_image=0;
//--- Получим размер массива добавляемых изображений
   uint images_total=::ArraySize(file_pathways);
//--- Добавим изображения в новую группу, если был передан не пустой массив
   for(uint i=0; i<images_total; i++)
      AddImage(images_group_total,file_pathways[i]);
  }

Установить или заменить изображение в той или иной группе можно во время выполнения программы. Для этого используйте метод CElement::SetImage(), передавая в качестве аргументов (1) индекс группы, (2) индекс изображения и (3) путь к файлу. 

class CElement : public CElementBase
  {
public:
   //--- Установка/замена изображения
   void              SetImage(const uint group_index,const uint image_index,const string file_path);
  };
//+------------------------------------------------------------------+
//| Установка/замена изображения                                     |
//+------------------------------------------------------------------+
void CElement::SetImage(const uint group_index,const uint image_index,const string file_path)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(group_index,image_index))
      return;
//--- Удалить изображение
   m_images_group[group_index].m_image[image_index].DeleteImageData();
//--- Добавим изображение
   m_images_group[group_index].m_image[image_index].ReadImageData(file_path);
  }

Если же все необходимые изображения установлены изначально при создании элемента управления, то лучше будет просто переключать их с помощью метода CElement::ChangeImage():

class CElement : public CElementBase
  {
public:
   //--- Переключение изображения
   void              ChangeImage(const uint group_index,const uint image_index);
  };
//+------------------------------------------------------------------+
//| Переключение изображения                                         |
//+------------------------------------------------------------------+
void CElement::ChangeImage(const uint group_index,const uint image_index)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(group_index,image_index))
      return;
//--- Сохранить индекс изображения для показа
   m_images_group[group_index].m_selected_image=(int)image_index;
  }

Узнать, какое в данный момент времени выбрано изображение в той или иной группе, можно с помощью метода CElement::SelectedImage(). Если нет ни одной группы или в указанной группе нет изображений, метод вернёт отрицательное значение.

class CElement : public CElementBase
  {
public:
   //--- Возвращает выбранное для показа изображение в указанной группе
   int               SelectedImage(const uint group_index=0);
  };
//+------------------------------------------------------------------+
//| Возвращает выбранное для показа изображение в указанной группе   |
//+------------------------------------------------------------------+
int CElement::SelectedImage(const uint group_index=0)
  {
//--- Выйти, если нет ни одной группы
   uint images_group_total=::ArraySize(m_images_group);
   if(images_group_total<1 || group_index>=images_group_total)
      return(WRONG_VALUE);
//--- Выйти, если ни одного изображения в указанной группе
   uint images_total=::ArraySize(m_images_group[group_index].m_image);
   if(images_total<1)
      return(WRONG_VALUE);
//--- Вернуть выбранное для показа изображение
   return(m_images_group[group_index].m_selected_image);
  }

Ранее во всех классах элементов, в которых пользователю могло понадобиться отобразить ярлык, присутствовали методы для установки изображений. Например, кнопкам можно было установить ярлыки для отжатого и нажатого состояния, а также для состояния, когда элемент заблокирован. Эту возможность мы оставим, ведь это наглядный и понятный вариант. При этом отступы для ярлыков можно устанавливать, как и раньше, с помощью методов CElement::IconXGap() и CElement::IconYGap().

class CElement : public CElementBase
  {
protected:
   //--- Отступы ярлыка
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //---
public:
   //--- Отступы ярлыка
   void              IconXGap(const int x_gap)                       { m_icon_x_gap=x_gap;              }
   int               IconXGap(void)                            const { return(m_icon_x_gap);            }
   void              IconYGap(const int y_gap)                       { m_icon_y_gap=y_gap;              }
   int               IconYGap(void)                            const { return(m_icon_y_gap);            }
   //--- Установка ярлыков для активного и заблокированного состояния
   void              IconFile(const string file_path);
   string            IconFile(void);
   void              IconFileLocked(const string file_path);
   string            IconFileLocked(void);
   //--- Установка ярлыков для элемента в нажатом состоянии (доступен/заблокирован)
   void              IconFilePressed(const string file_path);
   string            IconFilePressed(void);
   void              IconFilePressedLocked(const string file_path);
   string            IconFilePressedLocked(void);
  };

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

//+------------------------------------------------------------------+
//| Установка картинки для активного состояния                       |
//+------------------------------------------------------------------+
void CElement::IconFile(const string file_path)
  {
//--- Если ещё нет ни одной группы изображений
   if(ImagesGroupTotal()<1)
     {
      m_icon_x_gap =(m_icon_x_gap!=WRONG_VALUE)? m_icon_x_gap : 0;
      m_icon_y_gap =(m_icon_y_gap!=WRONG_VALUE)? m_icon_y_gap : 0;
      //--- Добавим группу и изображение
      AddImagesGroup(m_icon_x_gap,m_icon_y_gap);
      AddImage(0,file_path);
      AddImage(1,"");
      //--- Изображение по умолчанию
      m_images_group[0].m_selected_image=0;
      return;
     }
//--- Установить изображение в первую группу первым элементом
   SetImage(0,0,file_path);
  }

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

class CElement : public CElementBase
  {
public:
   //--- Возвращает количество групп изображений
   uint              ImagesGroupTotal(void) const { return(::ArraySize(m_images_group)); }
   //--- Возвращает количество изображений в указанной группе
   int               ImagesTotal(const uint group_index);
  };
//+------------------------------------------------------------------+
//| Возвращает количество изображений в указанной группе             |
//+------------------------------------------------------------------+
int CElement::ImagesTotal(const uint group_index)
  {
//--- Проверка индекса группы
   uint images_group_total=::ArraySize(m_images_group);
   if(images_group_total<1 || group_index>=images_group_total)
      return(WRONG_VALUE);
//--- Количество изображений
   return(::ArraySize(m_images_group[group_index].m_image));
  }


Слияние элементов в рамках оптимизации кода библиотеки

До сих пор многие элементы управления практически повторялись, будучи уникальными лишь  в нескольких методах. Это очень сильно раздуло код. Поэтому в библиотеку внесены изменения, облегчившие код и упростившие понимание.

1. Ранее для создания кнопок было разработано два класса.

  • CSimpleButton — простая кнопка.
  • CIconButton — кнопка с ярлыком.

Но теперь в любом элементе можно создать ярлык базовыми средствами. Поэтому, чтобы создать кнопки с различными свойствами, сейчас уже  нет необходимости в наличии двух классов. Оставлен только один модифицированный класс CButton. Чтобы центрировать текст в кнопке, нужно просто включить соответствующий режим методом CElement::IsCenterText(), который можно применить к любому элементу.

class CElement : public CElementBase
  {
protected:
   //--- Режим выравнивания текста
   bool              m_is_center_text;
   //--- 
public:

   //--- Выравнивать текст по центру
   void              IsCenterText(const bool state)                  { m_is_center_text=state;          }
   bool              IsCenterText(void)                        const { return(m_is_center_text);        }
  };


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

  • CButtonsGroup — группа из простых кнопок.
  • CRadioButtons — группа радио-кнопок.
  • CIconButtonsGroup — группа кнопок с ярлыками.

Во всех этих классах кнопки создавались из стандартных графических объектов-примитивов. Теперь остался только класс CButtonsGroup, и кнопки в нём создаются как уже готовые элементы типа CButton

Режим радио-кнопок (когда всегда выделена одна из кнопок в группе), можно включить методом CButtonsGroup::RadioButtonsMode(). Внешний вид, как у радио-кнопок, включается с помощью метода CButtonsGroup::RadioButtonsStyle().

class CButtonsGroup : public CElement
  {
protected:
   //    Режим радио-кнопок
   bool              m_radio_buttons_mode;
   //--- Стиль отображения радио-кнопок
   bool              m_radio_buttons_style;
   //--- 
public:
   //--- (1) Установка режима и (2) стиля отображения радио-кнопок
   void              RadioButtonsMode(const bool flag)              { m_radio_buttons_mode=flag;       }
   void              RadioButtonsStyle(const bool flag)             { m_radio_buttons_style=flag;      }
  };


3. Рассмотрим следующие три класса для создания элементов с полями ввода:

  • CSpinEdit — числовое поле ввода.
  • CCheckBoxEdit — числовое поле ввода с чекбоксом.
  • CTextEdit — текстовое поле ввода.

Все свойства вышеперечисленных классов можно компактно уместить в одном, пусть это будет класс CTextEdit. Если нужно создать числовое текстовое поле с переключателями инкремента и декремента, то включите соответствующий режим с помощью метода CTextEdit::SpinEditMode(). Если нужно при этом, чтобы в элементе присутствовал чекбокс, включите этот режим методом CTextEdit::CheckBoxMode(). 

class CTextEdit : public CElement
  {
protected:
   //--- Режим элемента с чекбоксом
   bool              m_checkbox_mode;
   //--- Режим числового поля ввода с кнопками
   bool              m_spin_edit_mode;
   //--- 
public:
   //--- (1) Режимы чекбокса и (2) числового поля ввода
   void              CheckBoxMode(const bool state)          { m_checkbox_mode=state;              }
   void              SpinEditMode(const bool state)          { m_spin_edit_mode=state;             }
  };


4. То же самое касается элементов для создания комбобоксов. Ранее было два класса:

  • CComboBox — кнопка с вызовом выпадающего списка.
  • CCheckComboBox — кнопка с вызовом выпадающего списка и с чекбоксом.

Два почти одинаковых класса держать избыточно, поэтому остался только один — CComboBox. Режим элемента с чек-боксом можно включить с помощью метода CComboBox::CheckBoxMode().

class CComboBox : public CElement
  {
protected:
   //--- Режим элемента с чекбоксом
   bool              m_checkbox_mode;
   //--- 
public:
   //--- Установка режима элемента с чекбоксом
   void              CheckBoxMode(const bool state)         { m_checkbox_mode=state;                  }
  };


5. Для создания слайдеров ранее предназначались два класса:

  • CSlider — простой числовой слайдер.
  • CDualSlider — двойной слайдер для указания числового диапазона.

Из них остался только один — класс CSlider. Режим двойного слайдера включается с помощью метода CSlider::DualSliderMode().

class CSlider : public CElement
  {
protected:
   //--- Режим двойного слайдера
   bool              m_dual_slider_mode;
   //--- 
public:
   //--- Режим двойного слайдера
   void              DualSliderMode(const bool state)           { m_dual_slider_mode=state;           }
   bool              DualSliderMode(void)                 const { return(m_dual_slider_mode);         }
  };


6. Ранее было два класса для создания списков с полосой прокрутки, один из которых позволял создавать список с чекбоксами:

  • CListView — простой список с возможностью выбора одного пункта.
  • CCheckBoxList — список с чекбоксами.

Из них в библиотеке остался только CListView, а чтобы создать список с чекбоксами, достаточно включить соответствующий режим методом CListView::CheckBoxMode().

class CListView : public CElement
  {
protected:
   //--- Режим списка с чекбоксами
   bool              m_checkbox_mode;
   //--- 
public:
   //--- Установка режима элемента с чекбоксом
   void              CheckBoxMode(const bool state)         { m_checkbox_mode=state;                  }
  };


7. В предыдущей версии библиотеки было целых три класса таблиц:

  • CTable — таблица из полей ввода.
  • CLabelsTable — таблица из текстовых меток.
  • CCanvasTable — нарисованная таблица.

В процессе доработки библиотеки самым развитым оказался класс CCanvasTable. Поэтому другие классы удалены, а класс CCanvasTable переименован в CTable.


8. Для создания вкладок ранее было два класса:

  • CTabs — простые вкладки.
  • CIconTabs — вкладки с ярлыками.

Эти два класса содержать в библиотеке тоже нет необходимости. Массив кнопок-вкладок ранее создавался из графических объектов-примитивов. Теперь для этих целей используются кнопки типа CButton, в которых можно определить ярлыки с помощью базовых методов, описанных выше. В итоге оставляем только класс CTabs.


Иерархия элементов управления

Изменилась и иерархия элементов управления графического интерфейса. До сих пор все элементы привязывались к форме (CWindow). Элементы располагались на форме относительно её левой верхней точки. При создании графического интерфейса нужно указывать координаты для каждого элемента. И если в процессе доработки изменялось расположение элемента, то для всех элементов, которые должны находиться в определенной области внутри него, нужно было заново вручную устанавливать координаты относительно этой области. Например, в области элемента «Вкладки» располагаются группы других элементов управления. И если бы нам понадобилось переместить «Вкладки» в другую часть формы, то все элементы на каждой отдельной вкладке остались бы на прежних позициях. Это очень неудобно, но теперь проблема решена.

Ранее перед тем, как создать в пользовательском классе MQL-приложения тот или иной элемент управления, нужно было обязательно с помощью метода CElement::WindowPointer() передать ему объект формы для сохранения указателя на неё. Теперь для этого используется метод CElement::MainPointer(). Он передает в качестве аргумента объект того элемента, к которому нужно привязать создаваемый элемент. 

class CElement : public CElementBase
  {
protected:
   //--- Указатель на главный элемент
   CElement         *m_main;
   //--- 
public:
   //--- Сохраняет и возвращает указатель на главный элемент
   CElement         *MainPointer(void)                               { return(::GetPointer(m_main));    }
   void              MainPointer(CElement &object)                   { m_main=::GetPointer(object);     }
  };

Как и раньше, не получится создать элемент, если он не привязан к главному элементу. В основном методе создания элементов (во всех классах) есть проверка на наличие указателя на главный элемент. Метод для такой проверки переименован и теперь называется CElement::CheckMainPointer(). Здесь же сохраняется указатель на форму и указатель на объект курсора мыши. Также определяется и сохраняется идентификатор элемента, сохраняются идентификатор графика и номер подокна, к которым присоединяется MQL-приложение и его графический интерфейс. Ранее этот код повторялся из класса в класс.

class CElement : public CElementBase
  {
protected:
   //--- Проверка наличия указателя на главный элемент
   bool              CheckMainPointer(void);
  };
//+------------------------------------------------------------------+
//| Проверка наличия указателя на главный элемент                    |
//+------------------------------------------------------------------+
bool CElement::CheckMainPointer(void)
  {
//--- Если нет указателя
   if(::CheckPointer(m_main)==POINTER_INVALID)
     {
      //--- Вывести сообщение в журнал терминала
      ::Print(__FUNCTION__,
              " > Перед созданием элемента... \n...нужно передать указатель на главный элемент: "+
              ClassName()+"::MainPointer(CElementBase &object)");
      //--- Прервать построение графического интерфейса приложения
      return(false);
     }
//--- Сохранение указателя на форму
   m_wnd=m_main.WindowPointer();
//--- Сохранение указателя на курсор мыши
   m_mouse=m_main.MousePointer();
//--- Сохранение свойств
   m_id       =m_wnd.LastId()+1;
   m_chart_id =m_wnd.ChartId();
   m_subwin   =m_wnd.SubwindowNumber();
//--- Отправить признак наличия указателя
   return(true);
  }

Подобный метод привязки элементов к главному элементу распространяется по всем классам. Сложные составные элементы собираются из нескольких других, и все они привязываются друг к другу в определённой последовательности. Исключение — только три класса:

  • CTable — класс для создания таблицы.
  • CListView — класс для создания списка.
  • CTextBox — класс для создания многострочного поля ввода.

Они собираются из нескольких графических объектов типа OBJ_BITMAP_LABEL, содержимое которых, тем не менее, тоже рисуется. Тестирование различных подходов показало, что использование нескольких объектов — оптимальный способ для данного этапа развития библиотеки.

Класс CButton стал своего рода универсальным «кирпичиком», который используется практически во всех других элементах управления библиотеки. Кроме этого, теперь элемент типа CButton (кнопка) — базовый для некоторых других классов элементов, таких как: 

  • CMenuItem — пункт меню.
  • CTreeItem — пункт древовидного списка.

В итоге общая схема взаимосвязей классов в формате "базовый—производный" ("родитель—потомок") выглядит сейчас так:

Рис. 1. Схема взаимосвязей классов, как базовый-производный (родитель-потомок).

Рис. 1. Схема взаимосвязей классов, в формате базовый—производный (родитель—потомок).


На текущий момент в библиотеке реализовано 33 (тридцать три) различных элемента управления. Ниже показан ряд схем, на которых изображены все элементы библиотеки в алфавитном порядке. Корневые элементы отмечены зелёным цветом и пронумерованы. Далее идут подключаемые элементы на всю глубину вложенности. Каждая колонка — очередной слой подключенных элементов для того или иного элемента управления. Символом ‘[]’ отмечены элементы, которые присутствуют в нескольких экземплярах. Такой взгляд поможет читателю быстрее и лучше разобраться с тем, как устроена ООП-схема библиотеки.

На иллюстрациях ниже показаны схемы следующих элементов:

1. CButton — кнопка.
2. CButtonsGroup — группа кнопок.
3. CCalendar — календарь.
4. CCheckBox — чекбокс.
5. CColorButton — кнопка для вызова цветовой палитры.
6. CColorPicker — цветовая палитра.
7. CComboBox — кнопка для вызова выпадающего списка (комбобокс).
8. CContextMenu — контекстное меню.

Рис. 2. Схематичное представление элементов управления (часть 1). 

Рис. 2. Схематичное представление элементов управления (часть 1).


9. CDropCalendar — кнопка для вызова выпадающего календаря.
10. CFileNavigator — файловый навигатор.
11. CLineGraph — линейный график.
12. CListView — список.

Рис. 3. Схематичное представление элементов управления (часть 2).

Рис. 3. Схематичное представление элементов управления (часть 2).


13. CMenuBar — главное меню.
14. CMenuItem — пункт меню.
15. CPicture — картинка.
16. CPicturesSlider — слайдер картинок.
17. CProgressBar — индикатор выполнения.
18. CScroll — полоса прокрутки.
19. CSeparateLine — разделительная линия.
20. CSlider — числовой слайдер.
21. CSplitButton — сдвоенная кнопка.
22. CStandartChart — стандартный график.
23. CStatusBar — статусная строка.
24. CTable — таблица.
 


Рис. 4. Схематичное представление элементов управления (часть 3).

Рис. 4. Схематичное представление элементов управления (часть 3).



25. CTabs — вкладки.
26. CTextBox — текстовое поле ввода с возможностью включения многострочного режима.
27. CTextEdit — поле ввода.
28. CTextLabel — текстовая метка.
29. CTimeEdit — элемент для ввода времени.
30. CTooltip — всплывающая подсказка.
31. CTreeItem — пункт древовидного списка.
32. CTreeView — древовидный список.
33. CWindow — форма (окно) для элементов управления.
 

Рис. 5. Схематичное представление элементов управления (часть 4).

Рис. 5. Схематичное представление элементов управления (часть 4).



Массив подключенных элементов

В предыдущей версии библиотеки при создании элементов в их базовом классе сохранялись указатели на объекты графических объектов-примитивов. Теперь же будут сохраняться указатели на элементы, которые являются составной частью того или иного элемента графического интерфейса. 

Указатели на элементы добавляются в динамический массив типа CElement с помощью защищённого (protected) метода CElement::AddToArray(). Этот метод предназначен для внутренних нужд и будет использоваться только в классах элементов. 

class CElement : public CElementBase
  {
protected:
   //--- Указатели на подключенные элементы
   CElement         *m_elements[];
   //---
protected:
   //--- Метод для добавления указателей на подключенные элементы
   void              AddToArray(CElement &object);
  };
//+------------------------------------------------------------------+
//| Метод для добавления указателей на подключенные элементы         |
//+------------------------------------------------------------------+
void CElement::AddToArray(CElement &object)
  {
   int size=ElementsTotal();
   ::ArrayResize(m_elements,size+1);
   m_elements[size]=::GetPointer(object);
  }

Предусмотрено получение указателя элемента из массива по указанному индексу. Для этих целей используется публичный (public) метод CElement::Element(). Кроме этого, доступны методы для получения количества подключенных элементов и освобождения массива.

class CElement : public CElementBase
  {
public:
   //--- (1) Получение количества подключенных элементов, (2) освобождение массива подключенных элементов
   int               ElementsTotal(void)                       const { return(::ArraySize(m_elements)); }
   void              FreeElementsArray(void)                         { ::ArrayFree(m_elements);         }
   //--- Возвращает указатель подключенного элемента по указанному индексу
   CElement         *Element(const uint index);
  };
//+------------------------------------------------------------------+
//| Возвращает указатель подключенного элемента                      |
//+------------------------------------------------------------------+
CElement *CElement::Element(const uint index)
  {
   uint array_size=::ArraySize(m_elements);
//--- Проверка размера массива объектов
   if(array_size<1)
     {
      ::Print(__FUNCTION__," > В этом элементе ("+m_class_name+") нет подключенных элементов!");
      return(NULL);
     }
//--- Корректировка в случае выхода из диапазона
   uint i=(index>=array_size)? array_size-1 : index;
//--- Вернуть указатель объекта
   return(::GetPointer(m_elements[i]));
  }


Базовый код виртуальных методов

Определён базовый код виртуальных методов, предназначенных для управления элементами. К этим методам относятся:

  • Moving() — перемещение.
  • Show() — показ.
  • Hide() — скрытие.
  • Delete() — удаление.
  • SetZorders() — установка приоритетов.
  • ResetZorders() — сброс приоритетов.

Теперь каждый элемент рисуется на отдельном графическом объекте. Поэтому вышеперечисленные методы можно сделать универсальными и исключить тем самым множественные повторы во всех классах элементов. Есть случаи, когда некоторые из этих методов будут переопределены в классах некоторых элементов. Именно для этого они объявлены как виртуальные. Например, к таким элементам можно отнести CListView, CTable и CTextBox. Ранее уже было рассказано, что в текущей версии только эти элементы библиотеки собираются из нескольких нарисованных графических объектов. 

Приведём в качестве примера метод CElement::Moving(). Теперь у него нет аргументов. Ранее в него передавались координаты и режим, в котором он мог работать. Но теперь в этом нет необходимости — всё стало существенно проще. Это связано с серьезными изменениями в ядре библиотеки (класс CWndEvents), но об этом я расскажу в одном из следующих разделов статьи. 

Элемент перемещается относительно текущего положения главного элемента, к которому он привязан. После того, как графический объект элемента перемещён, далее в цикле перемещаем подключенные к нему другие элементы, если они есть. Практически во всех элементах вызывается такая же копия метода CElement::Moving(). И так далее на всех уровнях вложенности. То есть, если нужно переместить какой-то элемент, то достаточно один раз вызвать метод, а аналогичные методы других элементов будут вызваны автоматически (последовательно в порядке их создания). 

В листинге ниже можно подробнее ознакомиться с кодом виртуального метода CElement::Moving():

class CElement : public CElementBase
  {
public:
   //--- Перемещение элемента
   virtual void      Moving(void);
  };
//+------------------------------------------------------------------+
//| Перемещение элемента                                             |
//+------------------------------------------------------------------+
void CElement::Moving(void)
  {
//--- Выйти, если элемент скрыт
   if(!CElementBase::IsVisible())
      return;
//--- Если привязка справа
   if(m_anchor_right_window_side)
     {
      //--- Сохранение координат в полях элемента
      CElementBase::X(m_main.X2()-XGap());
      //--- Сохранение координат в полях объектов
      m_canvas.X(m_main.X2()-m_canvas.XGap());
     }
   else
     {
      CElementBase::X(m_main.X()+XGap());
      m_canvas.X(m_main.X()+m_canvas.XGap());
     }
//--- Если привязка снизу
   if(m_anchor_bottom_window_side)
     {
      CElementBase::Y(m_main.Y2()-YGap());
      m_canvas.Y(m_main.Y2()-m_canvas.YGap());
     }
   else
     {
      CElementBase::Y(m_main.Y()+YGap());
      m_canvas.Y(m_main.Y()+m_canvas.YGap());
     }
//--- Обновление координат графических объектов
   m_canvas.X_Distance(m_canvas.X());
   m_canvas.Y_Distance(m_canvas.Y());
//--- Перемещение подключенных элементов
   int elements_total=ElementsTotal();
   for(int i=0; i<elements_total; i++)
      m_elements[i].Moving();
  }


Автоматическое определение приоритетов на нажатие левой кнопкой мыши

В предыдущей версии библиотеки приоритеты на нажатие левой кнопкой мыши прописывались вручную в классах каждого элемента библиотеки. Теперь, когда каждый элемент является одним графическим объектом, стало возможным реализовать автоматический режим определения приоритетов. Каждый графический объект, который оказывается над каким-либо другим объектом, получает более высокий приоритет:

Рис. 6. Наглядное представление определения приоритетов на нажатие левой кнопкой мыши. 

Рис. 6. Наглядное представление определения приоритетов на нажатие левой кнопкой мыши.


Но есть элементы, у которых вообще нет своего графического объекта. Как уже упоминалось ранее, есть и элементы, которые собираются из нескольких нарисованных объектов. В обоих случаях приоритет корректируется в классе каждого конкретного элемента. Для начала определим элементы, где понадобится такая корректировка.

Элементы без графических объектов:

  • CButtonsGroup — группа кнопок.
  • CDropCalendar — кнопка для вызова выпадающего календаря.
  • CSplitButton — сдвоенная кнопка.
  • CStandardChart — стандартный график.

Элементы с несколькими графическими объектами:

  • CListView — список.
  • CTable — таблица.
  • CTextBox — текстовое поле ввода.

Для установки и получения приоритета в класс CElement добавлены соответствующие методы:

class CElement : public CElementBase
  {
protected:
   //--- Приоритет на нажатие левой кнопки мыши
   long              m_zorder;
   //---
public:
   //--- Приоритет на нажатие левой кнопкой мыши
   long              Z_Order(void)                             const { return(m_zorder);                }
   void              Z_Order(const long z_order);
  };
//+------------------------------------------------------------------+
//| Приоритет на нажатие левой кнопкой мыши                          |
//+------------------------------------------------------------------+
void CElement::Z_Order(const long z_order)
  {
   m_zorder=z_order;
   SetZorders();
  }

Элементы, у которых нет графических объектов, — своего рода управляющие модули, к которым подключаются другие элементы. В самом таком элементе нечему устанавливать приоритет. Но относительно приоритета главного элемента рассчитывается  приоритет подключаемых элементов. Поэтому, чтобы всё правильно работало, нужно всё установить вручную.

Приоритет для элементов без объектов устанавливается таким же, как у их главного элемента:

...
//--- Приоритет, как у главного элемента, так как элемент не имеет своей области для нажатия
   CElement::Z_Order(m_main.Z_Order());
...

Для всех остальных элементов приоритет устанавливается после создания объекта для рисования. У форм это значение равно нулю, и далее для всех последующих создаваемых элементов оно увеличивается на 1 относительно главного:

...
//--- У всех элементов, кроме формы, приоритет больше, чем у главного элемента
   Z_Order((dynamic_cast<CWindow*>(&this)!=NULL)? 0 : m_main.Z_Order()+1);
...

В качестве примера рассмотрим ещё одну схему. Представим графический интерфейс из следующих элементов (перечислены в порядке создания):

  • Форма для элементов управления (CWindow). У формы могут быть включены кнопки (CButton) для (1) закрытия программы, (2) сворачивания/разворачивания формы и (3) всплывающие подсказки и т.д. В следующих обновлениях библиотеки могут быть добавлены другие кнопки, расширяющие возможности этого элемента.
  • Вкладки (CTabs). Кроме рабочей области, в которой размещаются группы элементов управления, в этом элементе присутствует группа кнопок (CButton), которые и являются вкладками.
  • На одной из вкладок разместим список (CListView) с полосой прокрутки (CScrollV), у которой есть кнопки (CButton) инкремента и декремента.
  • На ещё одной из вкладок разместим многострочное текстовое поле ввода (CTextBox) с горизонтальной (CScrollH) и вертикальной (CScrollV) полосами прокрутки.

Никаких действий по установке приоритетов для объектов графического интерфейса делать не нужно. Всё будет установлено автоматически, вот по такой схеме:

Рис. 7. Пример определения приоритетов на нажатие левой кнопкой мыши. 

Рис. 7. Пример определения приоритетов на нажатие левой кнопкой мыши.


Форма получает низший приоритет со значением 0 (ноль). У кнопок на форме приоритет со значением 1

Каждая кнопка-составная часть элемента типа CTabs (вкладки) получает приоритет 1, и рабочая область вкладок — тоже приоритет 1. Но в элементе CButtonsGroup будет храниться значение 0, ведь у него нет своего графического объекта, он является всего лишь управляющим модулем для кнопок типа CButton. В пользовательском классе MQL-приложения для указания главного элемента используйте метод CElement::MainPointer() (см. фрагмент кода ниже). Здесь главным элементом будет форма (CWindow), к которой прикрепляется элемент CTabs. Сохранять указатель нужно перед вызовом метода создания элементов.

...
//--- Сохраним указатель на главный элемент
   m_tabs1.MainPointer(m_window);
...

Список получает приоритет 2, так как его главный элемент здесь — CTabs. И это нужно обязательно указывать перед созданием элемента:

...
//--- Сохраним указатель на главный элемент
   m_listview1.MainPointer(m_tabs1);
...

Для полосы прокрутки не нужно указывать главный элемент, поскольку это уже реализовано внутри класса списка (CListView). То же относится ко всем элементам библиотеки, которые являются составной частью других элементов. Если для полосы прокрутки главный элемент — список (CListView) приоритет которого 2, то значение увеличивается на единицу (3). А для кнопок полосы прокрутки, которая является для них главным элементом, значение будет 4.

Точно так же, как и у списка (CListView), всё работает и в элементе CTextBox.


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

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

Рис. 12. Графический интерфейс тестового MQL-приложения.

Рис. 12. Графический интерфейс тестового MQL-приложения.


Это тестовое приложение прикреплено в конце статьи для более подробного изучения.



Заключение

Эта версия библиотеки имеет существенные отличия от той, которая была представлена в статье Графические интерфейсы X: Выделение текста в многострочном поле ввода (build 13). Проведена большая работа, которая коснулась практически всех файлов библиотеки. Теперь все элементы библиотеки рисуются на отдельных объектах. Читаемость кода стала лучше, объём его уменьшился приблизительно на 30%, а возможности улучшились. Исправлен ряд ошибок и недоработок, о которых сообщали пользователи. 

Если вы уже начали создавать свои MQL-приложения, используя предыдущую версию библиотеки, то рекомендуется сначала скачать новую версию в отдельно установленную копию торгового терминала MetaTrader 5, чтобы разобраться в ней и всё тщательно протестировать.

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

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

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


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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (38)
Реter Konow
Реter Konow | 3 июл 2017 в 18:59
Anatoli Kazharski:

Детский сад. )

Есть вещи намного более интересные, чем эта бессмысленная возня. Развивайтесь пока. У Вас пока не тот результат, чтобы на него вообще обращать внимание. Сами себя восхвалять конечно можете сколько угодно, если Вам это приносит столько удовольствия. )

Ну, ну...

В общем, буду ждать когда решитесь. Если не решитесь, то ничего страшного. Бывает.

Удачи.

Anatoli Kazharski
Anatoli Kazharski | 3 июл 2017 в 19:03
Реter Konow:

Ну, ну...

В общем, буду ждать когда решитесь. Если не решитесь, то ничего страшного. Бывает.

Удачи.

Да Пётр, у Вас бывает, что-то такое... странное. ) Особенно проявляется каждый раз после публикации моей новой статьи. )
Artyom Trishkin
Artyom Trishkin | 3 июл 2017 в 20:46
Реter Konow:

Я Вас на "батл" еще полгода назад в личке вызывал. Сравнить эффективность технологии всегда готов. Как только у Вас появится время и желание, - смело обращайтесь и я увиливать не стану. Здорово бы это публично провести. Это очень показательно и занимательно будет.)

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

Реter Konow
Реter Konow | 3 июл 2017 в 21:24
Artyom Trishkin:

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

А Вы баните по собственному усмотрению, или имеются какие то правила?
Artyom Trishkin
Artyom Trishkin | 3 июл 2017 в 23:44
Пётр забанен на сутки за флуд. Тему почистил от флуда и троллинга.
Пишем скальперский стакан цен на основе графической библиотеки CGraphic Пишем скальперский стакан цен на основе графической библиотеки CGraphic

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

Пользовательские индикаторы и инфографика в CCanvas Пользовательские индикаторы и инфографика в CCanvas

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

Использование облачных хранилищ для обмена данными между терминалами Использование облачных хранилищ для обмена данными между терминалами

Все большее распространение получают облачные технологии. К нашим услугам — как платные, так и бесплатные хранилища. Можем ли мы их использовать в трейдинге? В этой статье предлагается технология для обмена данными между терминалами с использованием облачных хранилищ.

Графические интерфейсы XI: Нарисованные элементы управления (build 14.2) Графические интерфейсы XI: Нарисованные элементы управления (build 14.2)

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