Графические интерфейсы X: Расширенное управление списками и таблицами. Оптимизация кода (build 7)
Содержание
- Введение
- Изменения в схеме библиотеки и оптимизация кода
- Программное управление полосой прокрутки
- Программное управление списками
- Оптимизация кода таблицы типа CTable
- Программное управление таблицей типа CTable
- Приложение для теста элемента
- Заключение
Введение
О том, для чего предназначена эта библиотека, более подробно можно прочитать в первой статье серии: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками, там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.
Коду нашей библиотеки не помешает оптимизация, которая сделает его более упорядоченным и, соответственно, более читаемым и понятным для изучения. Кроме этого, продолжим развивать уже созданные в предыдущих статьях элементы управления: списки, полосы прокрутки и таблицы. Добавим методы, которые позволят программно управлять свойствами этих элементов уже в процессе выполнения MQL-приложения.
Изменения в схеме библиотеки и оптимизация кода
Частично оптимизирован код во всех файлах библиотеки, относящихся к элементам управления. Случаи с многократно повторяющимся кодом упакованы в отдельные методы, а сами методы перенесены в отдельный класс.
Поясним, как это было сделано. Класс CElement переименован в CElementBase. Это базовый класс всех элементов управления библиотеки. Теперь следующим после него наследуемым классом идёт новый класс CElement, в котором расположились многократно повторяющиеся методы во всех элементах. К ним относятся:
- Метод для сохранения указателя формы, к которой прикрепляется элемент
- Проверка наличия указателя на форму
- Проверка идентификатора активированного элемента
- Расчёт абсолютных координат
- Расчёт относительных координат от крайней точки формы
Классы CElementBase и CElement находятся в разных файлах, ElementBase.mqh и Element.mqh соответственно. Поэтому файл ElementBase.mqh с базовым классом подключаем к файлу Element.mqh. Так как здесь нужно определение типа CWindows, то подключаем также и файл Window.mqh. В листинге кода ниже показано, как это выглядит:
//| Element.mqh |
//| Copyright 2016, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
#include "ElementBase.mqh"
#include "Controls\Window.mqh"
//+------------------------------------------------------------------+
//| Класс для получения параметров мыши |
//+------------------------------------------------------------------+
class CElement : public CElementBase
{
protected:
//--- Указатель на форму, к которой элемент присоединён
CWindow *m_wnd;
//---
public:
CElement(void);
~CElement(void);
//--- Сохраняет указатель формы
void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); }
//---
protected:
//--- Проверка наличия указателя на форму
bool CheckWindowPointer(void);
//--- Проверка идентификатора активированного элемента
bool CheckIdActivatedElement(void);
//--- Расчёт абсолютных координат
int CalculateX(const int x_gap);
int CalculateY(const int y_gap);
//--- Расчёт относительных координат от крайней точки формы
int CalculateXGap(const int x);
int CalculateYGap(const int y);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CElement::CElement(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CElement::~CElement(void)
{
}
Все эти методы и их код ранее многократно повторялись во всех классах элементов. Вынесение их в отдельный класс сделало код классов элементов существенно более понятным и читаемым. Код всех этих методов прост и умещается буквально в одну строчку (см. листинг ниже). При расчёте координат учитывается позиционирование элемента относительно одной из сторон формы.
//| Проверка идентификатора активированного элемента |
//+------------------------------------------------------------------+
bool CElement::CheckIdActivatedElement(void)
{
return(m_wnd.IdActivatedElement()==CElementBase::Id());
}
//+------------------------------------------------------------------+
//| Расчёт абсолютной X-координаты |
//+------------------------------------------------------------------+
int CElement::CalculateX(const int x_gap)
{
return((CElementBase::AnchorRightWindowSide())? m_wnd.X2()-x_gap : m_wnd.X()+x_gap);
}
//+------------------------------------------------------------------+
//| Расчёт абсолютной Y-координаты |
//+------------------------------------------------------------------+
int CElement::CalculateY(const int y_gap)
{
return((CElementBase::AnchorBottomWindowSide())? m_wnd.Y2()-y_gap : m_wnd.Y()+y_gap);
}
//+------------------------------------------------------------------+
//| Расчёт относительной X-координаты от крайней точки формы |
//+------------------------------------------------------------------+
int CElement::CalculateXGap(const int x)
{
return((CElementBase::AnchorRightWindowSide())? m_wnd.X2()-x : x-m_wnd.X());
}
//+------------------------------------------------------------------+
//| Расчёт относительной Y-координаты от крайней точки формы |
//+------------------------------------------------------------------+
int CElement::CalculateYGap(const int y)
{
return((CElementBase::AnchorBottomWindowSide())? m_wnd.Y2()-y : y-m_wnd.Y());
}
Может возникнуть вопрос: “Почему эти методы не были размещены в старой версии класса CElement?”. Сделать это было невозможно: при подключении файла Window.mqh и компиляции возникала ошибка отсутствия типа, и как следствие — множество других сопутствующих ошибок:
Рис. 1. Сообщение при компиляции об отсутствии типа CElement
Если же попытаться обойти эту сложность и подключить файл Window.mqh после тела класса CElement, когда в теле этого класса уже объявлен объект типа CWindow, то при компиляции мы увидим уже знакомую ошибку отсутствия указываемого типа:
Рис. 2. Сообщение при компиляции об отсутствии типа CWindow
Поэтому было решено сделать дополнительный наследуемый промежуточный класс, в котором можно размещать многократно повторяющийся код и методы для работы с указателем на форму, к которой прикрепляются элементы управления. Часть схемы библиотеки относительно взаимосвязей между формой и элементами выглядит так:
Рис. 3. Часть схемы библиотеки относительно взаимосвязей между формой и элементами
Как видно из схемы выше, класс CWindow сразу наследуется от класса CElementBase, так как для формы промежуточный класс CElement уже избыточен и неуместен. Все же остальные классы элементов наследуются от промежуточного класса CElement.
Программное управление полосой прокрутки
В процессе использования библиотеки назрела необходимость программно управлять полосами прокрутки. Для этого в классах CScrollV и CScrollH имплементирован метод MovingThumb(), с помощью которого можно переместить ползунок полосы прокрутки на указанную позицию.
В листинге ниже представлен код только для вертикальной полосы прокрутки, так как они с горизонтальной практически идентичны. У метода есть один аргумент, значение которого по умолчанию равно WRONG_VALUE. Если вызвать метод без указания позиции (со значением по умолчанию), то ползунок будет смещён на последнюю позицию списка. Это удобно, когда в список добавляются пункты уже в момент исполнения программы, и позволяет реализовать автоматическую прокрутку списка.
//| Класс для управления вертикальной полосой прокрутки |
//+------------------------------------------------------------------+
class CScrollV : public CScroll
{
public:
//--- Перемещает ползунок на указанную позицию
void MovingThumb(const int pos=WRONG_VALUE);
};
//+------------------------------------------------------------------+
//| Перемещает ползунок на указанную позицию |
//+------------------------------------------------------------------+
void CScrollV::MovingThumb(const int pos=WRONG_VALUE)
{
//--- Выйти, если полоса прокрутки не нужна
if(m_items_total<=m_visible_items_total)
return;
//--- Для проверки позиции ползунка
uint check_pos=0;
//--- Скорректируем позицию в случае выхода из диапазона
if(pos<0 || pos>m_items_total-m_visible_items_total)
check_pos=m_items_total-m_visible_items_total;
else
check_pos=pos;
//--- Запомним позицию ползунка
CScroll::CurrentPos(check_pos);
//--- Расчёт и установка координаты Y ползунка полосы прокрутки
CalculateThumbY();
}
Программное управление списками
Для управления списками были реализованы публичные методы, отвечающие за выполнение следующих действий:
- Реконструкция списка
- Добавление пункта в конец списка
- Очищение списка (удаление всех пунктов)
- Прокрутка списка
Кроме этого, в рамках оптимизации кода библиотеки в классы списков добавлены приватные методы для повторяющегося кода:
- Расчёт Y-координаты пункта
- Расчёт ширины пунктов
- Расчёт размера списка по оси Y
Давайте подробнее рассмотрим, как устроены эти методы в классе CListView. Приватные методы — просто вспомогательные методы кода, повторяющегося более одного раза в разных местах класса. Они занимают всего лишь одну строчку в каждом методе:
//| Класс для создания списка |
//+------------------------------------------------------------------+
class CListView : public CElement
{
private:
//--- Расчёт Y-координаты пункта
int CalculationItemY(const int item_index=0);
//--- Расчёт ширины пунктов
int CalculationItemsWidth(void);
//--- Расчёт размера списка по оси Y
int CalculationYSize(void);
//+------------------------------------------------------------------+
//| Расчёт Y-координаты пункта |
//+------------------------------------------------------------------+
int CListView::CalculationItemY(const int item_index=0)
{
return((item_index>0)? m_items[item_index-1].Y2()-1 : CElementBase::Y()+1);
}
//+------------------------------------------------------------------+
//| Расчёт ширины пунктов |
//+------------------------------------------------------------------+
int CListView::CalculationItemsWidth(void)
{
return((m_items_total>m_visible_items_total) ? CElementBase::XSize()-m_scrollv.ScrollWidth()-1 : CElementBase::XSize()-2);
}
//+------------------------------------------------------------------+
//| Расчёт размера списка по оси Y |
//+------------------------------------------------------------------+
int CListView::CalculationYSize(void)
{
return(m_item_y_size*m_visible_items_total-(m_visible_items_total-1)+2);
}
Очищение списка говорит само за себя: все пункты из списка удаляются. Для этого нужно использовать метод CListView::Clear(). Здесь сначала удаляются графические объекты-примитивы, освобождается массив указателей на эти объекты и устанавливаются значения по умолчанию для некоторых полей класса. После этого устанавливается нулевой размер списка и сбрасываются параметры полосы прокрутки. В самом конце метода нужно снова добавить в массив указателей элемента указатель на фон списка, так как до этого он был удалён методом CElementBase::FreeObjectsArray().
//| Класс для создания списка |
//+------------------------------------------------------------------+
class CListView : public CElement
{
public:
//--- Очищает список (удаление всех пунктов)
void Clear(void);
};
//+------------------------------------------------------------------+
//| Очищает список (удаление всех пунктов) |
//+------------------------------------------------------------------+
void CListView::Clear(void)
{
//--- Удалить объекты-пункты
for(int r=0; r<m_visible_items_total; r++)
m_items[r].Delete();
//--- Очистить массив указателей на объекты
CElementBase::FreeObjectsArray();
//--- Установить значения по умолчанию
m_selected_item_text ="";
m_selected_item_index =0;
//--- Установить нулевой размер списка
ListSize(0);
//--- Сбросить значения скролла
m_scrollv.Hide();
m_scrollv.MovingThumb(0);
m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
//--- Добавить фон списка в массив указателей на объекты элемента
CElementBase::AddToArray(m_area);
}
Для реконструкции списка нужно использовать метод CListView::Rebuilding(). Под реконструкцией подразумевается ситуация, когда нужно полностью заново сформировать список. С помощью этого метода можно изменить общее количество пунктов и количество видимых пунктов. То есть, размер списка тоже изменится, если указать отличающееся от исходного значения количество видимых пунктов.
В самом начале метода CListView::Rebuilding() список очищается. Затем на основе значений переданных аргументов устанавливаются новые размеры и корректируется высота списка, если количество видимых пунктов изменилось. Далее корректируются размеры объектов полосы прокрутки. После этого создаётся список, и если общее количество пунктов превышает заданное видимое их количество, то отображается полоса прокрутки.
//| Класс для создания списка |
//+------------------------------------------------------------------+
class CListView : public CElement
{
public:
//--- Реконструкция списка
void Rebuilding(const int items_total,const int visible_items_total);
};
//+------------------------------------------------------------------+
//| Реконструкция списка |
//+------------------------------------------------------------------+
void CListView::Rebuilding(const int items_total,const int visible_items_total)
{
//--- Очистка списка
Clear();
//--- Установим размер списка и его видимой части
ListSize(items_total);
VisibleListSize(visible_items_total);
//--- Скорректировать размеры списка
int y_size=CalculationYSize();
if(y_size!=CElementBase::YSize())
{
m_area.YSize(y_size);
m_area.Y_Size(y_size);
CElementBase::YSize(y_size);
}
//--- Скорректировать размеры полосы прокрутки
m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
m_scrollv.ChangeYSize(y_size);
//--- Создать список
CreateList();
//--- Отобразить полосу прокрутки, если нужно
if(m_items_total>m_visible_items_total)
{
if(CElementBase::IsVisible())
m_scrollv.Show();
}
}
Для создания одного пункта теперь реализован отдельный метод CListView::CreateItem(), так как при добавлении пункта в список в процессе выполнения программы его код будет использоваться в методе CListView::AddItem(), а не только при создании всего списка в цикле в методе CListView::CreateList().
В методе CListView::AddItem() принимается только один аргумент – отображаемый текст пункта. По умолчанию это пустая строка. Текст можно добавить и после создания с помощью метода CListView::SetItemValue(). В самом начале метода CListView::AddItem() массив пунктов увеличивается на один элемент. Затем, в случае, если общее количество пунктов на текущий момент не больше видимого количества пунктов, то это означает, что нужно создать графический объект. Если же мы уже вышли за пределы видимого количества, то нужно показать полосу прокрутки и скорректировать размер её ползунка, а также скорректировать ширину пунктов.
//| Класс для создания списка |
//+------------------------------------------------------------------+
class CListView : public CElement
{
public:
//--- Добавляет пункт в список
void AddItem(const string value="");
};
//+------------------------------------------------------------------+
//| Добавляет пункт в список |
//+------------------------------------------------------------------+
void CListView::AddItem(const string value="")
{
//--- Увеличим размер массива на один элемент
int array_size=ItemsTotal();
m_items_total=array_size+1;
::ArrayResize(m_item_value,m_items_total);
m_item_value[array_size]=value;
//--- Если общее кол-во пунктов больше видимого
if(m_items_total>m_visible_items_total)
{
//--- Скорректировать размер ползунка и показать полосу прокрутки
m_scrollv.ChangeThumbSize(m_items_total,m_visible_items_total);
if(CElementBase::IsVisible())
m_scrollv.Show();
//--- Выйти, если массив меньше одного элемента
if(m_visible_items_total<1)
return;
//--- Расчёт ширины пунктов списка
int width=CElementBase::XSize()-m_scrollv.ScrollWidth()-1;
if(width==m_items[0].XSize())
return;
//--- Установить новый размер пунктам списка
for(int i=0; i<m_items_total && i<m_visible_items_total; i++)
{
m_items[i].XSize(width);
m_items[i].X_Size(width);
}
//---
return;
}
//--- Расчёт координат
int x=CElementBase::X()+1;
int y=CalculationItemY(array_size);
//--- Расчёт ширины пунктов списка
int width=CalculationItemsWidth();
//--- Создание объекта
CreateItem(array_size,x,y,width);
//--- Подсветка выделенного пункта
HighlightSelectedItem();
//--- Сохраним текст выделенного пункта
if(array_size==1)
m_selected_item_text=m_item_value[0];
}
Для программной прокрутки списка предназначен метод CListView::Scrolling(). В качестве единственного аргумента принимается номер позиции в списке. По умолчанию установлено значение WRONG_VALUE, что означает смещение списка на последнюю позицию.
//| Класс для создания списка |
//+------------------------------------------------------------------+
class CListView : public CElement
{
public:
//--- Прокрутка списка
void Scrolling(const int pos=WRONG_VALUE);
};
//+------------------------------------------------------------------+
//| Прокрутка списка |
//+------------------------------------------------------------------+
void CListView::Scrolling(const int pos=WRONG_VALUE)
{
//--- Выйти, если полоса прокрутки не нужна
if(m_items_total<=m_visible_items_total)
return;
//--- Для определения позиции ползунка
int index=0;
//--- Индекс последней позиции
int last_pos_index=m_items_total-m_visible_items_total;
//--- Корректировка в случае выхода из диапазона
if(pos<0)
index=last_pos_index;
else
index=(pos>last_pos_index)? last_pos_index : pos;
//--- Сдвигаем ползунок полосы прокрутки
m_scrollv.MovingThumb(index);
//--- Сдвигаем список
UpdateList(index);
}
Аналогичные методы реализованы также для списка типа CCheckBoxList.
Оптимизация кода таблицы типа CTable
Код класса CTable тоже был оптимизирован и стал более компактным и читаемым благодаря добавлению ряда приватных методов, в которые упакован многократно повторяющийся код. К таким методам относятся:
- Изменение размера массивов ряда
- Инициализация ячеек значениями по умолчанию
- Расчёт размера таблицы по оси X
- Расчёт размера таблицы по оси Y
- Расчёт X-координаты ячейки
- Расчёт Y-координаты ячейки
- Расчёт ширины столбца
- Изменение ширины столбцов
- Изменение размера таблицы по оси Y
//| Класс для создания таблицы из полей ввода |
//+------------------------------------------------------------------+
class CTable : public CElement
{
private:
//--- Изменение размера массивов ряда
void RowResize(const uint column_index,const uint new_size);
//--- Инициализация ячеек значениями по умолчанию
void CellInitialize(const uint column_index,const int row_index=WRONG_VALUE);
//--- Расчёт размера таблицы по оси X
int CalculationXSize(void);
//--- Расчёт размера таблицы по оси Y
int CalculationYSize(void);
//--- Расчёт X-координаты ячейки
int CalculationCellX(const int column_index=0);
//--- Расчёт Y-координаты ячейки
int CalculationCellY(const int row_index=0);
//--- Расчёт ширины столбца
int CalculationColumnWidth(const bool is_last_column=false);
//--- Изменение ширины столбцов
void ColumnsXResize(void);
//--- Изменение размера таблицы по оси Y
void YResize(void);
};
Метод CTable::CalculationColumnWidth() предназначен для расчёта ширины столбцов таблицы и принимает только один аргумент, значение которого равно false. Со значением по умолчанию рассчитывается общая ширина для столбцов. Если передать значение true, то будет рассчитана ширина для последнего столбца. В этом случае используется рекурсивный вызов метода. Разделение на расчёт общей ширины и ширины последнего столбца необходимо, так как при общем расчёте правая граница последнего столбца может не сойтись с правой границей таблицы.
//| Расчёт ширины столбца |
//+------------------------------------------------------------------+
int CTable::CalculationColumnWidth(const bool is_last_column=false)
{
int width=0;
//--- Проверка на наличие вертикальной полосы прокрутки
bool is_scrollv=m_rows_total>m_visible_rows_total;
//---
if(!is_last_column)
{
if(m_visible_columns_total==1)
width=(is_scrollv)? m_x_size-m_scrollv.ScrollWidth() : width=m_x_size-2;
else
{
if(is_scrollv)
width=(m_x_size-m_scrollv.ScrollWidth())/int(m_visible_columns_total);
else
width=m_x_size/(int)m_visible_columns_total+1;
}
}
else
{
width=CalculationColumnWidth();
int last_column=(int)m_visible_columns_total-1;
int w=m_x_size-(width*last_column-last_column);
width=(is_scrollv) ? w-m_scrollv.ScrollWidth()-1 : w-2;
}
//---
return(width);
}
Когда таблица создаётся, или когда ширина таблицы изменяется, то осуществляется вызов метода CTable::ColumnsXResize(). Здесь для расчёта ширины столбцов вызывается метод CTable::CalculationColumnWidth() рассмотренный выше. В самом конце метода, если таблица отсортирована, то нужно скорректировать положение стрелки-признака отсортированной таблицы.
//| Изменение ширины столбцов |
//+------------------------------------------------------------------+
void CTable::ColumnsXResize(void)
{
//--- Расчёт ширины столбцов
int width=CalculationColumnWidth();
//--- Столбцы
for(uint c=0; c<m_columns_total && c<m_visible_columns_total; c++)
{
//--- Расчёт координаты X
int x=CalculationCellX(c);
//--- Корректировка ширины последнего столбца
if(c+1>=m_visible_columns_total)
width=CalculationColumnWidth(true);
//--- Ряды
for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
{
//--- Координаты
m_columns[c].m_rows[r].X(x);
m_columns[c].m_rows[r].X_Distance(x);
//--- Ширина
m_columns[c].m_rows[r].XSize(width);
m_columns[c].m_rows[r].X_Size(width);
//--- Отступы от крайней точки панели
m_columns[c].m_rows[r].XGap(CalculateXGap(x));
}
}
//--- Выйти, если таблица не отсортирована
if(m_is_sorted_column_index==WRONG_VALUE)
return;
//--- Смещение на один индекс, если включен режим закреплённых заголовков
int l=(m_fix_first_column) ? 1 : 0;
//--- Получим текущие позиции ползунков горизонтальной и вертикальной полос прокрутки
int h=m_scrollh.CurrentPos()+l;
//--- Если не выходим из диапазона
if(m_is_sorted_column_index>=h && m_is_sorted_column_index<(int)m_visible_columns_total)
{
//--- Смещение стрелки на отсортированный столбец таблицы
ShiftSortArrow(m_is_sorted_column_index);
}
}
С кодом остальных приватных методов, представленных в списке в начале этого раздела, предлагаю ознакомиться самостоятельно, так как в них нет ничего такого сложного, что могло бы вызвать вопросы.
Кроме методов, описанных выше, в рамках оптимизации был реализован отдельный приватный метод CTable::CreateCell() для создания ячейки таблицы. Ещё одно удобное дополнение для таблицы типа CTable в этом обновлении — автоформатирование в стиле «Зебра». Ранее, если пользователю библиотеки нужно было сделать таблицу полосатой для лучшего восприятия массива данных, он должен был воспользоваться методом CTable::CellColor(). То есть, нужно было для всех ячеек таблицы назначить свой цвет. Это не очень удобно и отнимает время. Теперь, чтобы сделать таблицу полосатой, нужно просто перед созданием элемента вызвать метод CTable::IsZebraFormatRows(), передав в качестве единственного аргумента второй цвет. В качестве первого цвета используется значение, которое задаётся методом CTable::CellColor() для всех ячеек таблицы (по умолчанию — белый).
//| Класс для создания таблицы из полей ввода |
//+------------------------------------------------------------------+
class CTable : public CElement
{
private:
//--- Режим полосатой расцветки таблицы типа "Зебра"
color m_is_zebra_format_rows;
//---
public:
//--- Режим формат строк в стиле "Зебра"
void IsZebraFormatRows(const color clr) { m_is_zebra_format_rows=clr; }
};
Если второй цвет для форматирования в стиле «Зебра» задан, то везде, где это необходимо, вызывается приватный метод CTable::ZebraFormatRows().
//| Класс для создания таблицы из полей ввода |
//+------------------------------------------------------------------+
class CTable : public CElement
{
private:
//--- Форматирует таблицу в стиле "Зебра"
void ZebraFormatRows(void);
};
//+------------------------------------------------------------------+
//| Форматирует таблицу в стиле "Зебра" |
//+------------------------------------------------------------------+
void CTable::ZebraFormatRows(void)
{
//--- Выйти, если режим отключен
if(m_is_zebra_format_rows==clrNONE)
return;
//--- Цвет по умолчанию
color clr=m_cell_color;
//---
for(uint c=0; c<m_columns_total; c++)
{
for(uint r=0; r<m_rows_total; r++)
{
if(m_fix_first_row)
{
if(r==0)
continue;
//---
clr=(r%2==0)? m_is_zebra_format_rows : m_cell_color;
}
else
clr=(r%2==0)? m_cell_color : m_is_zebra_format_rows;
//--- Установить цвет фона ячейки в общий массив
m_vcolumns[c].m_cell_color[r]=clr;
}
}
}
Программное управление таблицей типа CTable
В этом обновлении библиотеки программное управление получает пока только таблица типа CTable. Имплементировано несколько публичных методов для осуществления следующих действий:
- Реконструкция таблицы
- Добавление столбца
- Добавление ряда
- Очищение таблицы (удаление всех столбцов и рядов)
- Горизонтальная и вертикальная прокрутка таблицы
//| Класс для создания таблицы из полей ввода |
//+------------------------------------------------------------------+
class CTable : public CElement
{
public:
//--- Реконструкция таблицы
void Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total);
//--- Добавляет столбец в таблицу
void AddColumn(void);
//--- Добавляет ряд в таблицу
void AddRow(void);
//--- Очищает таблицу (удаление всех стобцов и рядов)
void Clear(void);
//--- Прокрутка таблицы: (1) вертикальная и (2) горизонтальная
void VerticalScrolling(const int pos=WRONG_VALUE);
void HorizontalScrolling(const int pos=WRONG_VALUE);
};
Метод CTable::Clear() для очищения таблицы не будем здесь рассматривать: он практически такой же, как и у списков, которые мы рассматривали в предыдущих разделах статьи.
Для реконструкции таблицы нужно вызвать метод CTable::Rebuilding(), в который в качестве аргументов нужно передать общее количество столбцов и рядов, а также их видимое количество. Здесь в начале метода таблица полностью очищается, то есть, из нее удаляются все столбцы и ряды. Затем массивам устанавливаются новые размеры по значениям переданных аргументов. В зависимости от того, каково сейчас общее количество рядов и столбцов по отношению к их видимому количеству, устанавливаются размеры для полос прокрутки. После всех расчётов создаются ячейки таблицы, а затем, при необходимости, делаются видимыми полосы прокрутки.
//| Реконструкция таблицы |
//+------------------------------------------------------------------+
void CTable::Rebuilding(const int columns_total,const int visible_columns_total,const int rows_total,const int visible_rows_total)
{
//--- Очистка таблицы
Clear();
//--- Установим размер таблицы и её видимой части
TableSize(columns_total,rows_total);
VisibleTableSize(visible_columns_total,visible_rows_total);
//--- Скорректировать размеры полос прокрутки
m_scrollv.ChangeThumbSize(rows_total,visible_rows_total);
m_scrollh.ChangeThumbSize(columns_total,visible_columns_total);
//--- Проверка на наличие вертикальной полосы прокрутки
bool is_scrollv=m_rows_total>m_visible_rows_total;
//--- Проверка на наличие горизонтальной полосы прокрутки
bool is_scrollh=m_columns_total>m_visible_columns_total;
//--- Рассчитаем размер таблицы по оси Y
int y_size=CalculationYSize();
//--- Установим новый размер для вертикальной полосы прокрутки
m_scrollv.ChangeYSize(y_size);
//--- Установим новый размер таблице
m_y_size=(is_scrollh)? y_size+m_scrollh.ScrollWidth()-1 : y_size;
m_area.YSize(m_y_size);
m_area.Y_Size(m_y_size);
//--- Скорректируем положение горизонтальной полосы прокрутки по оси Y
m_scrollh.YDistance(CElementBase::Y2()-m_scrollh.ScrollWidth());
//--- Если нужна гориз. полоса прокрутки
if(is_scrollh)
{
//--- Установим размер относительно наличия вертикальной полосы прокрутки
if(!is_scrollv)
m_scrollh.ChangeXSize(m_x_size);
else
{
//--- Рассчитать и изменить ширину горизонтальной полосы прокрутки
int x_size=m_area.XSize()-m_scrollh.ScrollWidth()+1;
m_scrollh.ChangeXSize(x_size);
}
}
//--- Создать ячейки таблицы
CreateCells();
//--- Отобразить полосу прокрутки, если нужно
if(rows_total>visible_rows_total)
{
if(CElementBase::IsVisible())
m_scrollv.Show();
}
if(columns_total>visible_columns_total)
{
if(CElementBase::IsVisible())
m_scrollh.Show();
}
}
Методы для добавления столбца CTable::AddColumn() и ряда CTable::AddRow() очень похожи по своему алгоритму, поэтому рассмотрим здесь только один из них.
В начале метода CTable::AddColumn() устанавливается размер массиву столбцов и рядов в этом столбце. Затем с помощью метода CTable::CellInitialize() осуществляется инициализация ячеек добавленного столбца значениями по умолчанию. После этого, если общее количество столбцов не больше установленного видимого количества:
- Осуществляется расчёт ширины столбцов
- Создаётся определённое количество графических объектов (ячеек таблицы) для добавленного столбца
- В случае необходимости осуществляется форматирование таблицы в стиле «Зебра»
- И в самом конце метода таблица обновляется
Если же после увеличения массивов столбцов и рядов оказалось, что общее количество столбцов больше, чем установленное видимое количество, то это означает, что нужно показать горизонтальную полосу прокрутки, а значит, и скорректировать высоту таблицы. После этого таблица форматируется в стиле «Зебра», обновляется, и программа выходит из метода.
//| Добавляет столбец в таблицу |
//+------------------------------------------------------------------+
void CTable::AddColumn(void)
{
//--- Увеличим размер массива на один элемент
uint array_size=ColumnsTotal();
m_columns_total=array_size+1;
::ArrayResize(m_vcolumns,m_columns_total);
//--- Установить размер массивам рядов
RowResize(array_size,m_rows_total);
//--- Инициализация массивов значениями по умолчанию
CellInitialize(array_size);
//--- Если общее количество столбцов больше видимого количества
if(m_columns_total>m_visible_columns_total)
{
//--- Скорректировать размер таблицы по оси Y
YResize();
//--- Если нет вертикальной полосы прокрутки, сделать горизонтальную полосу прокрутки на всю ширину таблицы
if(m_rows_total<=m_visible_rows_total)
m_scrollh.ChangeXSize(m_x_size);
//--- Скорректировать размер ползунка и показать полосу прокрутки
m_scrollh.ChangeThumbSize(m_columns_total,m_visible_columns_total);
//--- Показать полосу прокрутки
if(CElementBase::IsVisible())
m_scrollh.Show();
//--- Форматирование рядов в стиле "Зебра"
ZebraFormatRows();
//--- Обновить таблицу
UpdateTable();
return;
}
//--- Расчёт ширины столбцов
int width=CalculationColumnWidth();
//--- Корректировка ширины последнего столбца
if(m_columns_total>=m_visible_columns_total)
width=CalculationColumnWidth(true);
//--- Расчёт координаты X
int x=CalculationCellX(array_size);
//---
for(uint r=0; r<m_rows_total && r<m_visible_rows_total; r++)
{
//--- Расчёт координаты Y
int y=CalculationCellY(r);
//--- Создание объекта
CreateCell(array_size,r,x,y,width);
//--- Установить соответствующий цвет заголовку
if(m_fix_first_row && r==0)
m_columns[array_size].m_rows[r].BackColor(m_headers_color);
}
//--- Форматирование рядов в стиле "Зебра"
ZebraFormatRows();
//--- Обновить таблицу
UpdateTable();
}
Методы для прокрутки таблицы CTable::VerticalScrolling() и CTable::HorizontalScrolling() практически идентичны тем, что рассматривались в разделе списков, поэтому не будем здесь приводить их код. Вы можете ознакомиться с ними самостоятельно в приложенных к статье файлах в архиве.
Далее создадим тестовое MQL-приложение, которое позволит продемонстрировать новые возможности списков и таблицы типа CTable.
Приложение для теста элемента
Для теста напишем такое MQL-приложение, чтобы можно было сразу увидеть, как работают методы, которые мы добавили в классы списков и таблицы типа CTable. В графическом интерфейсе этого приложения создадим две вкладки. На первой вкладке разместим таблицу типа CTable, а над таблицей создадим элементы для управления свойствами таблицы. Это будут две кнопки и четыре числовых поля ввода:
- Кнопка «CLEAR TABLE» для очищения таблицы (удаление всех столбцов и рядов)
- Кнопка «REBUILD TABLE» для реконструкции таблицы по заданным параметрам в числовых полях ввода
- Поле ввода «Rows total» для указания общего количества рядов таблицы
- Поле ввода «Columns total» для указания общего количества столбцов таблицы
- Поле ввода «Visible rows total» для указания видимого количества рядов таблицы
- Поле ввода «Visible columns total» для указания видимого количества столбцов таблицы
На скриншоте ниже показано, как это выглядит:
Рис. 4. Группа элементов на первой вкладке
На второй вкладке разместим два списка (простой список и список с чекбоксами). Для демонстрации программного управления списками здесь будут следующие элементы:
- Кнопка «CLEAR LISTS» для очищения списков (удаление всех пунктов)
- Кнопка «REBUILD LISTS» для реконструкции списков по заданным параметрам в числовых полях ввода
- Поле ввода «Items total» для указания общего количества пунктов списков
- Поле ввода «Visible items total» для указания видимого количества пунктов списков
На скриншоте ниже показаны элементы на второй вкладке. В качестве дополнения на ней созданы ещё два элемента: выпадающий календарь и элемент «Время».
Рис. 5. Группа элементов на второй вкладке
Прежде чем рассказывать дальше о демонстрации реализованного в этом обновлении функционала списков и таблицы, остановимся ещё на одном дополнении, которое облегчит работу MQL-разработчика в таймере его MQL-приложения. Это класс CTimeCounter. С его помощью можно будет контролировать частоту обновления (перерисовку) для отдельных групп элементов графического интерфейса через указанные временные интервалы. В классе CTimeCounter есть только три поля и два метода (см. листинг кода ниже).
//| TimeCounter.mqh |
//| Copyright 2016, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Счётчик времени |
//+------------------------------------------------------------------+
class CTimeCounter
{
private:
//--- Шаг счётчика
uint m_step;
//--- Временной интервал
uint m_pause;
//--- Счётчик времени
uint m_time_counter;
//---
public:
CTimeCounter(void);
~CTimeCounter(void);
//--- Установка шага и временного интервала
void SetParameters(const uint step,const uint pause);
//--- Проверяет прохождение указанного временного интервала
bool CheckTimeCounter(void);
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CTimeCounter::CTimeCounter(void) : m_step(16),
m_pause(1000),
m_time_counter(0)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CTimeCounter::~CTimeCounter(void)
{
}
С помощью метода CTimeCounter::SetParameters() можно установить шаг приращения счётчика и временной интервал для паузы:
//| Установка шага и временного интервала |
//+------------------------------------------------------------------+
void CTimeCounter::SetParameters(const uint step,const uint pause)
{
m_step =step;
m_pause =pause;
}
Метод CTimeCounter::CheckTimeCounter() предназначен для проверки прохождения указанного в параметрах класса временного интервала. Если временной интервал пройден, то метод возвращает true.
//| Проверяет прохождение указанного временного интервала |
//+------------------------------------------------------------------+
bool CTimeCounter::CheckTimeCounter(void)
{
//--- Увеличим счётчик, если не прошли указанный временной интервал
if(m_time_counter<m_pause)
{
m_time_counter+=m_step;
return(false);
}
//--- Обнулить счётчик
m_time_counter=0;
return(true);
}
Прежде чем двигаться дальше, стоит ещё сообщить, что изменилось расположение файлов в директориях разрабатываемой библиотеки. Теперь в директории «MetaTrader 5\MQL5\Include\EasyAndFastGUI\Controls» расположены только те файлы, которые содержат классы элементов управления. Все остальные файлы перенесены в корневую директорию библиотеки: «MetaTrader 5\MQL5\Include\EasyAndFastGUI». Поэтому для подключения библиотеки к файлу своего пользовательского класса нужно написать путь, как показано в листинге ниже. Здесь также показано подключение файла с классом CTimeCounter (будет использоваться в тестовых примерах).
//| Program.mqh |
//| Copyright 2016, MetaQuotes Software Corp. |
//| http://www.mql5.com |
//+------------------------------------------------------------------+
#include <EasyAndFastGUI\WndEvents.mqh>
#include <EasyAndFastGUI\TimeCounter.mqh>
Установку параметров временных счётчиков расположим в конструкторе пользовательского класса:
//| Класс для создания приложения |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
{
protected:
//--- Временные счётчики
CTimeCounter m_counter1; // для обновления статусной строки
CTimeCounter m_counter2; // для обновления списков и таблицы
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CProgram::CProgram(void)
{
//--- Установка параметров для временных счётчиков
m_counter1.SetParameters(16,500);
m_counter2.SetParameters(16,150);
}
Демонстрацию добавления пунктов в списки, а также столбцов и рядов в таблицу, после полного очищения этих элементов, реализуем в таймере. Через указанный временной интервал, если количество пунктов/рядов/столбцов меньше, чем указано в соответствующих полях ввода, то в этом блоке (см. листинг кода ниже) они будут добавляться. Для демонстрации программного управления полосой прокрутки при каждом добавлении пункта в списки ползунки полос прокрутки будут смещаться в конец списков.
//| Таймер |
//+------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
{
CWndEvents::OnTimerEvent();
...
//--- Пауза между обновлением элементов
if(m_counter2.CheckTimeCounter())
{
//--- Добавить ряд в таблицу, если общее количество меньше указанного
if(m_table.RowsTotal()<m_spin_edit1.GetValue())
m_table.AddRow();
//--- Добавить столбец в таблицу, если общее кол-во меньше указанного
if(m_table.ColumnsTotal()<m_spin_edit2.GetValue())
m_table.AddColumn();
//--- Добавить пункт в список, если общее кол-во меньше указанного
if(m_listview.ItemsTotal()<m_spin_edit5.GetValue())
{
m_listview.AddItem("SYMBOL "+string(m_listview.ItemsTotal()));
//--- Переместить ползунок полосы прокрутки в конец списка
m_listview.Scrolling();
}
//--- Добавить пункт в список из чек-боксов, если общее кол-во меньше указанного
if(m_checkbox_list.ItemsTotal()<m_spin_edit5.GetValue())
{
m_checkbox_list.AddItem("Checkbox "+string(m_checkbox_list.ItemsTotal()));
//--- Переместить ползунок полосы прокрутки в конец списка
m_checkbox_list.Scrolling();
}
//--- Перерисовать график
m_chart.Redraw();
}
}
Обработка нажатий на кнопки для очищения и реконструкции списков и таблицы выглядит так:
//| Обработчик событий графика |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
//--- Событие нажатия на кнопке
if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
{
Print(__FUNCTION__," > id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
//--- Событие от первой кнопки
if(lparam==m_simple_button1.Id())
{
//--- Очистить таблицу
m_table.Clear();
return;
}
//--- Событие от второй кнопки
if(lparam==m_simple_button2.Id())
{
//--- Реконструировать таблицу
m_table.Rebuilding((int)m_spin_edit3.GetValue(),(int)m_spin_edit4.GetValue(),
(int)m_spin_edit1.GetValue(),(int)m_spin_edit2.GetValue());
//--- Инициализация таблицы
InitializeTable();
//--- Обновить таблицу для отображения изменений
m_table.UpdateTable();
return;
}
//--- Событие от третьей кнопки
if(lparam==m_simple_button3.Id())
{
//--- Очистить списки
m_listview.Clear();
m_checkbox_list.Clear();
return;
}
//--- Событие от четвёртой кнопки
if(lparam==m_simple_button4.Id())
{
//--- Реконструировать списки
m_checkbox_list.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
m_listview.Rebuilding((int)m_spin_edit5.GetValue(),(int)m_spin_edit6.GetValue());
//--- Выделить восьмой пункт в простом списке
m_listview.SelectItem(7);
//--- Заполнение списка данными
int items_total=m_listview.ItemsTotal();
for(int i=0; i<items_total; i++)
m_listview.SetItemValue(i,"SYMBOL "+string(i));
//--- Заполнение списка из чекбоксов данными, отметить чекбоксы через один
items_total=m_checkbox_list.ItemsTotal();
for(int i=0; i<items_total; i++)
{
m_checkbox_list.SetItemValue(i,"Checkbox "+string(i));
m_checkbox_list.SetItemState(i,(i%2!=0)? true : false);
}
//---
return;
}
//---
return;
}
}
В конце статьи можно загрузить к себе на компьютер это тестовое приложение для более подробного изучения.
Заключение
На текущем этапе разработки библиотеки для создания графических интерфейсов её общая схема выглядит так:
Рис. 6. Структура библиотеки на текущей стадии разработки.
В следующей версии библиотеки уже реализованные элементы будут развиваться и дополняться новым функционалом. Ниже вы можете загрузить к себе на компьютер последнюю версию библиотеки и файлы для тестов.
При возникновении вопросов по использованию материала, предоставленного в этих файлах, вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей этой серии или задать вопрос в комментариях к статье.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Если просто нужно обновить значения, то ничего удалять не нужно.
Если нужно изменить количество строк в таблице, то показан один из способов, как это можно сделать.
Второй способ это воспользоваться методом CTable::Rebuilding(). Но тогда нужно будет устанавливать заново некоторые свойства таблицы (заголовки, ширина столбцов и т.д.).
Спасибо, посмотрю еще раз.
Но у меня CCanvasTable. А какая разница вообще между CCanvasTable и CTable? Вроде, я и с CCanvasTable создаю таблицу нормально..
Спасибо, посмотрю еще раз.
Но у меня CCanvasTable. А какая разница вообще между CCanvasTable и CTable? Вроде, я и с CCanvasTable создаю таблицу нормально..
Скачайте последнюю версию библиотеки: EasyAndFast и обновлённые файлы с классами в этой статье: Торговый эксперт с графическим интерфейсом: Наполнение функционалом (Часть II)
Используйте класс CTable. CCanvasTable совсем старая версия с минимальными возможностями.
Скачал, но что-то не могу найти метод IsSortedColumnIndex() или его аналог.
...обновлённые файлы с классами в этой статье: Торговый эксперт с графическим интерфейсом: Наполнение функционалом (Часть II)
Класс CTable:
...
DeleteColumn(), DeleteRow() тоже исчезли.
Может нужно быть просто немного внимательней?
...обновлённые файлы с классами в этой статье: Торговый эксперт с графическим интерфейсом: Наполнение функционалом (Часть II)
Класс CTable:
Извиняюсь, мой косяк. Копирую в одно место, а смотрю в другом MetaEditor. Спасибо.