English 中文 Español Deutsch 日本語 Português
Графические интерфейсы X: Сортировка, реконструкция таблицы и элементы управления в ячейках (build 11)

Графические интерфейсы X: Сортировка, реконструкция таблицы и элементы управления в ячейках (build 11)

MetaTrader 5Примеры | 16 марта 2017, 14:50
4 585 94
Anatoli Kazharski
Anatoli Kazharski

Содержание

‌‌

Введение

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

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

  • Сортировка данных таблицы.
  • Управление количеством столбцов и строк: добавление и удаление столбцов и строк по указанному индексу; полная очистка таблицы (остаётся только один столбец и одна строка); реконструкция таблицы (полная очистка и установка новой размерности).
  • Расширим возможности пользовательского управления графическим интерфейсом: добавляется обработка двойного клика мышью.
  • В ячейки таблицы начнем добавлять элементы управления: чекбоксы и кнопки.

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

Сортировка таблицы

Большинство методов для сортировки табличных данных остались такими же, как и в CTable. Как это всё устроено, вы можете подробно изучить в статье Графические интерфейсы X: Элемент "Время", элемент "Список из чекбоксов" и сортировка таблицы. Здесь кратко коснёмся изменений и дополнений, которые относятся к нарисованной таблице типа CCanvasTable.

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

//+------------------------------------------------------------------+
//| Класс для создания нарисованной таблицы                          |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- Ярлыки для признака отсортированных данных
   CTImage           m_sort_arrows[2];
   //---
public:
   //--- Установка картинок для признака отсортированных данных
   void              SortArrowFileAscend(const string path)  { m_sort_arrows[0].m_bmp_path=path; }
   void              SortArrowFileDescend(const string path) { m_sort_arrows[1].m_bmp_path=path; }
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) 
  {
...
//--- Инициализация структуры признака сортировки
   m_sort_arrows[0].m_bmp_path="";
   m_sort_arrows[1].m_bmp_path="";
  }
//+------------------------------------------------------------------+
//| Создаёт заголовки таблицы                                        |
//+------------------------------------------------------------------+
bool CCanvasTable::CreateHeaders(void)
  {
//--- Выйти, если заголовки отключены
   if(!m_show_headers)
      return(true);
//--- Формирование имени объекта
   string name=CElementBase::ProgramName()+"_table_headers_"+(string)CElementBase::Id();
//--- Координаты
   int x =m_x+1;
   int y =m_y+1;
//--- Определим картинки как признак возможности сортировки таблицы
   if(m_sort_arrows[0].m_bmp_path=="")
      m_sort_arrows[0].m_bmp_path="::Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrows[1].m_bmp_path=="")
      m_sort_arrows[1].m_bmp_path="::Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//---
   for(int i=0; i<2; i++)
     {
      ::ResetLastError();
      if(!::ResourceReadImage(m_sort_arrows[i].m_bmp_path,m_sort_arrows[i].m_image_data,
         m_sort_arrows[i].m_image_width,m_sort_arrows[i].m_image_height))
        {
         ::Print(__FUNCTION__," > error: ",::GetLastError());
        }
     }
//--- Создание объекта
   ::ResetLastError();
   if(!m_headers.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,m_table_x_size,m_header_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::Print(__FUNCTION__," > Не удалось создать холст для рисования заголовков таблицы: ",::GetLastError());
      return(false);
     }
//--- Прикрепить к графику
//--- Установим свойства
//--- Координаты
//--- Сохраним размеры
//--- Отступы от крайней точки панели
//--- Сохраним указатель объекта
//--- Установим размеры видимой области
//--- Зададим смещение фрейма внутри изображения по осям X и Y
...
   return(true);
  }

Для отрисовки признака отсортированного массива будет использоваться метод CCanvasTable::DrawSignSortedData().Этот элемент отрисовывается только если (1) режим сортировки включен и (2) сортировка данных таблицы уже была. Отступы можно контролировать с помощью методов CCanvasTable::SortArrowXGap() и CCanvasTable::SortArrowYGap(). Картинка обозначает, что сортировка по возрастанию будет иметь индекс 0, а сортировка по убыванию – 1. Далее следует двойной цикл рисования изображения. Эта тема уже была подробно рассмотрена.‌

class CCanvasTable : public CElement
  {
private:
   //--- Отступы для ярлыка-признака отсортированных данных
   int               m_sort_arrow_x_gap;
   int               m_sort_arrow_y_gap;
   //---
public:
   //--- Отступы для признака отсортированной таблицы
   void              SortArrowXGap(const int x_gap)          { m_sort_arrow_x_gap=x_gap;         }
   void              SortArrowYGap(const int y_gap)          { m_sort_arrow_y_gap=y_gap;         }
   //---
private:
   //--- Рисует признак возможности сортировки таблицы
   void              DrawSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_sort_arrow_x_gap(20),
                                   m_sort_arrow_y_gap(6)
  {
...
  }
//+------------------------------------------------------------------+
//| Рисует признак возможности сортировки таблицы                    |
//+------------------------------------------------------------------+
void CCanvasTable::DrawSignSortedData(void)
  {
//--- Выйти, если (1) сортировка отключена или (2) ещё не была осуществлена
   if(!m_is_sort_mode || m_is_sorted_column_index==WRONG_VALUE)
      return;
//--- Расчёт координат
   int x =m_columns[m_is_sorted_column_index].m_x2-m_sort_arrow_x_gap;
   int y =m_sort_arrow_y_gap;
//--- Выбранная картинка по направлению сортировки
   int image_index=(m_last_sort_direction==SORT_ASCEND)? 0 : 1;
//--- Рисуем
   for(uint ly=0,i=0; ly<m_sort_arrows[image_index].m_image_height; ly++)
     {
      for(uint lx=0; lx<m_sort_arrows[image_index].m_image_width; lx++,i++)
        {
         //--- Если нет цвета, перейти к следующему пикселю
         if(m_sort_arrows[image_index].m_image_data[i]<1)
            continue;
         //--- Получаем цвет нижнего слоя (фона заголовка) и цвет указанного пикселя картинки
         uint background  =m_headers.PixelGet(x+lx,y+ly);
         uint pixel_color =m_sort_arrows[image_index].m_image_data[i];
         //--- Смешиваем цвета
         uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
         //--- Рисуем пиксель наслаиваемого изображения
         m_headers.PixelSet(x+lx,y+ly,foreground);
        }
     }
  }

При сортировке значений таблицы, чтобы поменять их местами, используется метод CCanvasTable::Swap(). Он отличается тем, что здесь нужно перемещать не только отображаемый в ячейке текст и/или картинки, но и большее количество свойств ячейки.  При перемещении картинок для удобства, чтобы исключить повторяющиеся фрагменты кода, понадобится вспомогательный метод CCanvasTable::ImageCopy(). В него передаются два массива типа CTImage — приемник и источник данных. В качестве третьего аргумента передаётся индекс копируемого изображения, так как в одной ячейке их может быть несколько.‌

class CCanvasTable : public CElement
  {
private:
   //--- Копирует данные изображения из одного массива в другой
   void              ImageCopy(CTImage &destination[],CTImage &source[],const int index);
  };
//+------------------------------------------------------------------+
//| Копирует данные изображения из одного массива в другой           |
//+------------------------------------------------------------------+
void CCanvasTable::ImageCopy(CTImage &destination[],CTImage &source[],const int index)
  {
//--- Копируем пиксели картинки
   ::ArrayCopy(destination[index].m_image_data,source[index].m_image_data);
//--- Копируем свойства картинки
   destination[index].m_image_width  =source[index].m_image_width;
   destination[index].m_image_height =source[index].m_image_height;
   destination[index].m_bmp_path     =source[index].m_bmp_path;
  }

В итоге код метода CCanvasTable::Swap() выглядит так, как показано в листинге кода ниже. Перед тем, как переместить изображения, нужно сначала проверить, есть ли они, и если нет, то перейти к следующему столбцу. Если же в одной из ячеек есть картинки, то с помощью метода CCanvasTable::ImageCopy() они перемещаются. Эта операция такая же, как и в случае с перемещением других свойств ячейки. То есть, сначала запоминаем свойство первой ячейки. Затем на место первой копируем это же свойство из второй ячейки. И наконец, на место второй ячейки помещаем ранее сохранённое значение из первой.‌

class CCanvasTable : public CElement
  {
private:
   //--- Поменять значения в указанных ячейках местами
   void              Swap(uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| Меняет элементы местами                                          |
//+------------------------------------------------------------------+
void CCanvasTable::Swap(uint r1,uint r2)
  {
//--- Пройдёмся в цикле по всем столбцам
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- Меняем местами полный текст
      string temp_text                    =m_columns[c].m_rows[r1].m_full_text;
      m_columns[c].m_rows[r1].m_full_text =m_columns[c].m_rows[r2].m_full_text;
      m_columns[c].m_rows[r2].m_full_text =temp_text;
      //--- Меняем местами краткий текст
      temp_text                            =m_columns[c].m_rows[r1].m_short_text;
      m_columns[c].m_rows[r1].m_short_text =m_columns[c].m_rows[r2].m_short_text;
      m_columns[c].m_rows[r2].m_short_text =temp_text;
      //--- Меняем местами количество знаков после запятой
      uint temp_digits                 =m_columns[c].m_rows[r1].m_digits;
      m_columns[c].m_rows[r1].m_digits =m_columns[c].m_rows[r2].m_digits;
      m_columns[c].m_rows[r2].m_digits =temp_digits;
      //--- Меняем местами цвет текста
      color temp_text_color                =m_columns[c].m_rows[r1].m_text_color;
      m_columns[c].m_rows[r1].m_text_color =m_columns[c].m_rows[r2].m_text_color;
      m_columns[c].m_rows[r2].m_text_color =temp_text_color;
      //--- Меняем местами индекс выбранной картинки
      int temp_selected_image                  =m_columns[c].m_rows[r1].m_selected_image;
      m_columns[c].m_rows[r1].m_selected_image =m_columns[c].m_rows[r2].m_selected_image;
      m_columns[c].m_rows[r2].m_selected_image =temp_selected_image;
      //--- Проверим, есть ли картинки в ячейках
      int r1_images_total=::ArraySize(m_columns[c].m_rows[r1].m_images);
      int r2_images_total=::ArraySize(m_columns[c].m_rows[r2].m_images);
      //--- Переходим к следующему столбцу, если в обеих ячейках нет картинок
      if(r1_images_total<1 && r2_images_total<1)
         continue;
      //--- Меняем местами картинки
      CTImage r1_temp_images[];
      //---
      ::ArrayResize(r1_temp_images,r1_images_total);
      for(int i=0; i<r1_images_total; i++)
         ImageCopy(r1_temp_images,m_columns[c].m_rows[r1].m_images,i);
      //---
      ::ArrayResize(m_columns[c].m_rows[r1].m_images,r2_images_total);
      for(int i=0; i<r2_images_total; i++)
         ImageCopy(m_columns[c].m_rows[r1].m_images,m_columns[c].m_rows[r2].m_images,i);
      //---
      ::ArrayResize(m_columns[c].m_rows[r2].m_images,r1_images_total);
      for(int i=0; i<r1_images_total; i++)
         ImageCopy(m_columns[c].m_rows[r2].m_images,r1_temp_images,i);
     }
  }

Нажатие на один из заголовков таблицы вызывает метод CCanvasTable::OnClickHeaders(), который запускает сортировку данных. В этом классе он значительно проще, чем в таблице типа CTable: сравните и убедитесь в этом сами.

class CCanvasTable : public CElement
  {
private:
   //--- Обработка нажатия на заголовке
   bool              OnClickHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на заголовке                                   |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickHeaders(const string clicked_object)
  {
//--- Выйти, если (1) режим сортировки отключен или (2) в процессе изменения ширины столбца
   if(!m_is_sort_mode || m_column_resize_control!=WRONG_VALUE)
      return(false);
//--- Выйдем, если полоса прокрутки в активном режиме
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Выйдем, если чужое имя объекта
   if(m_headers.Name()!=clicked_object)
      return(false);
//--- Для определения индекса столбца
   uint column_index=0;
//--- Получим относительную X-координату под курсором мыши
   int x=m_mouse.RelativeX(m_headers);
//--- Определим заголовок, на котором нажали
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- Если нашли заголовок, запомним его индекс
      if(x>=m_columns[c].m_x && x<=m_columns[c].m_x2)
        {
         column_index=c;
         break;
        }
     }
//--- Сортировка данных по указанному столбцу
   SortData(column_index);
//--- Отправим сообщение об этом
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElementBase::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

Остальные методы для сортировки данных остались без изменения и ничем не отличаются от ранее рассмотренных.  Поэтому рассмотрим только список объявлений этих полей и методов в классе CCanvasTable:‌

class CCanvasTable : public CElement
  {
private:
   //--- Режим сортируемых данных по столбцам
   bool              m_is_sort_mode;
   //--- Индекс отсортированного столбца (WRONG_VALUE - таблица не отсортирована)
   int               m_is_sorted_column_index;
   //--- Последнее направление сортировки
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- Режим сортируемых данных
   void              IsSortMode(const bool flag)             { m_is_sort_mode=flag;              }

   //--- Получение/установка типа данных
   ENUM_DATATYPE     DataType(const uint column_index);
   void              DataType(const uint column_index,const ENUM_DATATYPE type);
   //--- Сортировать данные по указанному столбцу
   void              SortData(const uint column_index=0);
   //---
private:
   //--- Обработка нажатия на заголовке
   bool              OnClickHeaders(const string clicked_object);

   //--- Метод быстрой сортировки
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
   //--- Проверка условия сортировки
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };

Ниже вы можете посмотреть, как работает сортировка в этом типе таблиц:

 Рис. 1. Демонстрация сортировки в таблице типа CCanvasTable.‌

Рис. 1. Демонстрация сортировки в таблице типа CCanvasTable.‌


Добавление и удаление столбцов и строк

В одной из предыдущих статей для таблиц типа CTable рассматривались методы для добавления и удаления столбцов и строк. В той версии добавление было возможно только в конце таблицы. Сейчас устраним это досадное неудобство: сделаем так, чтобы можно было добавлять столбец или строку, указывая индекс места их добавления. 

Пример: добавим столбец в начало таблицы с данными: то есть, новый столбец должен стать первым (индекс 0). Массив столбцов должен увеличиться на один элемент, а свойства и значения всех ячеек таблицы — переместиться на один элемент вправо, оставив пустыми ячейки только нового столбца. Такой же принцип действует и при добавлении строк таблицы. 

Обратный принцип работает, когда нужно удалить столбец или строку. Попробуем удалить первый столбец из предыдущего нашего примера: сначала данные и свойства ячеек сместятся на один элемент влево, и уже после этого размер массива столбцов уменьшится на один элемент.

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

class CCanvasTable : public CElement
  {
private:
   //--- Инициализация указанного столбца значениями по умолчанию
   void              ColumnInitialize(const uint column_index);
   //--- Инициализация указанной ячейки значениями по умолчанию
   void              CellInitialize(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| Инициализация указанного столбца значениями по умолчанию         |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnInitialize(const uint column_index)
  {
//--- Инициализация свойств столбцов значениями по умолчанию
   m_columns[column_index].m_x              =0;
   m_columns[column_index].m_x2             =0;
   m_columns[column_index].m_width          =100;
   m_columns[column_index].m_type           =TYPE_STRING;
   m_columns[column_index].m_text_align     =ALIGN_CENTER;
   m_columns[column_index].m_text_x_offset  =m_text_x_offset;
   m_columns[column_index].m_image_x_offset =m_image_x_offset;
   m_columns[column_index].m_image_y_offset =m_image_y_offset;
   m_columns[column_index].m_header_text    ="";
  }
//+------------------------------------------------------------------+
//| Инициализация указанной ячейки значениями по умолчанию           |
//+------------------------------------------------------------------+
void CCanvasTable::CellInitialize(const uint column_index,const uint row_index)
  {
   m_columns[column_index].m_rows[row_index].m_full_text      ="";
   m_columns[column_index].m_rows[row_index].m_short_text     ="";
   m_columns[column_index].m_rows[row_index].m_selected_image =0;
   m_columns[column_index].m_rows[row_index].m_text_color     =m_cell_text_color;
   m_columns[column_index].m_rows[row_index].m_digits         =0;
   m_columns[column_index].m_rows[row_index].m_type           =CELL_SIMPLE;
//--- По умолчанию у ячейки нет картинок
   ::ArrayFree(m_columns[column_index].m_rows[row_index].m_images);
  }

Для того, чтобы скопировать свойства одного столбца в другой столбец нужно использовать метод CCanvasTable::ColumnCopy(), код которого показан в листинге ниже:‌

class CCanvasTable : public CElement
  {
private:
   //--- Делает копию указанного столбца (source) в новое место (dest.)
   void              ColumnCopy(const uint destination,const uint source);
  };
//+------------------------------------------------------------------+
//| Делает копию указанного столбца (source) в новое место (dest.)   |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnCopy(const uint destination,const uint source)
  {
   m_columns[destination].m_header_text    =m_columns[source].m_header_text;
   m_columns[destination].m_width          =m_columns[source].m_width;
   m_columns[destination].m_type           =m_columns[source].m_type;
   m_columns[destination].m_text_align     =m_columns[source].m_text_align;
   m_columns[destination].m_text_x_offset  =m_columns[source].m_text_x_offset;
   m_columns[destination].m_image_x_offset =m_columns[source].m_image_x_offset;
   m_columns[destination].m_image_y_offset =m_columns[source].m_image_y_offset;
  }

Метод CCanvasTable::CellCopy() копирует одну указанную ячейку в другую. Для этого нужно передать индексы столбца и строки ячейки-приёмника и индексы столбца и строки ячейки-источника.‌

class CCanvasTable : public CElement
  {
private:
   //--- Делает копию указанной ячейки (source) в новое место (dest.)
   void              CellCopy(const uint column_dest,const uint row_dest,const uint column_source,const uint row_source);
  };
//+------------------------------------------------------------------+
//| Делает копию указанной ячейки (source) в новое место (dest.)     |
//+------------------------------------------------------------------+
void CCanvasTable::CellCopy(const uint column_dest,const uint row_dest,const uint column_source,const uint row_source)
  {
   m_columns[column_dest].m_rows[row_dest].m_type           =m_columns[column_source].m_rows[row_source].m_type;
   m_columns[column_dest].m_rows[row_dest].m_digits         =m_columns[column_source].m_rows[row_source].m_digits;
   m_columns[column_dest].m_rows[row_dest].m_full_text      =m_columns[column_source].m_rows[row_source].m_full_text;
   m_columns[column_dest].m_rows[row_dest].m_short_text     =m_columns[column_source].m_rows[row_source].m_short_text;
   m_columns[column_dest].m_rows[row_dest].m_text_color     =m_columns[column_source].m_rows[row_source].m_text_color;
   m_columns[column_dest].m_rows[row_dest].m_selected_image =m_columns[column_source].m_rows[row_source].m_selected_image;
//--- Копируем размер массива из источника в приёмник
   int images_total=::ArraySize(m_columns[column_source].m_rows[row_source].m_images);
   ::ArrayResize(m_columns[column_dest].m_rows[row_dest].m_images,images_total);
//---
   for(int i=0; i<images_total; i++)
     {
      //--- Копировать, если есть картинки
      if(::ArraySize(m_columns[column_source].m_rows[row_source].m_images[i].m_image_data)<1)
         continue;
      //--- Делаем копию картинки
      ImageCopy(m_columns[column_dest].m_rows[row_dest].m_images,m_columns[column_source].m_rows[row_source].m_images,i);
     }
  }

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

class CCanvasTable : public CElement
  {
private:
   //--- Расчёт с учётом последних изменений и изменение размеров таблицы
   void              RecalculateAndResizeTable(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Расчёт с учётом последних изменений и изменение размеров таблицы |
//+------------------------------------------------------------------+
void CCanvasTable::RecalculateAndResizeTable(const bool redraw=false)
  {
//--- Рассчитать размеры таблицы
   CalculateTableSize();
//--- Установить новый размер таблице
   ChangeTableSize();
//--- Обновить таблицу
   UpdateTable(redraw);
  }

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

class CCanvasTable : public CElement
  {
public:
   //--- Добавляет столбец в таблицу по указанному индексу
   void              AddColumn(const int column_index,const bool redraw=false);
   //--- Удаляет столбец в таблице по указанному индексу
   void              DeleteColumn(const int column_index,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Добавляет столбец в таблицу по указанному индексу                |
//+------------------------------------------------------------------+
void CCanvasTable::AddColumn(const int column_index,const bool redraw=false)
  {
//--- Увеличим размер массива на один элемент
   int array_size=(int)ColumnsTotal();
   m_columns_total=array_size+1;
   ::ArrayResize(m_columns,m_columns_total);
//--- Установить размер массивам рядов
   ::ArrayResize(m_columns[array_size].m_rows,m_rows_total);
//--- Корректировка индекса в случае выхода из диапазона
   int checked_column_index=(column_index>=(int)m_columns_total)? (int)m_columns_total-1 : column_index;
//--- Сместить другие столбцы (двигаемся от конца массива к индексу добавляемого столбца)
   for(int c=array_size; c>=checked_column_index; c--)
     {
      //--- Сместить признак отсортированного массива
      if(c==m_is_sorted_column_index && m_is_sorted_column_index!=WRONG_VALUE)
         m_is_sorted_column_index++;
      //--- Индекс предыдущего столбца
      int prev_c=c-1;
      //--- В новом столбце инициализация значениями по умолчанию
      if(c==checked_column_index)
         ColumnInitialize(c);
      //--- Перемещаем данные из предыдущего столбца в текущий
      else
         ColumnCopy(c,prev_c);
      //---
      for(uint r=0; r<m_rows_total; r++)
        {
         //--- Инициализация ячеек нового столбца значениями по умолчанию
         if(c==checked_column_index)
           {
            CellInitialize(c,r);
            continue;
           }
         //--- Перемещаем данные из ячейки предыдущего столбца в ячейку текущего
         CellCopy(c,r,prev_c,r);
        }
     }
//--- Рассчитать и установить новые размеры таблицы
   RecalculateAndResizeTable(redraw);
  }
//+------------------------------------------------------------------+
//| Удаляет столбец в таблице по указанному индексу                  |
//+------------------------------------------------------------------+
void CCanvasTable::DeleteColumn(const int column_index,const bool redraw=false)
  {
//--- Получим размер массива столбцов
   int array_size=(int)ColumnsTotal();
//--- Корректировка индекса в случае выхода из диапазона
   int checked_column_index=(column_index>=array_size)? array_size-1 : column_index;
//--- Сместить другие столбцы (двигаемся от указанного индекса до последнего столбца)
   for(int c=checked_column_index; c<array_size-1; c++)
     {
      //--- Сместить признак отсортированного массива
      if(c!=checked_column_index)
        {
         if(c==m_is_sorted_column_index && m_is_sorted_column_index!=WRONG_VALUE)
            m_is_sorted_column_index--;
        }
      //--- Обнулить, если удалён отсортированный столбец
      else
         m_is_sorted_column_index=WRONG_VALUE;
      //--- Индекс следующего столбца
      int next_c=c+1;
      //--- Перемещаем данные из следующего столбца в текущий
      ColumnCopy(c,next_c);
      //--- Перемещаем данные из ячеек следующего столбца в ячейки текущего
      for(uint r=0; r<m_rows_total; r++)
         CellCopy(c,r,next_c,r);
     }
//--- Уменьшим массив столбцов на один элемент
   m_columns_total=array_size-1;
   ::ArrayResize(m_columns,m_columns_total);
//--- Рассчитать и установить новые размеры таблицы
   RecalculateAndResizeTable(redraw);
  }

Методы для полной реконструкции и очистки таблицы тоже очень просты, в отличие от аналогичных методов в таблице типа CTable. Здесь достаточно установить новый размер вызовом метода CCanvasTable::TableSize(). При очистке таблицы устанавливается минимальный размер, когда остаётся только один столбец и одна строка. При очистке обнуляются и поля, в которых хранятся значения выделенной строки, направление сортировки и индекс отсортированного столбца. В конце обоих методов рассчитываются и устанавливаются новые размеры таблицы.‌

class CCanvasTable : public CElement
  {
public:
   //--- Реконструкция таблицы
   void              Rebuilding(const int columns_total,const int rows_total,const bool redraw=false);   
   //--- Очищает таблицу. Остаётся только один столбец и одна строка.
   void              Clear(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| Реконструкция таблицы                                            |
//+------------------------------------------------------------------+
void CCanvasTable::Rebuilding(const int columns_total,const int rows_total,const bool redraw=false)
  {
//--- Установить новый размер
   TableSize(columns_total,rows_total);
//--- Рассчитать и установить новые размеры таблицы
   RecalculateAndResizeTable(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает таблицу. Остаётся только один столбец и одна строка.     |
//+------------------------------------------------------------------+
void CCanvasTable::Clear(const bool redraw=false)
  {
//--- Установить минимальный размер 1x1
   TableSize(1,1);
//--- Установить значения по умолчанию
   m_selected_item_text     ="";
   m_selected_item          =WRONG_VALUE;
   m_last_sort_direction    =SORT_ASCEND;
   m_is_sorted_column_index =WRONG_VALUE;
//--- Рассчитать и установить новые размеры таблицы
   RecalculateAndResizeTable(redraw);
  }

Это работает так:

 Рис. 2. Демонстрация управления размерностью таблицы.

Рис. 2. Демонстрация управления размерностью таблицы.


В конце статьи можно загрузить к себе на компьютер тестовое приложение, изображённое на анимации выше.‌


Событие двойного нажатия левой кнопкой мыши

Иногда может понадобиться вызвать какое-либо действие через двойной клик мыши. Реализуем в нашей библиотеке генерацию такого события. Для этого в файле Defines.mqh добавим идентификатор ON_DOUBLE_CLICK для события двойного клика левой кнопкой мыши:

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_DOUBLE_CLICK             (34) // Двойной клик левой кнопки мыши
...

Событие с идентификатором ON_DOUBLE_CLICK будет генерироваться в классе CMouse. Обычно в ОС такое событие генерируется при действиях “нажатие-отжатие-нажатие” левой кнопкой мыши. Но тестирование в среде терминала показало, что не всегда получается одномоментно отследить событие нажатия левой кнопки мыши по приходу события CHARTEVENT_MOUSE_MOVE (параметр sparam). Поэтому решено реализовать генерацию события через действия “нажатие-отжатие-нажатие-отжатие”. Точно отследить эти действия можно по приходу события CHARTEVENT_CLICK

По умолчанию между двумя нажатиями должно пройти не менее 300 миллисекунд. Для отслеживания события CHARTEVENT_CLICK в обработчике событий мыши будет вызываться метод CMouse::CheckDoubleClick(). В начале этого метода объявлены две статические переменные, в которых каждый раз при вызове метода будут обновляться значения предыдущего и текущего тика (количество миллисекунд, прошедших с момента старта системы). Если между этими значениями окажется меньшее количество миллисекунд, чем указано в поле m_pause_between_clicks, то будет сгенерировано событие двойного нажатия левой кнопкой мыши. ‌

//+------------------------------------------------------------------+
//| Класс для получения параметров мыши                              |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- Пауза между кликами левой кнопкой мыши (для определения двойного нажатия)
   uint              m_pause_between_clicks;
   //---
private:
   //--- Проверка двойного нажатия левой кнопки мыши
   bool              CheckDoubleClick(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouse::CMouse(void) : m_pause_between_clicks(300)
  {
...
  }
//+------------------------------------------------------------------+
//| Обработка событий мыши                                           |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Обработка события нажатия на графике
   if(id==CHARTEVENT_CLICK)
     {
      //--- Проверим двойное нажатие левой кнопкой мыши
      CheckDoubleClick();
      return;
     }
  }
//+------------------------------------------------------------------+
//| Проверка двойного нажатия левой кнопки мыши                      |
//+------------------------------------------------------------------+
void CMouse::CheckDoubleClick(void)
  {
   static uint prev_depressed =0;
   static uint curr_depressed =::GetTickCount();
//--- Обновим значения
   prev_depressed =curr_depressed;
   curr_depressed =::GetTickCount();
//--- Определим время между нажатиями
   uint counter=curr_depressed-prev_depressed;
//--- Если между кликами прошло меньше времени, чем указано, отправим сообщение о двойном нажатии
   if(counter<m_pause_between_clicks)
      ::EventChartCustom(m_chart.ChartId(),ON_DOUBLE_CLICK,counter,0.0,"");
  }

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


Элементы управления в ячейках таблицы

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

В первую очередь, понадобится новое перечисление ENUM_TYPE_CELL, в котором будут определены типы ячеек:

//+------------------------------------------------------------------+
//| Перечисление типов ячейки таблицы                                |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2
  };

‌По умолчанию при инициализации ячейкам присваивается простой тип – CELL_SIMPLE, что означает “ячейка без элемента управления”. Чтобы установить или получить тип ячейки, используйте методы CCanvasTable::CellType(), код которых показан в листинге ниже. Установив для ячейки тип CELL_CHECKBOX (чекбокс), нужно также установить и картинки для состояний чекбокса. Минимальное количество картинок для этого типа ячейки – две. Можно установить более двух картинок, тогда будет возможность работать с многопараметрическими чекбоксами.

class CCanvasTable : public CElement
  {
   //--- Установка/получение типа ячейки
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
   ENUM_TYPE_CELL    CellType(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| Устанавливает тип ячейки                                         |
//+------------------------------------------------------------------+
void CCanvasTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- Установить тип ячейки
   m_columns[column_index].m_rows[row_index].m_type=type;
  }
//+------------------------------------------------------------------+
//| Получение типа ячейки                                            |
//+------------------------------------------------------------------+
ENUM_TYPE_CELL CCanvasTable::CellType(const uint column_index,const uint row_index)
  {
//--- Проверка на выход из диапазона
   if(!CheckOutOfRange(column_index,row_index))
      return(WRONG_VALUE);
//--- Вернуть тип данных для указанного столбца
   return(m_columns[column_index].m_rows[row_index].m_type);
  }

Серии картинок для чекбоксов и кнопок в каждом столбце могут быть разных размеров, поэтому нужна возможность установить X- и Y-отступы картинок для каждого столбца отдельно. Это можно сделать с помощью методов CCanvasTable::ImageXOffset() и CCanvasTable::ImageYOffset():‌

class CCanvasTable : public CElement
  {
public:
   //--- Смещение картинок по X- и Y-осям
   void              ImageXOffset(const int &array[]);
   void              ImageYOffset(const int &array[]);
  };

‌Ещё одним дополнением будет режим, при включении которого можно исключить отмену выделения строки при повторном нажатии. Этот режим работает, только если включен режим выделения строки.

class CCanvasTable : public CElement
  {
private:
   //--- Без отмены выделения строки при повторном нажатии
   bool              m_is_without_deselect;
   //---
public:
   //--- Режим «Без отмены выделения строки при повторном нажатии»
   void              IsWithoutDeselect(const bool flag)   { m_is_without_deselect=flag;      }
  };

Для определения индекса строки и столбца, на котором был клик, теперь используются отдельные методы CCanvasTable::PressedRowIndex() и CCanvasTable::PressedCellColumnIndex(). Они уже рассматривались ранее как блоки метода CCanvasTable::OnClickTable(). Полный их код с новыми дополнениями вы можете изучить самостоятельно в приложенных к статье файлах. Отмечу отдельно только, что в совокупности с помощью этих методов можно определить ячейку, на которой было нажатие левой кнопкой мыши. Далее рассмотрим, куда будут передаваться полученные индексы столбца и строки.‌

class CCanvasTable : public CElement
  {
private:
   //--- Возвращает индекс нажатой строки
   int               PressedRowIndex(void);
   //--- Возвращает индекс столбца нажатой ячейки
   int               PressedCellColumnIndex(void);
  };

При нажатии на ячейку таблицы нужно получить её тип, и если она содержит элемент управления, то нужно также определить, был ли он задействован. Для этого реализован ряд приватных методов, основной из которых — CCanvasTable::CheckCellElement(). Именно в него передаются полученные с помощью методов CCanvasTable::PressedRowIndex() и CCanvasTable::PressedCellColumnIndex() индексы столбца и строки. Метод двухрежимный. В зависимости от того, при обработке какого события он вызывается, используется третий параметр, которым можно указать, было ли это событие двойным кликом мыши.

Далее проверяется тип ячейки, и в зависимости от её типа вызывается соответствующий метод. Если в ячейке элемент управления “Кнопка”, то будет вызван метод CCanvasTable::CheckPressedButton(). Для ячеек-чекбоксов предназначен метод CCanvasTable::CheckPressedCheckBox().‌

class CCanvasTable : public CElement
  {
private:
   //--- Проверяет, был ли задействован элемент в ячейке при нажатии
   bool              CheckCellElement(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Проверяет, был ли при нажатии задействован элемент в ячейке      |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Выйти, если в ячейке нет элемента управления
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      //--- Если это ячейка-кнопка
      case CELL_BUTTON :
        {
         if(!CheckPressedButton(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- Если это ячейка-чекбокс
      case CELL_CHECKBOX :
        {
         if(!CheckPressedCheckBox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

Рассмотрим, как устроены методы CCanvasTable::CheckPressedButton() и CCanvasTable::CheckPressedCheckBox(). В самом начале обоих методов стоит проверка на количество картинок в ячейке. В ячейках-кнопках должна быть минимум одна картинка, а в ячейках-чекбоксах – минимум две. Затем нужно определить, было ли нажатие именно на картинке. В случае с чекбоксом реализуем два способа его переключения. Одиночное нажатие будет работать, только если нажать на картинку с изображением чекбокса. Двойной клик переключает чекбокс в любом месте ячейки. В обоих методах в случае исполнения всех условий генерируется событие с соответствующим элементу идентификатором. В качестве double-параметра отправляется индекс картинки, а в string-параметре формируется строка с индексами столбца и строки.‌

class CCanvasTable : public CElement
  {
private:
   //--- Проверяет, было ли нажатие на кнопке в ячейке
   bool              CheckPressedButton(const int column_index,const int row_index,const bool double_click=false);
   //--- Проверяет, было ли нажатие на чекбоксе в ячейке
   bool              CheckPressedCheckBox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| Проверяет, было ли нажатие на кнопке в ячейке                    |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckPressedButton(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Выйти, если нет картинок в ячейке
   if(ImagesTotal(column_index,row_index)<1)
     {
      ::Print(__FUNCTION__," > Установите минимум одну картинку для ячейки-кнопки!");
      return(false);
     }
//--- Получим относительные координаты под курсором мыши
   int x=m_mouse.RelativeX(m_table);
//--- Получим правую границу картинки
   int image_x  =int(m_columns[column_index].m_x+m_columns[column_index].m_image_x_offset);
   int image_x2 =int(image_x+m_columns[column_index].m_rows[row_index].m_images[0].m_image_width);
//--- Выйти, если нажали не на картинке
   if(x>image_x2)
      return(false);
   else
     {
      //--- Если это не двойной клик, отправим сообщение
      if(!double_click)
        {
         int image_index=m_columns[column_index].m_rows[row_index].m_selected_image;
         ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElementBase::Id(),image_index,string(column_index)+"_"+string(row_index));
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Проверяет, было ли нажатие на чекбоксе в ячейке                  |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckPressedCheckBox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- Выйти, если нет картинок в ячейке
   if(ImagesTotal(column_index,row_index)<2)
     {
      ::Print(__FUNCTION__," > Установите минимум две картинки для ячейки-чекбокса!");
      return(false);
     }
//--- Получим относительные координаты под курсором мыши
   int x=m_mouse.RelativeX(m_table);
//--- Получим правую границу картинки
   int image_x  =int(m_columns[column_index].m_x+m_image_x_offset);
   int image_x2 =int(image_x+m_columns[column_index].m_rows[row_index].m_images[0].m_image_width);
//--- Выйти, если (1) нажали не на картинке и (2) это не двойное клик
   if(x>image_x2 && !double_click)
      return(false);
   else
     {
      //--- Текущий индекс выбранной картинки
      int image_i=m_columns[column_index].m_rows[row_index].m_selected_image;
      //--- Определим следующий индекс для картинки
      int next_i=(image_i<ImagesTotal(column_index,row_index)-1)? ++image_i : 0;
      //--- Выбрать следующую картинку и обновить таблицу
      ChangeImage(column_index,row_index,next_i,true);
      m_table.Update(false);
      //--- Отправим сообщение об этом
      ::EventChartCustom(m_chart_id,ON_CLICK_CHECKBOX,CElementBase::Id(),next_i,string(column_index)+"_"+string(row_index));
     }
//---
   return(true);
  }

В итоге код методов CCanvasTable::OnClickTable() и CCanvasTable::OnDoubleClickTable(), обрабатывающих нажатия на таблице, стал намного более понятным и читаемым (см. листинг ниже). В зависимости от того, какие режимы включены и на ячейке какого типа было нажатие, генерируется соответствующее событие.‌

class CCanvasTable : public CElement
  {
private:
   //--- Обработка нажатия на таблице
   bool              OnClickTable(const string clicked_object);
   //--- Обработка двойного нажатия на таблице
   bool              OnDoubleClickTable(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| Обработка нажатия на таблице                                     |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickTable(const string clicked_object)
  {
//--- Выйти, если (1) отключен режим выделения ряда или (2) в процессе изменения ширины столбца
   if(m_column_resize_control!=WRONG_VALUE)
      return(false);
//--- Выйдем, если полоса прокрутки в активном режиме
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- Выйдем, если чужое имя объекта
   if(m_table.Name()!=clicked_object)
      return(false);
//--- Определим ряд, на котором нажали
   int r=PressedRowIndex();
//--- Определим ячейку, на которой нажали
   int c=PressedCellColumnIndex();
//--- Проверим был ли задействован элемент в ячейке
   bool is_cell_element=CheckCellElement(c,r);
//--- Если (1) включен режим выделения строки и (2) не задействован элемент в ячейке
   if(m_selectable_row && !is_cell_element)
     {
      //--- Изменить цвет
      RedrawRow(true);
      m_table.Update();
      //--- Отправим сообщение об этом
      ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,string(c)+"_"+string(r));
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Обработка двойного нажатия на таблице                            |
//+------------------------------------------------------------------+
bool CCanvasTable::OnDoubleClickTable(const string clicked_object)
  {
   if(!m_table.MouseFocus())
      return(false);
//--- Определим ряд, на котором нажали
   int r=PressedRowIndex();
//--- Определим ячейку, на которой нажали
   int c=PressedCellColumnIndex();
//--- Проверим был ли задействован элемент в ячейке и вернём результат
   return(CheckCellElement(c,r,true));
  }

Обработка двойного нажатия левой кнопкой мыши на ячейке осуществляется в обработчике элемента по событию ON_DOUBLE_CLICK

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Обработка двойного нажатия левой кнопки мыши
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- Нажатие на таблице
      if(OnDoubleClickTable(sparam))
         return;
      //---
      return;
     }
  }

‌Работает всё в конечном итоге так:

 Рис. 3. Демонстрация взаимодействия с элементами управления в ячейках таблицы.

Рис. 3. Демонстрация взаимодействия с элементами управления в ячейках таблицы.


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


Заключение

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

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

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


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

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (94)
Konstantin
Konstantin | 21 апр. 2017 в 09:31

вопрос снят, сам ступил в индексации:

      if(a_table.RowsTotal() < _count) {
         a_table.AddRow(_count - 1, true);
         
         //--- делать дополнительно выравнивание не нужно, все сохраняется от первой строки

         a_table.CellType(1, _count - 1, CELL_BUTTON);

         //--- массив картинок
         string _image[3]= { "::Images\\EasyAndFastGUI\\Icons\\bmp16\\circle_gray.bmp",
                       "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_up.bmp",
                       "::Images\\EasyAndFastGUI\\Icons\\bmp16\\arrow_down.bmp" };
         a_table.SetImages(1, _count - 1, _image);
      }
Artyom Trishkin
Artyom Trishkin | 5 мая 2017 в 20:28

Толь, заметил странности при изменении размеров колонок - неверно обрезается текст. И ещё случайно обнаружил постоянное изменение размера колонки при удержании клавиши Ctrl.


Anatoli Kazharski
Anatoli Kazharski | 7 мая 2017 в 15:14
Artyom Trishkin:

1. Толь, заметил странности при изменении размеров колонок - неверно обрезается текст.

2. И ещё случайно обнаружил постоянное изменение размера колонки при удержании клавиши Ctrl.

1. Попробовал потестировать с различными режимами выравнивания текста в столбцах, но не удалось воспроизвести. 

2. С клавишей Ctrl воспроизводится, но непонятно, почему такое поведение. Эта клавиша не прописана в коде таблицы.

Juer
Juer | 18 мая 2018 в 19:12
Здравствуйте, подскажите, как обновлять таблицу, если в ней динамические данные https://www.mql5.com/ru/forum/165152/page10#comment_7488387
Anatoli Kazharski
Anatoli Kazharski | 19 мая 2018 в 09:03
Juer:
Здравствуйте, подскажите, как обновлять таблицу, если в ней динамические данные https://www.mql5.com/ru/forum/165152/page10#comment_7488387

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


Сравнительный анализ 10 трендовых стратегий Сравнительный анализ 10 трендовых стратегий
В статье сделан краткий обзор 10 трендовых стратегий, проведено их тестирование, сравнительный анализ. На основе полученных результатов сделан общий вывод о целесообразности, достоинствах и недостатках торговли по тренду.
Готовые советники из Мастера MQL5 работают в MetaTrader 4 Готовые советники из Мастера MQL5 работают в MetaTrader 4
В статье предлагается простой эмулятор торгового окружения MetaTrader 5 для MetaTrader 4. С его помощью выполняются перенос и адаптация торговых классов стандартной библиотеки. В результате советники, генерируемые в Мастере MetaTrader 5, могут компилироваться и запускаться без изменений в MetaTrader 4.
Секвента ДеМарка (TD SEQUENTIAL) с использованием искусственного интеллекта Секвента ДеМарка (TD SEQUENTIAL) с использованием искусственного интеллекта
В этой статье я расскажу, как с помощью "скрещивания" одной очень известной стратегии и нейронной сети можно успешно заниматься трейдингом. Речь пойдет о стратегии Томаса Демарка "Секвента" с применением системы искусственного интеллекта. Работать будем ТОЛЬКО по первой части стратегии, используя сигналы "Установка" и "Пересечение".
Рецепты MQL5 - Торговые сигналы пивотов Рецепты MQL5 - Торговые сигналы пивотов
В статье представлен процесс разработки и реализации класса-сигнальщика на основе пивотов — разворотных уровней. На базе этого класса строится стратегия с использованием Стандартной библиотеки. Рассматриваются возможности развития стратегии пивотов посредством добавления фильтров.