Графические интерфейсы X: Элемент "Время", элемент "Список из чекбоксов" и сортировка таблицы (build 6)
Содержание
- Введение
- Элемент «Время»
- Класс для создания элемента «Время»
- Элемент «Список с чекбоксами»
- Класс для создания списка из чекбоксов
- Сортировка таблицы
- Другие обновления библиотеки
- Приложение для теста элемента
- Заключение
Введение
О том, для чего предназначена эта библиотека, более подробно можно прочитать в первой статье серии: Графические интерфейсы I: Подготовка структуры библиотеки (Глава 1). В конце статей каждой части представлен список глав со ссылками, и там же есть возможность загрузить к себе на компьютер полную версию библиотеки на текущей стадии разработки. Файлы нужно разместить по тем же директориям, как они расположены в архиве.
Библиотека продолжает развиваться. Рассмотрим такие элементы, как «Время» и «Список из чекбоксов». Кроме этого, в класс таблицы типа CTable добавлена возможность сортировать данные по возрастанию и убыванию. Об этом и о других обновлениях и будет рассказано в этой статье.
Элемент «Время»
При создании графического интерфейса для индикатора или торгового эксперта иногда может понадобиться указать временные диапазоны. Иногда такая необходимость возникает при внутридневном трейдинге. В одной из предыдущих статей уже были продемонстрированы Календарь и Выпадающий календарь, с помощью которых можно установить дату, но не время (часы и минуты).
Перечислим все составные части, из которых будет собираться элемент «Время»:
- Фон
- Ярлык
- Описание
- Два поля ввода
Рис. 1. Составные части элемента «Время».
Рассмотрим подробнее, как устроен класс этого элемента.
Класс для создания элемента «Время»
Создаём файл TimeEdit.mqh с классом CTimeEdit со стандартными для всех элементов методами и подключаем его к движку библиотеки (файл WndContainer.mqh). В списке ниже перечислены свойства элемента, которые доступны для настройки пользователем.
- Цвет фона элемента
- Ярлыки элемента для активного и заблокированного состояния
- Отступы ярлыка по двум осям (x, y)
- Текст описания элемента
- Отступы текстовой метки по двум осям (x, y)
- Цвет текста в разных состояниях элемента
- Ширина полей ввода
- Отступы полей ввода по двум осям (x, y)
- Состояние элемента (доступен/заблокирован)
- Режим сброса значений в полях ввода
//| Класс для создания элемента "Время" |
//+------------------------------------------------------------------+
class CTimeEdit : public CElement
{
private:
//--- Цвет фона элемента
color m_area_color;
//--- Ярлыки элемента в активном и заблокированном состоянии
string m_icon_file_on;
string m_icon_file_off;
//--- Отступы ярлыка
int m_icon_x_gap;
int m_icon_y_gap;
//--- Текст описания элемента
string m_label_text;
//--- Отступы текстовой метки
int m_label_x_gap;
int m_label_y_gap;
//--- Цвета текста в разных состояниях
color m_label_color;
color m_label_color_hover;
color m_label_color_locked;
color m_label_color_array[];
//--- Размеры поля ввода
int m_edit_x_size;
//--- Отступы поля ввода
int m_edit_x_gap;
int m_edit_y_gap;
//--- Состояние элемента (доступен/заблокирован)
bool m_time_edit_state;
//--- Режим сброса значения
bool m_reset_mode;
//---
public:
//--- (1) Цвет фона, (2) отступы ярлыка
void AreaColor(const color clr) { m_area_color=clr; }
void IconXGap(const int x_gap) { m_icon_x_gap=x_gap; }
void IconYGap(const int y_gap) { m_icon_y_gap=y_gap; }
//--- (1) Текст описания элемента, (2) отступы текстовой метки
string LabelText(void) const { return(m_label.Description()); }
void LabelText(const string text) { m_label.Description(text); }
void LabelXGap(const int x_gap) { m_label_x_gap=x_gap; }
void LabelYGap(const int y_gap) { m_label_y_gap=y_gap; }
//--- Цвета текстовой метки в разных состояниях
void LabelColor(const color clr) { m_label_color=clr; }
void LabelColorHover(const color clr) { m_label_color_hover=clr; }
void LabelColorLocked(const color clr) { m_label_color_locked=clr; }
//--- (1) Размеры поля ввода, (2) отступы полей ввода
void EditXSize(const int x_size) { m_edit_x_size=x_size; }
void EditXGap(const int x_gap) { m_edit_x_gap=x_gap; }
void EditYGap(const int y_gap) { m_edit_y_gap=y_gap; }
//--- (1) Режим сброса при нажатии на текстовой метке, (2) режим текстового выделения
bool ResetMode(void) { return(m_reset_mode); }
void ResetMode(const bool mode) { m_reset_mode=mode; }
};
Для создания элемента «Время» задействованы пять приватных (private) методов и один публичный (public). Этот элемент относится к составному типу, и в качестве полей ввода будут использоваться уже готовые элементы типа CSpinEdit.
{
private:
//--- Объекты для создания элемента
CRectLabel m_area;
CBmpLabel m_icon;
CLabel m_label;
CSpinEdit m_hours;
CSpinEdit m_minutes;
//---
public:
//--- Методы для создания элемента
bool CreateTimeEdit(const long chart_id,const int subwin,const string label_text,const int x_gap,const int y_gap);
//---
private:
bool CreateArea(void);
bool CreateIcon(void);
bool CreateLabel(void);
bool CreateHoursEdit(void);
bool CreateMinutesEdit(void);
//---
public:
//--- Возвращает указатели полей ввода
CSpinEdit *GetHoursEditPointer(void) { return(::GetPointer(m_hours)); }
CSpinEdit *GetMinutesEditPointer(void) { return(::GetPointer(m_minutes)); }
};
Чтобы программно получить и установить текущие значения в полях ввода (часы и минуты), воспользуйтесь методами, показанными в листинге кода ниже:
{
public:
//--- Возвращение и установка значений полей ввода
int GetHours(void) const { return((int)m_hours.GetValue()); }
int GetMinutes(void) const { return((int)m_minutes.GetValue()); }
void SetHours(const uint value) { m_hours.ChangeValue(value); }
void SetMinutes(const uint value) { m_minutes.ChangeValue(value); }
};
Далее в статье будет показан пример того, как этот элемент выглядит на графике в терминале.
Элемент «Список с чекбоксами»
В одной из предыдущих статей подробно рассматривался элемент «Список» (класс CListView), с помощью которого можно выбрать один пункт из представленного списка. Но иногда может возникнуть необходимость выделить несколько пунктов. Например, может понадобиться создать список символов или таймфреймов, в котором пользователь MQL-приложения может выбрать только те, которые необходимы ему для торговли.
Список составных частей, из которых будет собираться элемент «Список из чекбоксов»:
- Общий фон элемента
- Вертикальная полоса прокрутки
- Группа чекбоксов:
- Фон
- Ярлык
- Текстовая метка
Рис. 2. Составные части элемента «Список из чекбоксов»
Далее кратко рассмотрим, чем отличается этот тип списка (CCheckBoxList) от простого списка (CListView), который мы уже рассматривали ранее.
Класс для создания списка из чекбоксов
Создаём файл CheckBoxList.mqh с классом CCheckBoxList со стандартными для всех элементов библиотеки методами. Первое отличие от списка типа CListView заключается в том, что пункты списка собираются из трёх графических объектов (см. листинг кода ниже). Для каждого типа объекта создается отдельный приватный метод.
//| Класс для создания списка с чекбоксами |
//+------------------------------------------------------------------+
class CCheckBoxList : public CElement
{
private:
//--- Объекты для создания списка
CRectLabel m_area;
CEdit m_items[];
CBmpLabel m_checks[];
CLabel m_labels[];
CScrollV m_scrollv;
//---
public:
//--- Методы для создания элемента
bool CreateCheckBoxList(const long chart_id,const int subwin,const int x_gap,const int y_gap);
//---
private:
bool CreateArea(void);
bool CreateList(void);
bool CreateItems(void);
bool CreateChecks(void);
bool CreateLabels(void);
bool CreateScrollV(void);
};
Кроме массива значений пунктов (описание пунктов), понадобится еще и массив состояний чекбоксов (включен/выключен). Также потребуются соответствующие методы для установки и получения значений по указанному индексу в списке:
{
private:
//--- Массив значений и состояний чекбоксов списка
string m_item_value[];
bool m_item_state[];
//---
public:
//--- Возвращает/cохраняет (1) состояние и (2) текст пункта в списке по указанному индексу
void SetItemState(const uint item_index,const bool state);
void SetItemValue(const uint item_index,const string value);
bool GetItemState(const uint item_index);
string GetItemValue(const uint item_index);
};
//+------------------------------------------------------------------+
//| Установка состояния |
//+------------------------------------------------------------------+
void CCheckBoxList::SetItemState(const uint item_index,const bool state)
{
uint array_size=::ArraySize(m_item_state);
//--- Если нет ни одного пункта в списке, сообщить об этом
if(array_size<1)
::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в списке есть хотя бы один пункт!");
//--- Корректировка в случае выхода из диапазона
uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Сохранить значение
m_item_state[check_index]=state;
//--- Сдвигает список относительно полосы прокрутки
ShiftList();
}
//+------------------------------------------------------------------+
//| Получение состояния чекбокса списка |
//+------------------------------------------------------------------+
bool CCheckBoxList::GetItemState(const uint item_index)
{
uint array_size=::ArraySize(m_item_state);
//--- Если нет ни одного пункта в списке, сообщить об этом
if(array_size<1)
::Print(__FUNCTION__," > Вызов этого метода нужно осуществлять, когда в списке есть хотя бы один пункт!");
//--- Корректировка в случае выхода из диапазона
uint check_index=(item_index>=array_size)? array_size-1 : item_index;
//--- Сохранить значение
return(m_item_state[check_index]);
}
Соответствующие изменения были внесены в методы, которые относятся к управлению списком. С ними Вы можете ознакомиться самостоятельно.
Сортировка таблицы
Если в графическом интерфейсе приложения используются таблицы с данными, то иногда может понадобиться отсортировать их по указанному пользователем столбцу. Во многих реализациях графических интерфейсов это реализовано так, что данные сортируются нажатием на заголовок столбца. Первое нажатие на заголовке сортирует данные по возрастанию, то есть от минимального значения к максимальному. Второе нажатие сортирует данные по убыванию, то есть от максимального значения к минимальному.
На скриншоте ниже показано окно «Инструменты» из редактора кода MetaEditor, с таблицей из трёх столбцов. Сортировка (на убывание) в таблице выполнена по третьему столбцу, в котором размещены даты.
Рис. 3. Пример таблицы с отсортированными данными
В качестве признака отсортированных данных обычно в заголовке столбца показывают стрелку. Если она направлена вниз, как на скриншоте выше, то это означает, что данные отсортированы на убывание и наоборот.
Итак, в этой статье сделаем сортировку для таблицы типа CTable. В ней уже есть возможность включать заголовки для столбцов, и теперь их нужно сделать интерактивными. В первую очередь нужно сделать так, чтобы заголовки изменяли свой цвет при наведении на них курсора мыши, а также при нажатии на них. Для этого добавим в класс CTable поля и методы для установки цвета заголовков столбцов в разных состояниях (см. листинг кода ниже).
{
private:
//--- Цвет фона заголовков
color m_headers_color;
color m_headers_color_hover;
color m_headers_color_pressed;
//---
public:
//--- Цвета фона заголовков
void HeadersColor(const color clr) { m_headers_color=clr; }
void HeadersColorHover(const color clr) { m_headers_color_hover=clr; }
void HeadersColorPressed(const color clr) { m_headers_color_pressed=clr; }
};
Пользователь сам решает, нужна ли возможность сортировки в таблице. По умолчанию режим сортировки будет отключен. Для его включения воспользуйтесь методом CTable::IsSortMode():
{
private:
//--- Режим сортируемых данных по столбцам
bool m_is_sort_mode;
//---
public:
//--- Режим сортируемых данных
void IsSortMode(const bool flag) { m_is_sort_mode=flag; }
};
Для изменения цвета заголовков при наведении курсора мыши будет использоваться приватный метод CTable::HeaderColorByHover(). Его вызов осуществляется в обработчике событий элемента.
{
private:
//--- Изменение цвета заголовка таблицы при наведении курсора мыши
void HeaderColorByHover(void);
};
//+------------------------------------------------------------------+
//| Изменение цвета заголовка таблицы при наведении курсора мыши |
//+------------------------------------------------------------------+
void CTable::HeaderColorByHover(void)
{
//--- Выйти, если режим сортируемых столбцов отключен
if(!m_is_sort_mode || !m_fix_first_row)
return;
//---
for(uint c=0; c<m_visible_columns_total; c++)
{
//--- Проверим фокус на текущем заголовке
bool condition=m_mouse.X()>m_columns[c].m_rows[0].X() && m_mouse.X()<m_columns[c].m_rows[0].X2() &&
m_mouse.Y()>m_columns[c].m_rows[0].Y() && m_mouse.Y()<m_columns[c].m_rows[0].Y2();
//---
if(!condition)
m_columns[c].m_rows[0].BackColor(m_headers_color);
else
{
if(!m_mouse.LeftButtonState())
m_columns[c].m_rows[0].BackColor(m_headers_color_hover);
else
m_columns[c].m_rows[0].BackColor(m_headers_color_pressed);
}
}
}
Для создания ярлыка-признака отсортированных данных нужно добавить приватный метод CTable::CreateSignSortedData(). Если режим сортировки не был включен перед созданием таблицы, то картинка не будет создана. Если же режим сортировки включен, то картинка будет скрыта сразу после создания, так как изначально данные таблицы не отсортированы.
{
private:
//--- Объекты для создания таблицы
CBmpLabel m_sort_arrow;
//---
private:
//--- Методы для создания таблицы
bool CreateSignSortedData(void);
};
//+------------------------------------------------------------------+
//| Создаёт ярлык-стрелку, как признак отсортированных данных |
//+------------------------------------------------------------------+
bool CTable::CreateSignSortedData(void)
{
//--- Выйти, если режим сортировки не включен
if(!m_is_sort_mode)
return(true);
//--- Формирование имени объекта
string name=CElement::ProgramName()+"_table_sort_array_"+(string)CElement::Id();
//--- Координаты
int x =(m_anchor_right_window_side)? m_columns[0].m_rows[0].X()-m_columns[0].m_rows[0].XSize()+m_sort_arrow_x_gap : m_columns[0].m_rows[0].X2()-m_sort_arrow_x_gap;
int y =(m_anchor_bottom_window_side)? CElement::Y()-m_sort_arrow_y_gap : CElement::Y()+m_sort_arrow_y_gap;
//--- Если не указали картинку для стрелки, значит установка по умолчанию
if(m_sort_arrow_file_on=="")
m_sort_arrow_file_on="Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
if(m_sort_arrow_file_off=="")
m_sort_arrow_file_off="Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//--- Установим объект
if(!m_sort_arrow.Create(m_chart_id,name,m_subwin,x,y))
return(false);
//--- Установим свойства
m_sort_arrow.BmpFileOn("::"+m_sort_arrow_file_on);
m_sort_arrow.BmpFileOff("::"+m_sort_arrow_file_off);
m_sort_arrow.Corner(m_corner);
m_sort_arrow.GetInteger(OBJPROP_ANCHOR,m_anchor);
m_sort_arrow.Selectable(false);
m_sort_arrow.Z_Order(m_zorder);
m_sort_arrow.Tooltip("\n");
//--- Сохраним координаты
m_sort_arrow.X(x);
m_sort_arrow.Y(y);
//--- Сохраним размеры (в объекте)
m_sort_arrow.XSize(m_sort_arrow.X_Size());
m_sort_arrow.YSize(m_sort_arrow.Y_Size());
//--- Отступы от крайней точки
m_sort_arrow.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
m_sort_arrow.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Скрыть объект
m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- Сохраним указатель объекта
CElement::AddToArray(m_sort_arrow);
return(true);
}
В структуру значений и свойств ячеек таблицы нужно добавить массив, в котором будут храниться количество десятичных знаков после запятой для каждой ячейки, если это вещественные числа, а также поле с типом данных для каждого столбца таблицы.
{
private:
//--- Массивы значений и свойств таблицы
struct TOptions
{
ENUM_DATATYPE m_type;
string m_vrows[];
uint m_digits[];
ENUM_ALIGN_MODE m_text_align[];
color m_text_color[];
color m_cell_color[];
};
TOptions m_vcolumns[];
};
При занесении значения в ячейку таблицы количество десятичных знаков после запятой для нее по умолчанию ставится равное нулю:
{
public:
//--- Устанавливает значение в указанную ячейку таблицы
void SetValue(const uint column_index,const uint row_index,const string value="",const uint digits=0);
};
Для установки типа данных в том или ином столбце таблицы, а также для получения типа нужно воспользоваться методами CTable::DataType():
{
public:
//--- Получение/установка типа данных
ENUM_DATATYPE DataType(const uint column_index) { return(m_vcolumns[column_index].m_type); }
void DataType(const uint column_index,const ENUM_DATATYPE type) { m_vcolumns[column_index].m_type=type; }
};
Перед созданием таблицы мы указываем общее количество столбцов и рядов. При этом поле m_type и массив m_digits инициализируются значениями по умолчанию. Изначально тип во всех столбцах строковой (TYPE_STRING), а количество знаков после запятой во всех ячейках равно нулю.
//| Устанавливает размер таблицы |
//+------------------------------------------------------------------+
void CTable::TableSize(const uint columns_total,const uint rows_total)
{
//--- Должно быть не менее одного столбца
m_columns_total=(columns_total<1) ? 1 : columns_total;
//--- Должно быть не менее двух рядов
m_rows_total=(rows_total<2) ? 2 : rows_total;
//--- Установить размер массиву столбцов
::ArrayResize(m_vcolumns,m_columns_total);
//--- Установить размер массивам рядов
for(uint i=0; i<m_columns_total; i++)
{
::ArrayResize(m_vcolumns[i].m_vrows,m_rows_total);
::ArrayResize(m_vcolumns[i].m_digits,m_rows_total);
::ArrayResize(m_vcolumns[i].m_text_align,m_rows_total);
::ArrayResize(m_vcolumns[i].m_text_color,m_rows_total);
::ArrayResize(m_vcolumns[i].m_cell_color,m_rows_total);
//--- Инициализация массива цвета фона ячеек значением по умолчанию
m_vcolumns[i].m_type=TYPE_STRING;
::ArrayInitialize(m_vcolumns[i].m_digits,0);
::ArrayInitialize(m_vcolumns[i].m_text_align,m_align_mode);
::ArrayInitialize(m_vcolumns[i].m_cell_color,m_cell_color);
::ArrayInitialize(m_vcolumns[i].m_text_color,m_cell_text_color);
}
}
Для сортировки массивов таблицы понадобится несколько приватных методов, в которых будут осуществляться следующие операции:
- Алгоритм сортировки.
- Сравнение значений по указанному условию.
- Обмен значений элементов массива.
За обмен значений элементов массива таблицы отвечает метод CTable::Swap(). Здесь обмен осуществляется сразу рядами таблицы. Местами меняются не только значения в ячейках, но и цвет текста.
{
private:
//--- Поменять значения в указанных ячейках местами
void Swap(uint c,uint r1,uint r2);
};
//+------------------------------------------------------------------+
//| Поменять элементы местами |
//+------------------------------------------------------------------+
void CTable::Swap(uint c,uint r1,uint r2)
{
//--- Пройдёмся в цикле по всем столбцам
for(uint i=0; i<m_columns_total; i++)
{
//--- Меняем местами текст
string temp_text =m_vcolumns[i].m_vrows[r1];
m_vcolumns[i].m_vrows[r1] =m_vcolumns[i].m_vrows[r2];
m_vcolumns[i].m_vrows[r2] =temp_text;
//--- Меняем местами цвет текста
color temp_text_color =m_vcolumns[i].m_text_color[r1];
m_vcolumns[i].m_text_color[r1] =m_vcolumns[i].m_text_color[r2];
m_vcolumns[i].m_text_color[r2] =temp_text_color;
}
}
Для корректной сортировки сравнивать значения нужно с приведением к указанному типу в поле m_type. Поэтому был написан отдельный метод CTable::CheckSortCondition().
{
private:
//--- Проверка условия сортировки
bool CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
};
//+------------------------------------------------------------------+
//| Сравнение значений по указанному условию сортировки |
//+------------------------------------------------------------------+
//| direction: true (>), false (<) |
//+------------------------------------------------------------------+
bool CTable::CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction)
{
bool condition=false;
//---
switch(m_vcolumns[column_index].m_type)
{
case TYPE_STRING :
{
string v1=m_vcolumns[column_index].m_vrows[row_index];
string v2=check_value;
condition=(direction)? v1>v2 : v1<v2;
break;
}
//---
case TYPE_DOUBLE :
{
double v1=double(m_vcolumns[column_index].m_vrows[row_index]);
double v2=double(check_value);
condition=(direction)? v1>v2 : v1<v2;
break;
}
//---
case TYPE_DATETIME :
{
datetime v1=::StringToTime(m_vcolumns[column_index].m_vrows[row_index]);
datetime v2=::StringToTime(check_value);
condition=(direction)? v1>v2 : v1<v2;
break;
}
//---
default :
{
long v1=(long)m_vcolumns[column_index].m_vrows[row_index];
long v2=(long)check_value;
condition=(direction)? v1>v2 : v1<v2;
break;
}
}
//---
return(condition);
}
Методы CTable::Swap() и CTable::CheckSortCondition() будут использоваться в методе с алгоритмом сортировки. Остановимся немного подробнее на том, какой именно был выбран алгоритм для сортировки данных. Были протестированы десять алгоритмов, включая штатную MQL-сортировку с помощью функции ArraySort():
- MQL-сортировка
- Сортировка выборкой
- Пузырьковая сортировка
- Шейкерная сортировка
- Сортировка вставкой
- Сортировка Шелла
- Бинарная сортировка
- Быстрая сортировка
- Пирамидальная сортировка
- Сортировка слиянием
Тест этих методов был осуществлён с массивом, размер которого равен 100000 (сто тысяч) элементов. Массив инициализировался случайными числами. Результаты теста показаны на гистограмме ниже:
Рис. 4. График результатов теста различных методов сортировки. Размер массива 100 000 элементов.
Самым быстрым оказался метод быстрой сортировки. Код этого метода был взят из стандартной библиотеки – метод QuickSort(). В этом тесте он даже показал результат лучше, чем функция ArraySort(). Замеры длительности действия алгоритма для большей точности осуществлялись с помощью функции GetMicrosecondCount(). Оставим только те алгоритмы, которые показали наилучший результат (менее одной секунды).
Рис. 5. График лучших результатов теста методов сортировки. Размер массива 100 000 элементов.
Увеличим размер массива в 10 раз, то есть теперь будем сортировать массив из 1000000 (одного миллиона) элементов.
Рис. 6. График результатов теста методов сортировки. Размер массива 1 000 000 элементов.
В этом тесте лучшим оказался штатный алгоритм – функция ArraySort(). Ненамного хуже показал себя метод быстрой сортировки, поэтому остановим свой выбор именно на нём. Функция ArraySort() не подходит под нашу задачу, так как: (1) нужна возможность осуществлять сортировку в двух направлениях, (2) в классе CTable используется массив структур и (3) нужно контролировать расположение не только значений в ячейках таблицы, но и другие их свойства.
В файл Enums.mqh нужно добавить перечисление ENUM_SORT_MODE для двух направлений сортировки:
- SORT_ASCEND – по возрастанию.
- SORT_DESCEND – по убыванию.
//| Перечисление режимов сортировки |
//+------------------------------------------------------------------+
enum ENUM_SORT_MODE
{
SORT_ASCEND =0,
SORT_DESCEND =1
};
В листинге кода ниже представлена текущая версия алгоритма быстрой сортировки в методе CTable::QuickSort(). Жёлтым маркером отмечены методы CTable::Swap() и CTable::CheckSortCondition(), которые были представлены до этого выше в статье.
{
private:
//--- Метод быстрой сортировки
void QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
};
//+------------------------------------------------------------------+
//| Алгоритм быстрой сортировки |
//+------------------------------------------------------------------+
void CTable::QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND)
{
uint r1 =beg;
uint r2 =end;
uint c =column;
string temp =NULL;
string value =NULL;
uint data_total =m_rows_total-1;
//--- Выполнять алгоритм, пока левый индекс меньше самого крайнего правого индекса
while(r1<end)
{
//--- Получим значение из середины ряда
value=m_vcolumns[c].m_vrows[(beg+end)>>1];
//--- Выполнять алгоритм, пока левый индекс меньше найденного правого индекса
while(r1<r2)
{
//--- Смещать индекс вправо, пока находим значение по указанному условию
while(CheckSortCondition(c,r1,value,(mode==SORT_ASCEND)? false : true))
{
//--- Контроль выхода за границы массива
if(r1==data_total)
break;
r1++;
}
//--- Смещать индекс влево, пока находим значение по указанному условию
while(CheckSortCondition(c,r2,value,(mode==SORT_ASCEND)? true : false))
{
//--- Контроль выхода за границы массива
if(r2==0)
break;
r2--;
}
//--- Если левый индекс ещё не больше правого
if(r1<=r2)
{
//--- Поменять значения местами
Swap(c,r1,r2);
//--- Если дошли до предела слева
if(r2==0)
{
r1++;
break;
}
//---
r1++;
r2--;
}
}
//--- Рекурсивное продолжение алгоритма, пока не дойдём до начала диапазона
if(beg<r2)
QuickSort(beg,r2,c,mode);
//--- Cужение диапазона для следующей итерации
beg=r1;
r2=end;
}
}
Всё это приватные методы. Теперь рассмотрим публичный метод CTable::SortData(), предназначенный для вызова (1) при нажатии на заголовках столбцов данных таблицы или (2) программно в любой другой момент, когда это может понадобиться по замыслу автора MQL-приложения. В метод CTable::SortData() нужно передать индекс столбца, по умолчанию сортируется первый столбец. Если сортировка указанного столбца осуществляется в первый раз или последняя сортировка этого же столбца была на убывание, то значения будут упорядочены по возрастанию. После сортировки данных таблица обновляется. И в последней строке метода CTable::SortData() устанавливается соответствующий ярлык для стрелки-признака отсортированной таблицы.
{
private:
//--- Индекс отсортированного столбца (WRONG_VALUE – таблица не отсортирована)
int m_is_sorted_column_index;
//--- Последнее направление сортировки
ENUM_SORT_MODE m_last_sort_direction;
//---
public:
//--- Сортировать данные по указанному столбцу
void SortData(const uint column_index=0);
};
//+------------------------------------------------------------------+
//| Сортировать данные по указанному столбцу |
//+------------------------------------------------------------------+
void CTable::SortData(const uint column_index=0)
{
//--- Индекс (с учётом наличия заголовков), с которого нужно начать сортировку
uint first_index=(m_fix_first_row) ? 1 : 0;
//--- Последний индекс массива
uint last_index=m_rows_total-1;
//--- В первый раз будет отсортировано по возрастанию, а затем каждый раз в противоложном направлении
if(m_is_sorted_column_index==WRONG_VALUE || column_index!=m_is_sorted_column_index || m_last_sort_direction==SORT_DESCEND)
m_last_sort_direction=SORT_ASCEND;
else
m_last_sort_direction=SORT_DESCEND;
//--- Запомним индекс последнего отсортированного столбца данных
m_is_sorted_column_index=(int)column_index;
//--- Сортировка
QuickSort(first_index,last_index,column_index,m_last_sort_direction);
//--- Обновить таблицу
UpdateTable();
//--- Установить ярлык в соответствии с направлением сортировки
m_sort_arrow.State((m_last_sort_direction==SORT_ASCEND)? true : false);
}
Понадобится метод для обработки нажатия на заголовках столбцов — CTable::OnClickTableHeaders(), когда включен режим сортировки данных. После прохождения всех проверок на принадлежность объекта к этому элементу в цикле определяется индекс заголовка (столбца) и затем таблица сортируется. Сразу после сортировки таблицы генерируется событие с новым идентификатором ON_SORT_DATA. Кроме этого идентификатора события, в сообщении содержится (1) идентификатор элемента, (2) индекс отсортированного столбца и (3) тип данных этого столбца.
{
private:
//--- Обработка нажатия на заголовках таблицы
bool OnClickTableHeaders(const string clicked_object);
};
//+------------------------------------------------------------------+
//| Обработка нажатия на заголовке таблицы |
//+------------------------------------------------------------------+
bool CTable::OnClickTableHeaders(const string clicked_object)
{
//--- Выйти, если режим сортировки отключен
if(!m_is_sort_mode)
return(false);
//--- Выйдем, если нажатие было не на ячейке таблицы
if(::StringFind(clicked_object,CElement::ProgramName()+"_table_edit_",0)<0)
return(false);
//--- Получим идентификатор имени объекта
int id=CElement::IdFromObjectName(clicked_object);
//--- Выйти, если идентификатор не совпадает
if(id!=CElement::Id())
return(false);
//--- Выйти, если это не заголовок таблицы
if(RowIndexFromObjectName(clicked_object)>0)
return(false);
//--- Для определения индекса столбца
uint column_index=0;
//--- Смещение на один индекс, если включен режим закреплённых заголовков
int l=(m_fix_first_column) ? 1 : 0;
//--- Получим текущую позицию ползунка горизонтальной полосы прокрутки
int h=m_scrollh.CurrentPos()+l;
//--- Столбцы
for(uint c=l; c<m_visible_columns_total; c++)
{
//--- Если нажатие было на этой ячейке
if(m_columns[c].m_rows[0].Name()==clicked_object)
{
//--- Получим индекс столбца
column_index=(m_fix_first_column && c==0) ? 0 : h;
break;
}
//---
h++;
}
//--- Сортировка данных по указанному столбцу
SortData(column_index);
//--- Отправим сообщение об этом
::EventChartCustom(m_chart_id,ON_SORT_DATA,CElement::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
return(true);
}
Если общее количество столбцов больше, чем количество видимых столбцов, то при перемещении горизонтальной полосы прокрутки нужно корректировать положение стрелки-признака отсортированной таблицы. Для этого будет использоваться приватный метод CTable::ShiftSortArrow().
{
private:
//--- Смещение стрелки-признака сортировки
void ShiftSortArrow(const uint column);
};
//+------------------------------------------------------------------+
//| Смещение стрелки на отсортированный столбец таблицы |
//+------------------------------------------------------------------+
void CTable::ShiftSortArrow(const uint column)
{
//--- Показать объект, если элемент не скрыт
if(CElement::IsVisible())
m_sort_arrow.Timeframes(OBJ_ALL_PERIODS);
//--- Рассчитать и установить координату
int x=m_columns[column].m_rows[0].X2()-m_sort_arrow_x_gap;
m_sort_arrow.X(x);
m_sort_arrow.X_Distance(x);
//--- Отступ от крайней точки
m_sort_arrow.XGap((m_anchor_right_window_side)? m_wnd.X2()-x : x-m_wnd.X());
}
Вызов этого метода будет в методе CTable::UpdateTable() в блоке кода, где осуществляется смещение заголовков. Ниже представлена сокращённая версия метода CTable::UpdateTable() с добавленными фрагментами. Здесь, если в первом цикле найден отсортированный столбец, то ставится флаг и осуществляется смещение стрелки-признака. После завершения цикла, если обнаружилось, что отсортированный столбец есть, но не был найден в предыдущем цикле, то это может означать, что он вышел из зоны видимости и его нужно скрыть. Если же это первый столбец (нулевой индекс) и при этом он зафиксирован от смещения, то стрелка-признак устанавливается на него.
//| Обновление данных таблицы с учётом последних изменений |
//+------------------------------------------------------------------+
void CTable::UpdateTable(void)
{
//...
//--- Смещение заголовков в верхнем ряду
if(m_fix_first_row)
{
//--- Для определения смещения ярлыка сортировки
bool is_shift_sort_arrow=false;
//--- Столбцы
for(uint c=l; c<m_visible_columns_total; c++)
{
//--- Если не выходим из диапазона
if(h>=l && h<m_columns_total)
{
//--- Если нашли отсортированный столбец
if(!is_shift_sort_arrow && m_is_sort_mode && h==m_is_sorted_column_index)
{
is_shift_sort_arrow=true;
//--- Скорректируем ярлык сортировки
uint column=h-(h-c);
if(column>=l && column<m_visible_columns_total)
ShiftSortArrow(column);
}
//--- Корректировка (1) значений, (2) цвета фона, (3) цвета текста и (4) выравнивания текста в ячейках
SetCellParameters(c,0,m_vcolumns[h].m_vrows[0],m_headers_color,m_headers_text_color,m_vcolumns[h].m_text_align[0]);
}
//---
h++;
}
//--- Если отсортированный столбец есть, но не был найден
if(!is_shift_sort_arrow && m_is_sort_mode && m_is_sorted_column_index!=WRONG_VALUE)
{
//--- Скрыть, если индекс больше нуля
if(m_is_sorted_column_index>0 || !m_fix_first_column)
m_sort_arrow.Timeframes(OBJ_NO_PERIODS);
//--- Установить на заголовок первого столбца
else
ShiftSortArrow(0);
}
}
//...
}
В конце статьи можно скачать файлы с тестовым экспертом, в котором можно самостоятельно протестировать, как это работает.
Другие обновления библиотеки
В качестве дополнительных возможностей в билд включены следующие обновления библиотеки:
1. Теперь для каждого элемента можно установить свой шрифт и его размер. Для этого в базовый класс элементов CElement добавлены соответствующие поля и методы. По умолчанию установлен шрифт «Calibri», а размер шрифта 8 пунктов.
{
protected:
//--- Шрифт
string m_font;
int m_font_size;
//---
public:
//--- (1) Шрифт и (2) размер шрифта
void Font(const string font) { m_font=font; }
string Font(void) const { return(m_font); }
void FontSize(const int font_size) { m_font_size=font_size; }
int FontSize(void) const { return(m_font_size); }
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_font("Calibri"),
m_font_size(8)
{
}
Соответственно во всех методах создания элементов, где нужно указывать шрифт значения берутся из базового класса. Ниже показан пример для текстовой метки класса CCheckBox. То же самое было сделано во всех классах библиотеки.
//| Создаёт текстовую метку чекбокса |
//+------------------------------------------------------------------+
bool CCheckBox::CreateLabel(void)
{
//--- Формирование имени объекта
string name=CElement::ProgramName()+"_checkbox_lable_"+(string)CElement::Id();
//--- Координаты
int x =(m_anchor_right_window_side)? m_x-m_label_x_gap : m_x+m_label_x_gap;
int y =(m_anchor_bottom_window_side)? m_y-m_label_y_gap : m_y+m_label_y_gap;
//--- Цвет текста относительно состояния
color label_color=(m_check_button_state) ? m_label_color : m_label_color_off;
//--- Установим объект
if(!m_label.Create(m_chart_id,name,m_subwin,x,y))
return(false);
//--- Установим свойства
m_label.Description(m_label_text);
m_label.Font(CElement::Font());
m_label.FontSize(CElement::FontSize());
m_label.Color(label_color);
m_label.Corner(m_corner);
m_label.Anchor(m_anchor);
m_label.Selectable(false);
m_label.Z_Order(m_zorder);
m_label.Tooltip("\n");
//--- Отступы от крайней точки
m_label.XGap((m_anchor_right_window_side)? x : x-m_wnd.X());
m_label.YGap((m_anchor_bottom_window_side)? y : y-m_wnd.Y());
//--- Инициализация массива градиента
CElement::InitColorArray(label_color,m_label_color_hover,m_label_color_array);
//--- Сохраним указатель объекта
CElement::AddToArray(m_label);
return(true);
}
2. Отступы от крайней точки формы для каждого элемента графического интерфейса теперь нужно передавать напрямую в метод создания элемента. Расчёт будет осуществляться автоматически. Для примера в листинге ниже показан метод создания выпадающего календаря из пользовательского класса CProgram.
//| Создаёт выпадающий календарь |
//+------------------------------------------------------------------+
bool CProgram::CreateDropCalendar(const int x_gap,const int y_gap,const string text)
{
//--- Передать объект панели
m_drop_calendar.WindowPointer(m_window);
//--- Закрепить за 2-ой вкладкой
m_tabs.AddToElementsArray(1,m_drop_calendar);
//--- Установим свойства перед созданием
m_drop_calendar.XSize(140);
m_drop_calendar.YSize(20);
m_drop_calendar.AreaBackColor(clrWhite);
//--- Создадим элемент управления
if(!m_drop_calendar.CreateDropCalendar(m_chart_id,m_subwin,text,x_gap,y_gap))
return(false);
//--- Добавим указатель на элемент в базу
CWndContainer::AddToElementsArray(0,m_drop_calendar);
return(true);
}
Приложение для теста элемента
Теперь напишем тестовое MQL-приложение, в котором Вы сможете протестировать все новые элементы. Создадим графический интерфейс, в котором будет главное меню (CMenuBar) с выпадающими контекстными меню, статусной строкой и двумя вкладками. На первой вкладке будет таблица типа CTable, с включенным режимом сортировки.
В таблице первые три столбца сделаем со следующими типами данных:
- Первый столбец – TYPE_DATETIME.
- Второй столбец – TYPE_DOUBLE.
- Третий столбец – TYPE_LONG.
Остальные столбцы по умолчанию останутся с типом TYPE_STRING. На скриншоте ниже показано, как выглядит графический интерфейс с таблицей на первой вкладке.
Рис. 7. Пример отсортированной (на возрастание) таблицы по первому второму столбцу.
На второй вкладке создадим четыре элемента:
- Выпадающий календарь (класс CDropCalendar).
- Элемент «Время» (класс CTimeEdit).
- Список из чекбоксов (класс CCheckBoxList).
- Простой список (класс CListView).
Ниже на скриншоте показано, как это выглядит в графическом интерфейсе тестового MQL-приложения:
Рис. 8. Элементы управления на второй вкладке.
Исходный код этого тестового приложения приложен в конце статьи.
Заключение
На текущем этапе разработки библиотеки для создания графических интерфейсов её общая схема выглядит так:
Рис. 9. Структура библиотеки на текущей стадии разработки.
Это не последняя статья серии о графических интерфейсах. Мы продолжим развивать ее и дополнять новыми возможностями. Ниже Вы можете загрузить к себе на компьютер последнюю версию библиотеки и файлы для тестов.
При возникновении вопросов по использованию материала, предоставленного в этих файлах, вы можете обратиться к подробному описанию процесса разработки библиотеки в одной из статей этой серии или задать вопрос в комментариях к статье.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Необходим элемент который будет добавлять произвольный текст в произвольное место (в виде информационной пометки), как вы посоветуете это сделать?
Возможно подойдёт элемент типа CTextLabel ?
Примеры можно посмотреть вот в этой статье: Графические интерфейсы X: Текстовое поле ввода, слайдер картинок и простые элементы управления (build 5)
Анатолий, твоя работа грандиозна, и даже хочется ее применять на практике, но многое сдерживает. В принципе можно допиливать твой код под свои нужды, но наверно лучше свой создавать.
Например такая досадная мелочь. Возникла мысль динамического изменения размера окна, т.е. многие оконные GUI интерфейсы дают возможность менять размер окна.
Как правило сложился следующий стиль, кликается мышкой в нижний правый угол окна, затем окно тянется до нужного размера и окно масштабируется.
Первое что попробовал , изменить размер окна.
В результате для TAB1 и TAB2 масштабируемость отсутствует.
...
Как правило сложился следующий стиль, кликается мышкой в нижний правый угол окна, затем окно тянется до нужного размера и окно масштабируется.
В планах есть, но когда это будет не могу пока сказать.
Дело в том, что, для того, чтобы начать "дирижировать" всеми элементами управления, которые представлены в библиотеке, их нужно сначала поднять до определённого уровня. Многие из них сейчас находятся в промежуточной стадии, а некоторые вообще, как временные варианты.
...
В принципе можно допиливать твой код под свои нужды, но наверно лучше свой создавать.
Попробуйте. Интересно было бы посмотреть на Вашу версию библиотеки, если Вы её опубликуете конечно.
P.S. Большие цели достигаются значительно быстрее совместными усилиями. )В планах есть, но когда это будет не могу пока сказать.
Дело в том, что, для того, чтобы начать "дирижировать" всеми элементами управления, которые представлены в библиотеке, их нужно сначала поднять до определённого уровня. Многие из них сейчас находятся в промежуточной стадии, а некоторые вообще, как временные варианты.
Попробуйте. Интересно было бы посмотреть на Вашу версию библиотеки, если Вы её опубликуете конечно.
P.S. Большие цели достигаются значительно быстрее совместными усилиями. )Это если команда работает с целями и на результат и нужен классный настоящий руководитель (менеджер) - не МАНАГЕР.
Это если команда работает с целями и на результат и нужен классный настоящий руководитель (менеджер) - не МАНАГЕР.
...
Можно и без руководителя. Если каждый сделает, какой-то свой маленький вклад в решении задач, которых ещё не касались.
Если бы я думал о том, что мне нужен руководитель, чтобы сдвинуться с места, то этой библиотеки, хотя бы на таком уровне развития, до сих пор бы не было. Я понимаю, что этого ещё далеко недостаточно. )