Пользовательские типы
Ключевое слово typedef в языке C++ позволяет создавать пользовательские типы данных – для этого достаточно определить новое имя типа данных для уже существующего типа данных. При этом сам новый тип данных не создается, а лишь определяется новое имя для уже существующего типа. Благодаря использованию пользовательских типов можно делать программы более гибкими: для этого иногда достаточно изменить typedef-инструкции с помощью макросов подстановки (#define). Использование пользовательских типов позволяет также улучшить читабельность кода, поскольку для стандартных типов данных с помощью typedef можно использовать собственные описательные имена. Общий формат записи инструкции для создания пользовательского типа:
Здесь элемент тип означает любой допустимый тип данных, а элемент новое_имя – новое имя для этого типа. Важно отметить, что новое имя определяется только в качестве дополнения к существующему имени типа, а не для его замены. В языке MQL5 с помощью typedef можно создавать указатели на функции.
Указатель на функцию
Указатель на функцию в общем виде определятся форматом записи
typedef тип_результата_функции (*Имя_типа_функции)(список_типов_входных параметров); |
где после слова typedef задается сигнатура функции – количество и тип входных параметров, а также тип возвращаемого функцией результата. В качестве объяснения приведем простой пример создания и использования указателя на функцию:
//--- объявим указатель на функцию, которая принимает два параметра типа int
typedef int (*TFunc)(int,int);
//--- TFunc является типом и мы можем объявить переменную-указатель на функцию
TFunc func_ptr; // указатель на функцию
//--- объявим функции, которые соответствуют описанию TFunc
int sub(int x,int y) { return(x-y); } // вычитание одного числа из другого
int add(int x,int y) { return(x+y); } // сложение двух чисел
int neg(int x) { return(~x); } // инвертирование битов в переменной
//--- в переменную func_ptr можно сохранить адрес функции, чтобы в дальнейшем ее вызывать
func_ptr=sub;
Print(func_ptr(10,5));
func_ptr=add;
Print(func_ptr(10,5));
func_ptr=neg; // ошибка: neg не имеет тип int (int,int)
Print(func_ptr(10)); // ошибка: должно быть два параметра |
В данном примере переменной func_ptr можно присвоить функции sub и add, поскольку они имеют по два входных параметра типа int, как это указано в определении указателя на функцию TFunc. А вот функция neg не может быть присвоена указателю func_ptr, так как ее сигнатура отличается.
Организации событийных моделей в пользовательском интерфейсе
С помощью указателей на функции удобно строить обработку событий при создании пользовательского интерфейса. Сначала определим указатель на функцию TAction, которая будет вызываться по нажатию кнопки, и создадим три функции в соответствии с описанием TAction.
//--- создадим пользовательский тип функции
typedef int(*TAction)(string,int);
//+------------------------------------------------------------------+
//| Открывает файл |
//+------------------------------------------------------------------+
int Open(string name,int id)
{
PrintFormat("Вызвана функция %s (name=%s id=%d)",__FUNCTION__,name,id);
return(1);
}
//+------------------------------------------------------------------+
//| Сохраняет файл |
//+------------------------------------------------------------------+
int Save(string name,int id)
{
PrintFormat("Вызвана функция %s (name=%s id=%d)",__FUNCTION__,name,id);
return(2);
}
//+------------------------------------------------------------------+
//| Закрывает файл |
//+------------------------------------------------------------------+
int Close(string name,int id)
{
PrintFormat("Вызвана функция %s (name=%s id=%d)",__FUNCTION__,name,id);
return(3);
}
|
Затем произведем класс MyButton от CButton, в котором добавим член TAction, являющийся указателем на функцию.
//+------------------------------------------------------------------+
//| Создадим свой класс кнопки с функцией обработки событий |
//+------------------------------------------------------------------+
class MyButton: public CButton
{
private:
TAction m_action; // обработчик событий графика
public:
MyButton(void){}
~MyButton(void){}
//--- конструктор с указанием текста кнопки и указателя на функцию для обработки событий
MyButton(string text, TAction act)
{
Text(text);
m_action=act;
}
//--- установка собственной функции, которая будет вызываться из обработчика событий OnEvent()
void SetAction(TAction act){m_action=act;}
//--- стандартный обработчик событий графика
virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) override
{
if(m_action!=NULL & lparam==Id())
{
//--- вызовем собственный обработчик m_action()
m_action(sparam,(int)lparam);
return(true);
}
else
//--- вернем результат вызова обработчика из родительского класса CButton
return(CButton::OnEvent(id,lparam,dparam,sparam));
}
}; |
Далее создадим производный класс CControlsDialog от CAppDialog, в котором добавим массив m_buttons для хранения кнопок типа MyButton, а также методы AddButton(MyButton &button) и CreateButtons().
//+------------------------------------------------------------------+
//| Класс CControlsDialog |
//| Назвначение: графическая панель для управления приложением |
//+------------------------------------------------------------------+
class CControlsDialog : public CAppDialog
{
private:
CArrayObj m_buttons; // массив кнопок
public:
CControlsDialog(void){};
~CControlsDialog(void){};
//--- create
virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) override;
//--- добавление кнопки
bool AddButton(MyButton &button){return(m_buttons.Add(GetPointer(button)));m_buttons.Sort();};
protected:
//--- создание кнопок
bool CreateButtons(void);
};
//+------------------------------------------------------------------+
//| Создание объекта CControlsDialog на графике |
//+------------------------------------------------------------------+
bool CControlsDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
{
if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
return(false);
return(CreateButtons());
//---
}
//+------------------------------------------------------------------+
//| defines |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT (11) // indent from left (with allowance for border width)
#define INDENT_TOP (11) // indent from top (with allowance for border width)
#define CONTROLS_GAP_X (5) // gap by X coordinate
#define CONTROLS_GAP_Y (5) // gap by Y coordinate
//--- for buttons
#define BUTTON_WIDTH (100) // size by X coordinate
#define BUTTON_HEIGHT (20) // size by Y coordinate
//--- for the indication area
#define EDIT_HEIGHT (20) // size by Y coordinate
//+------------------------------------------------------------------+
//| Создание и добавление кнопок на панель CControlsDialog |
//+------------------------------------------------------------------+
bool CControlsDialog::CreateButtons(void)
{
//--- расчет координат кнопок
int x1=INDENT_LEFT;
int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
int x2;
int y2=y1+BUTTON_HEIGHT;
//--- добавим объекты кнопок вместе с указателями на функции
AddButton(new MyButton("Open",Open));
AddButton(new MyButton("Save",Save));
AddButton(new MyButton("Close",Close));
//--- создадим кнопки графически
for(int i=0;i<m_buttons.Total();i++)
{
MyButton *b=(MyButton*)m_buttons.At(i);
x1=INDENT_LEFT+i*(BUTTON_WIDTH+CONTROLS_GAP_X);
x2=x1+BUTTON_WIDTH;
if(!b.Create(m_chart_id,m_name+"bt"+b.Text(),m_subwin,x1,y1,x2,y2))
{
PrintFormat("Failed to create button %s %d",b.Text(),i);
return(false);
}
//--- добавим каждую кнопку в контейнер CControlsDialog
if(!Add(b))
return(false);
}
//--- succeed
return(true);
} |
Теперь мы можем написать программу с использованием панели управления CControlsDialog, в которой создается 3 кнопки "Open", "Save" и "Close". При нажатии на кнопку вызывается соответствующая ей функция, которая прописана в виде указателя на функцию TAction.
//--- объявим объект на глобальном уровне, чтобы создать его автоматически при запуске программы
CControlsDialog MyDialog;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- теперь создадим объект на графике
if(!MyDialog.Create(0,"Controls",0,40,40,380,344))
return(INIT_FAILED);
//--- запускаем приложение
MyDialog.Run();
//--- успешная инициализация приложения
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- очистим комментарии при завершении работы приложения
Comment(");
//--- destroy dialog
MyDialog.Destroy(reason);
}
//+------------------------------------------------------------------+
//| Expert chart event function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, // event ID
const long& lparam, // event parameter of the long type
const double& dparam, // event parameter of the double type
const string& sparam) // event parameter of the string type
{
//--- для событий графика вызываем обработчик из родительского класса (CAppDialog в данном случае)
MyDialog.ChartEvent(id,lparam,dparam,sparam);
} |
Внешний вид запущенного приложения и результаты нажатия кнопок представлены на картинке.
Полный исходный код программы
//+------------------------------------------------------------------+
//| Panel_Buttons.mq5 |
//| Copyright 2017, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Панель с несколькими кнопками CButton"
#include <Controls\Dialog.mqh>
#include <Controls\Button.mqh>
//+------------------------------------------------------------------+
//| defines |
//+------------------------------------------------------------------+
//--- indents and gaps
#define INDENT_LEFT (11) // indent from left (with allowance for border width)
#define INDENT_TOP (11) // indent from top (with allowance for border width)
#define CONTROLS_GAP_X (5) // gap by X coordinate
#define CONTROLS_GAP_Y (5) // gap by Y coordinate
//--- for buttons
#define BUTTON_WIDTH (100) // size by X coordinate
#define BUTTON_HEIGHT (20) // size by Y coordinate
//--- for the indication area
#define EDIT_HEIGHT (20) // size by Y coordinate
//--- создадим пользовательский тип функции
typedef int(*TAction)(string,int);
//+------------------------------------------------------------------+
//| Открывает файл |
//+------------------------------------------------------------------+
int Open(string name,int id)
{
PrintFormat("Вызвана функция %s (name=%s id=%d)",__FUNCTION__,name,id);
return(1);
}
//+------------------------------------------------------------------+
//| Сохраняет файл |
//+------------------------------------------------------------------+
int Save(string name,int id)
{
PrintFormat("Вызвана функция %s (name=%s id=%d)",__FUNCTION__,name,id);
return(2);
}
//+------------------------------------------------------------------+
//| Закрывает файл |
//+------------------------------------------------------------------+
int Close(string name,int id)
{
PrintFormat("Вызвана функция %s (name=%s id=%d)",__FUNCTION__,name,id);
return(3);
}
//+------------------------------------------------------------------+
//| Создадим свой класс кнопки с функцией обработки событий |
//+------------------------------------------------------------------+
class MyButton: public CButton
{
private:
TAction m_action; // обработчик событий графика
public:
MyButton(void){}
~MyButton(void){}
//--- конструктор с указанием текста кнопки и указателя на функцию для обработки событий
MyButton(string text,TAction act)
{
Text(text);
m_action=act;
}
//--- установка собственной функции, которая будет вызываться из обработчика событий OnEvent()
void SetAction(TAction act){m_action=act;}
//--- стандартный обработчик событий графика
virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) override
{
if(m_action!=NULL & lparam==Id())
{
//--- вызовем собственный обработчик
m_action(sparam,(int)lparam);
return(true);
}
else
//--- вернем результат вызова обработчика из родительского класса CButton
return(CButton::OnEvent(id,lparam,dparam,sparam));
}
};
//+------------------------------------------------------------------+
//| Класс CControlsDialog |
//| Назвначение: графическая панель для управления приложением |
//+------------------------------------------------------------------+
class CControlsDialog : public CAppDialog
{
private:
CArrayObj m_buttons; // массив кнопок
public:
CControlsDialog(void){};
~CControlsDialog(void){};
//--- create
virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) override;
//--- добавление кнопки
bool AddButton(MyButton &button){return(m_buttons.Add(GetPointer(button)));m_buttons.Sort();};
protected:
//--- создание кнопок
bool CreateButtons(void);
};
//+------------------------------------------------------------------+
//| Создание объекта CControlsDialog на графике |
//+------------------------------------------------------------------+
bool CControlsDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
{
if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
return(false);
return(CreateButtons());
//---
}
//+------------------------------------------------------------------+
//| Создание и добавление кнопок на панель CControlsDialog |
//+------------------------------------------------------------------+
bool CControlsDialog::CreateButtons(void)
{
//--- расчет координат кнопок
int x1=INDENT_LEFT;
int y1=INDENT_TOP+(EDIT_HEIGHT+CONTROLS_GAP_Y);
int x2;
int y2=y1+BUTTON_HEIGHT;
//--- добавим объекты кнопок вместе с указателями на функции
AddButton(new MyButton("Open",Open));
AddButton(new MyButton("Save",Save));
AddButton(new MyButton("Close",Close));
//--- создадим кнопки графически
for(int i=0;i<m_buttons.Total();i++)
{
MyButton *b=(MyButton*)m_buttons.At(i);
x1=INDENT_LEFT+i*(BUTTON_WIDTH+CONTROLS_GAP_X);
x2=x1+BUTTON_WIDTH;
if(!b.Create(m_chart_id,m_name+"bt"+b.Text(),m_subwin,x1,y1,x2,y2))
{
PrintFormat("Failed to create button %s %d",b.Text(),i);
return(false);
}
//--- добавим каждую кнопку в контейнер CControlsDialog
if(!Add(b))
return(false);
}
//--- succeed
return(true);
}
//--- объявим объект на глобальном уровне, чтобы создать его автоматически при запуске программы
CControlsDialog MyDialog;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- теперь создадим объект на графике
if(!MyDialog.Create(0,"Controls",0,40,40,380,344))
return(INIT_FAILED);
//--- запускаем приложение
MyDialog.Run();
//--- успешная инициализация приложения
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- очистим комментарии при завершении работы приложения
Comment(");
//--- destroy dialog
MyDialog.Destroy(reason);
}
//+------------------------------------------------------------------+
//| Expert chart event function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, // event ID
const long& lparam, // event parameter of the long type
const double& dparam, // event parameter of the double type
const string& sparam) // event parameter of the string type
{
//--- для событий графика вызываем обработчика из родительского класса (CAppDialog в данном случае)
MyDialog.ChartEvent(id,lparam,dparam,sparam);
} |
Смотри также
Переменные, Функции
|