Торговые инструменты на языке MQL5 (Часть 9): Мастер первого запуска для советников с прокручиваемым руководством
Введение
В своей предыдущей статье (Часть 8) мы разработали информационную панель на MetaQuotes Language 5 (MQL5) для мониторинга позиций на нескольких символах и показателей счета. В части 9 мы создаем динамический мастер первого запуска, чтобы ознакомить новых пользователей при первом запуске программы. Мастера настройки при первом запуске являются необходимыми инструментами для упрощения конфигурации сложных систем, такие как советники (EAs) в MetaTrader 5, которые помогают новым пользователям в начальной настройке и предоставляют ориентирующий сценарий для обеспечения оптимального результата. В этой статье мы разрабатываем мастер первоначальной настройки пользователя MQL5 для советников, включающий прокручиваемую панель с динамическим текстом, интерактивные кнопки и флажок для обеспечения упрощенной конфигурации. Запускается только один раз при первом запуске программы. В статье рассмотрим следующие темы:
- Понимание роли и ценности руководства по первоначальной настройке для торговых программ
- Реализация средствами MQL5
- Тестирование мастера начальной настройки
- Заключение
В итоге у вас будет интерактивный мастер в MQL5 для улучшения инициализации советника, который можно адаптировать под ваши торговые задачи. Давайте начнём!
Понимание роли и ценности руководства по первоначальной настройке для торговых программ
Руководство по настройке при первом запуске является важной функцией для торговых программ, таких как советники (EAs) в MetaTrader 5. Оно содержит пошаговые инструкции по настройке основных параметров, таких как размеры лотов, уровни риска и торговые фильтры, помогая трейдерам избежать ошибок, которые могут привести к убыткам, например, при установке слишком большого размера лота, что может привести к чрезмерной просадке. Допустим, это скорее вводный сценарий, который знакомит новых пользователей со схемой и возможностями программы. Его ценность заключается в упрощении процесса присоединения для трейдеров с любым уровнем опыта, обеспечении правильной настройки программы с самого начала и использовании механизма запоминания того, было ли показано руководство, что позволяет избежать ненужных подсказок при будущих инициализациях для оптимизации пользовательского опыта, особенно для трейдеров, которые постоянно подключают программы к графикам.
Наш подход заключается в создании интуитивно понятной панели с возможностью прокрутки, отображающей четкое руководство по настройке с визуально различимым текстом (например, выделенными заголовками и интерактивными ссылками для получения поддержки), интерактивными кнопками для действий пользователя и флажком, позволяющим трейдерам выбирать, пропускать ли руководство в будущих запусках. Мы будем использовать возможности глобальной переменной MQL5 для сохранения выбора пользователя в переменной, которая сохраняет и напоминает номер сборки торгового терминала (версию программного обеспечения) и операционную систему (OS), в которой программа запускается впервые. Мы создадим централизованный интерфейс с заголовком, телом и нижней панелью, включающий динамическое форматирование текста для удобства чтения, прокручиваемый контент для получения подробных инструкций и адаптивный размер для соответствия различным разрешениям экрана. Это гарантирует, что трейдеры смогут легко выполнять такие действия, как настройка параметров риска или включение автоматической торговли, что делает процесс настройки плавным и эффективным. Мы будем следовать встроенной структуре руководства по настройке 'Торговли в один клик' (One Click Trading) следующим образом.

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

Реализация средствами MQL5
Чтобы создать программу на MQL5, откройте MetaEditor, перейдите в Навигатор, найдите папку «Советники» (Experts), щелкните кнопкой мыши на вкладке "Создать" (New) и следуйте инструкциям по созданию файла. Как только это будет сделано, в среде программирования нужно будет объявить некоторые глобальные переменные, которые будем использовать во всей программе.
//+------------------------------------------------------------------+ //| EA Initialization Setup.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #property icon "1. Forex Algo-Trader.ico" //+------------------------------------------------------------------+ //| Global variables for setup | //+------------------------------------------------------------------+ string GV_SETUP = ""; //--- Store global variable name with build and OS string g_scaled_image_resource = ""; //--- Store name of scaled image resource int g_mainX = 0; //--- Store calculated x-coordinate of main container int g_mainY = 50; //--- Set starting y-coordinate 50px below chart top int g_mainWidth = 500; //--- Set main container width int g_headerHeight = 50; //--- Set header height int g_footerHeight = 40; //--- Set footer height int g_padding = 10; //--- Set general padding int g_textPadding = 10; //--- Set text padding int g_spacing = 0; //--- Set spacing between header/body/footer int g_lineSpacing = 3; //--- Set spacing between text lines int g_minBodyHeight = 200; //--- Set minimum body height int g_maxBodyHeight = 400; //--- Set maximum body height int g_bottomMargin = 50; //--- Set bottom margin int g_displayHeight = 0; //--- Store calculated display height int g_mainHeight = 0; //--- Store calculated main container height int g_adjustedLineHeight = 0; //--- Store adjusted line height for scrolling int g_max_scroll = 0; //--- Store maximum scroll to prevent overflow bool scroll_visible = false; //--- Track scrollbar visibility bool mouse_in_body = false; //--- Track if mouse is in body int scroll_pos = 0; //--- Store current scroll position int prev_scroll_pos = -1; //--- Store previous scroll position int slider_height = 20; //--- Set default slider height bool movingStateSlider = false; //--- Track slider drag state int mlbDownX_Slider = 0; //--- Store mouse x on slider click int mlbDownY_Slider = 0; //--- Store mouse y on slider click int mlbDown_YD_Slider = 0; //--- Store slider y on click int g_total_height = 0; //--- Store total text height int g_visible_height = 0; //--- Store visible text height bool checkbox_checked = false; //--- Track checkbox state bool ok_button_hovered = false; //--- Track OK button hover bool cancel_button_hovered = false; //--- Track Cancel button hover bool checkbox_hovered = false; //--- Track checkbox hover bool header_cancel_hovered = false; //--- Track header cancel hover bool scroll_up_hovered = false; //--- Track scroll up button hover bool scroll_down_hovered = false; //--- Track scroll down button hover bool scroll_slider_hovered = false; //--- Track scroll slider hover string ea_name = "Expert Advisor Setup Wizard"; //--- Set EA name const int MAX_LINES = 100; //--- Set maximum text lines //+------------------------------------------------------------------+ //| Enum for scrollbar mode | //+------------------------------------------------------------------+ enum ENUM_SCROLLBAR_MODE { // Define scrollbar visibility modes SCROLL_ALWAYS, // Show scrollbar if needed SCROLL_ON_HOVER, // Show scrollbar on hover if needed SCROLL_NEVER // Never show scrollbar, wheel only }; ENUM_SCROLLBAR_MODE ScrollbarMode = SCROLL_ALWAYS; // Scrollbar shows when needed and remains //+------------------------------------------------------------------+ //| Scrollbar object names | //+------------------------------------------------------------------+ #define SCROLL_LEADER "Setup_Scroll_Leader" //--- Define scroll leader name #define SCROLL_UP_REC "Setup_Scroll_Up_Rec" //--- Define scroll up rectangle name #define SCROLL_UP_LABEL "Setup_Scroll_Up_Label" //--- Define scroll up label name #define SCROLL_DOWN_REC "Setup_Scroll_Down_Rec" //--- Define scroll down rectangle name #define SCROLL_DOWN_LABEL "Setup_Scroll_Down_Label" //--- Define scroll down label name #define SCROLL_SLIDER "Setup_Scroll_Slider" //--- Define scroll slider name //+------------------------------------------------------------------+ //| Dashboard object names | //+------------------------------------------------------------------+ #define SETUP_MAIN "Setup_MainContainer" //--- Define main container name #define SETUP_HEADER_BG "Setup_HeaderBg" //--- Define header background name #define SETUP_HEADER_IMAGE "Setup_HeaderImage" //--- Define header image name #define SETUP_HEADER_TITLE "Setup_HeaderTitle" //--- Define header title name #define SETUP_HEADER_SUBTITLE "Setup_HeaderSubtitle" //--- Define header subtitle name #define SETUP_HEADER_CANCEL "Setup_HeaderCancel" //--- Define header cancel button name #define SETUP_BODY_BG "Setup_BodyBg" //--- Define body background name #define SETUP_FOOTER_BG "Setup_FooterBg" //--- Define footer background name #define SETUP_CHECKBOX_BG "Setup_CheckboxBg" //--- Define checkbox background name #define SETUP_CHECKBOX_LABEL "Setup_CheckboxLabel" //--- Define checkbox label name #define SETUP_CHECKBOX_TEXT "Setup_CheckboxText" //--- Define checkbox text name #define SETUP_OK_BUTTON "Setup_OkButton" //--- Define OK button name #define SETUP_CANCEL_BUTTON "Setup_CancelButton" //--- Define Cancel button name //+------------------------------------------------------------------+ //| Enhanced setup text | //+------------------------------------------------------------------+ string setup_text = //--- Define setup guide text "\nExpert Advisor Initialization Guide\n\n" "Welcome to the Expert Advisor Setup Wizard – Your Gateway to Automated Trading in MetaTrader 5!\n\n" "Unlock the power of algorithmic trading with this comprehensive setup guide. Designed for seamless integration, this wizard ensures your EA is configured optimally for performance, risk management, and reliability across diverse market conditions.\n\n" "Key Features:\n" "- Versatile Configuration: Tailor parameters for lot sizing, magic numbers, stop losses, and take profits to suit your trading style and broker requirements.\n" "- Risk Controls: Implement drawdown limits, position sizing rules, and equity protection mechanisms to safeguard your capital.\n" "- Filter Integration: Apply time-based, spread, and news filters to avoid unfavorable trading environments and enhance entry precision.\n" "- Monitoring Tools: Access real-time panels for trade tracking, performance metrics, and alert notifications.\n" "- Backtesting Support: Optimize settings with historical data, ensuring robust strategies before live deployment.\n" "- Broker Adaptability: Supports netting and hedging modes, with customizable slippage and execution tolerances.\n\n" "Initial Setup Instructions:\n" "1. Attach the EA to a new chart of your selected symbol (e.g., EURUSD) on an appropriate timeframe (e.g., M15 for intraday strategies).\n" "2. Adjust core inputs: Define risk parameters, enable/disable filters, and set notification preferences to align with your objectives.\n" "3. Activate AutoTrading: Ensure MT5's AutoTrading is enabled, and verify EA permissions for secure operation.\n" "4. Customize Interfaces: Toggle visibility of info panels, trade managers, and alerts for an intuitive user experience.\n" "5. Validate Setup: Run a forward test on demo to confirm functionality and fine-tune based on observed behavior.\n\n" "Important Notes:\n" "- Risk Disclaimer: Automated trading carries inherent risks. Always use appropriate leverage and start with conservative settings on a demo account.\n" "- Compatibility Check: Confirm broker supports required features like hedging; monitor spreads during volatile periods.\n" "- Optimization Tips: Regularly review performance logs and adjust filters to adapt to evolving market dynamics.\n" "- Security Measures: Use unique magic numbers and enable two-factor authentication for account protection.\n" "- Legal Notice: No guarantees of profitability. Trade responsibly and consult professionals as needed.\n\n" "Contact Methods:\n" "NB:\n" "********************************************\n" " >*** FOR SUPPORT, QUERIES, OR CUSTOMIZATIONS, REACH OUT IMMEDIATELY: ***<\n" " __________________________________________\n\n" " 1. Email: mutiiriallan.forex@gmail.com (Primary Support Channel)\n" " 2. Telegram Channel: @ForexAlgo-Trader (Updates & Community)\n" " 3. Telegram Group: https://t.me/Forex_Algo_Trader (Direct Assistance & Discussions)\n\n" "********************************************\n\n" "Thank you for choosing our Expert Advisor solutions. Configure wisely, trade confidently, and elevate your trading journey! 🚀\n";
Чтобы настроить основу мастера, мы используем глобальные переменные для управления структурой панели. Задаем координаты ("g_mainX", "g_mainY" - на 50), размеры ("g_mainWidth" - на 500, "g_headerHeight" - на 50, "g_footerHeight" - на 40) и отступы ("g_padding", "g_textPadding" - на 10). Также определяем интервал ("g_spacing" - на 0, "g_lineSpacing" - на 3), ограничения по высоте тела ("g_minBodyHeight" - на 200, "g_maxBodyHeight" - на 400) и припуск ("g_bottomMargin" - на 50). Для прокрутки устанавливаем такие переменные, как "scroll_visible", "scroll_pos" и "slider_height", равными 20. Состояния взаимодействия с мышью включают в себя "movingStateSlider" и "mlbDownX_Slider". Добавляем флажки при наведении курсора мыши на кнопки и флажки-чекбоксы. Устанавливаем "ea_name" в качестве "Expert Advisor Setup Wizard" (Мастера настройки советника), а "MAX_LINES" - на 100.
Перечисление "ENUM_SCROLLBAR_MODE" определяет поведение полосы прокрутки ("SCROLL_ALWAYS", "SCROLL_ON_HOVER", "SCROLL_NEVER"), значение по умолчанию "SCROLL_ALWAYS". Определяем константы для имен объектов, таких как "SETUP_MAIN", "SETUP_HEADER_BG", "SCROLL_LEADER" и других, для согласованного именования элементов панели и полосы прокрутки. Наконец, создаем строку "setup_text", полное руководство с разделами, посвященными функциям, инструкциям по настройке, заметкам и методам связи, в формате заголовков и пронумерованных шагов, создавая систему организации интерфейса мастера и содержимого для взаимодействия с пользователем. Вы можете изменить расположение или контент; мы просто использовали произвольные значения. Следующее, что нам нужно будет настроить, - это изображение, которое будет использоваться в качестве значка заголовка. Вы можете пропустить этот шаг, если не хотите его выполнять. Нам нужно будет преобразовать наш графический файл в растровый (BMP) файл. После преобразования изображение должно обладать следующими свойствами.

Судя по изображению, вы видите, что наше изображение представляет собой растровый файл. Не нужно беспокоиться о размере или габаритах, так как позже, при необходимости, мы сможем масштабировать его в любом нужном направлении. Не забудьте поместить файл в ту же папку, что и файл программы. Что нам теперь нужно сделать, так это добавить файл в качестве ресурса в программу.
#resource "1. Forex Algo-Trader SQ.bmp" #define resourceImg "::1. Forex Algo-Trader SQ.bmp"
Мы используем директиву "#resource", чтобы включить файл изображения с именем "1. Forex Algo-Trader SQ.bmp" и определить константу "resourceImg" как "::1. Forex Algo-Trader SQ.bmp" для ссылки на изображение в программе. Это обеспечит панели мастера профессиональный и фирменный пользовательский интерфейс. Теперь мы приступим к созданию интерфейса, и нам понадобятся некоторые вспомогательные функции. Давайте определим функции для создания необходимых прямоугольных меток, текста, изображений и кнопок.
//+------------------------------------------------------------------+ //| Create rectangle label | //+------------------------------------------------------------------+ bool createRecLabel(string objName, int xD, int yD, int xS, int yS, color clrBg, int widthBorder, color clrBorder = clrNONE, ENUM_BORDER_TYPE borderType = BORDER_FLAT, ENUM_LINE_STYLE borderStyle = STYLE_SOLID, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) { ResetLastError(); //--- Reset error code if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { //--- Create rectangle label Print(__FUNCTION__, ": failed to create rec label! Error code = ", GetLastError()); //--- Log failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set width ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set height ObjectSetInteger(0, objName, OBJPROP_CORNER, corner); //--- Set corner ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_TYPE, borderType); //--- Set border type ObjectSetInteger(0, objName, OBJPROP_STYLE, borderStyle); //--- Set border style ObjectSetInteger(0, objName, OBJPROP_WIDTH, widthBorder); //--- Set border width ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set to foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Disable state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectability ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selection return true; //--- Return success } //+------------------------------------------------------------------+ //| Create button | //+------------------------------------------------------------------+ bool createButton(string objName, int xD, int yD, int xS, int yS, string txt = "", color clrTxt = clrBlack, int fontSize = 12, color clrBg = clrNONE, color clrBorder = clrNONE, string font = "Arial", ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, bool isBack = false) { ResetLastError(); //--- Reset error code if (!ObjectCreate(0, objName, OBJ_BUTTON, 0, 0, 0)) { //--- Create button Print(__FUNCTION__, ": failed to create the button! Error code = ", GetLastError()); //--- Log failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set width ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set height ObjectSetInteger(0, objName, OBJPROP_CORNER, corner); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set button text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set text color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrBg); //--- Set background color ObjectSetInteger(0, objName, OBJPROP_BORDER_COLOR, clrBorder); //--- Set border color ObjectSetInteger(0, objName, OBJPROP_BACK, isBack); //--- Set background/foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Disable state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectability ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selection return true; //--- Return success } //+------------------------------------------------------------------+ //| Create text label | //+------------------------------------------------------------------+ bool createLabel(string objName, int xD, int yD, string txt, color clrTxt = clrBlack, int fontSize = 12, string font = "Arial", ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER, ENUM_ANCHOR_POINT anchor = ANCHOR_LEFT_UPPER) { ResetLastError(); //--- Reset error code if (!ObjectCreate(0, objName, OBJ_LABEL, 0, 0, 0)) { //--- Create label Print(__FUNCTION__, ": failed to create the label! Error code = ", GetLastError()); //--- Log failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y distance ObjectSetInteger(0, objName, OBJPROP_CORNER, corner); //--- Set corner ObjectSetString(0, objName, OBJPROP_TEXT, txt); //--- Set label text ObjectSetInteger(0, objName, OBJPROP_COLOR, clrTxt); //--- Set text color ObjectSetInteger(0, objName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetString(0, objName, OBJPROP_FONT, font); //--- Set font ObjectSetInteger(0, objName, OBJPROP_ANCHOR, anchor); //--- Set anchor ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set to foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Disable state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectability ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selection return true; //--- Return success } //+------------------------------------------------------------------+ //| Create bitmap label | //+------------------------------------------------------------------+ bool createBitmapLabel(string objName, int xD, int yD, int xS, int yS, string bitmapPath, color clr, ENUM_BASE_CORNER corner = CORNER_LEFT_UPPER) { ResetLastError(); //--- Reset error code if (!ObjectCreate(0, objName, OBJ_BITMAP_LABEL, 0, 0, 0)) { //--- Create bitmap label Print(__FUNCTION__, ": failed to create bitmap label! Error code = ", GetLastError()); //--- Log failure return false; //--- Return failure } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, xD); //--- Set x distance ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, yD); //--- Set y distance ObjectSetInteger(0, objName, OBJPROP_XSIZE, xS); //--- Set width ObjectSetInteger(0, objName, OBJPROP_YSIZE, yS); //--- Set height ObjectSetInteger(0, objName, OBJPROP_CORNER, corner); //--- Set corner ObjectSetString(0, objName, OBJPROP_BMPFILE, bitmapPath); //--- Set bitmap path ObjectSetInteger(0, objName, OBJPROP_COLOR, clr); //--- Set color ObjectSetInteger(0, objName, OBJPROP_BACK, false); //--- Set to foreground ObjectSetInteger(0, objName, OBJPROP_STATE, false); //--- Disable state ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false); //--- Disable selectability ObjectSetInteger(0, objName, OBJPROP_SELECTED, false); //--- Disable selection return true; //--- Return success }
Здесь мы реализуем основные графические компоненты для интерактивной панели. Сначала разрабатываем функцию "createRecLabel", создающую прямоугольную метку (OBJ_RECTANGLE_LABEL) с заданными координатами, размером, цветом фона, шириной границы, типом (BORDER_FLAT), стилем (STYLE_SOLID) и углом (CORNER_LEFT_UPPER), используя функции ObjectCreate и ObjectSetInteger, выводя сообщения об ошибках с помощью Print в случае сбоя при создании, и устанавливая её на невыбираемый передний план. Затем реализуем функцию "createButton", которая создает кнопку (OBJ_BUTTON) с текстом, цветом, размером шрифта, шрифтом (по умолчанию "Arial"), фоном, рамкой и углом, используя тот же формат.
Далее создаем функцию "createLabel", генерирующую текстовую метку (OBJ_LABEL) с текстом, цветом, размером шрифта, шрифтом, углом и привязкой (ANCHOR_LEFT_UPPER), используя аналогичные вызовы для создания объектов и настройки свойств, при необходимости выводя сообщения об ошибках. Наконец, создаем функцию "createBitmapLabel", создающую графическую метку (OBJ_BITMAP_LABEL) для изображений с координатами, размером, путем к растровому изображению, цветом и углом, используя "ObjectCreate" и устанавливая свойства для обеспечения невыбираемого отображения на переднем плане, выводя сообщения о любых сбоях. Это позволит нам создать систему для отображения визуальных элементов мастера, таких как контейнеры, кнопки, текст и изображения.
Можно приступить к созданию некоторых служебных функций, которые помогут нам рассчитать высоту панели, поскольку нам надо динамически центрировать текст, динамически подбирать шрифты текста в соответствии с разрешением экрана, чтобы некоторые тексты не казались слишком маленькими или слишком большими на разных устройствах, а также обрезать текст, если он слишком длинный, чтобы исключить потенциальное переполнение.
//+------------------------------------------------------------------+ //| Calculate font size based on screen DPI | //+------------------------------------------------------------------+ int getFontSizeByDPI(int baseFontSize, int baseDPI = 96) { int currentDPI = (int)TerminalInfoInteger(TERMINAL_SCREEN_DPI); //--- Retrieve current screen DPI int scaledFontSize = (int)(baseFontSize * (double)baseDPI / currentDPI); //--- Calculate scaled font size return scaledFontSize; //--- Return scaled font size } //+------------------------------------------------------------------+ //| Calculate dashboard dimensions | //+------------------------------------------------------------------+ void CalculateDashboardDimensions() { long chart_width = ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); //--- Get chart width long chart_height = ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); //--- Get chart height g_mainX = (int)((chart_width - g_mainWidth) / 2); //--- Center main container horizontally int available_height = (int)(chart_height - g_mainY - g_bottomMargin - g_headerHeight - g_footerHeight - 2 * g_spacing); //--- Calculate available height g_displayHeight = MathMin(g_maxBodyHeight, MathMax(g_minBodyHeight, available_height)); //--- Set display height g_mainHeight = g_headerHeight + g_displayHeight + g_footerHeight + 2 * g_spacing; //--- Calculate main container height } //+------------------------------------------------------------------+ //| Truncate string | //+------------------------------------------------------------------+ string truncateString(string valueStr, int startPos, int lengthStr = -1, int threshHold = 0, bool isEllipsis = false) { string result = valueStr; //--- Initialize result if (StringLen(valueStr) > threshHold && threshHold > 0) { //--- Check if truncation needed result = StringSubstr(valueStr, startPos, lengthStr); //--- Extract substring if (isEllipsis) result += "..."; //--- Add ellipsis if needed } return result; //--- Return truncated string }
Здесь мы реализуем служебные функции для обеспечения адаптивного изменения размера и форматирования текста. Разрабатываем функцию "getFontSizeByDPI", которая извлекает значение DPI экрана (точек на дюйм), используя TerminalInfoInteger с помощью TERMINAL_SCREEN_DPI, рассчитывает масштабированный размер шрифта, корректируя базовый размер шрифта относительно стандартного значения DPI (96), используя простое соотношение, и возвращает результат для согласованного отображения текста на разных устройствах.
Затем создаем функцию "CalculateDashboardDimensions", которая получает ширину и высоту графика через ChartGetInteger с помощью CHART_WIDTH_IN_PIXELS и "CHART_HEIGHT_IN_PIXELS", центрирует основной контейнер по горизонтали, устанавливая значение "g_mainX" равным половине разницы между шириной графика и "g_mainWidth", вычисляет доступную высоту, вычитая "g_mainY", "g_bottomMargin", "g_headerHeight", "g_footerHeight" и дважды "g_spacing" от высоты графика, устанавливает "g_displayHeight" в пределах границ "g_minBodyHeight" и "g_maxBodyHeight", используя MathMin и MathMax, и вычисляет "g_mainHeight" как сумму высот заголовка, тела и нижнего колонтитула, а также интервала.
Наконец, реализуем функцию "truncateString", которая возвращает строку ввода без изменений, если ее длина меньше порогового значения или равна нулю, в противном случае извлекает подстроку с помощью StringSubstr из "startPos" для символов "lengthStr", добавляя многоточие, если указано, для управления переполнением текста. Используя эти функции, можно начать реализацию с создания главной панели. Для обеспечения модульности мы разместим её логику в отдельной функции.
//+------------------------------------------------------------------+ //| Show the setup dashboard | //+------------------------------------------------------------------+ void ShowDashboard() { checkbox_checked = false; //--- Reset checkbox state createRecLabel(SETUP_MAIN, g_mainX, g_mainY, g_mainWidth, g_mainHeight, C'20,20,20', 1, C'40,40,40', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create main container createRecLabel(SETUP_HEADER_BG, g_mainX, g_mainY, g_mainWidth, g_headerHeight, C'45,45,45', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create header background }
Реализуем функцию "ShowDashboard", и сначала устанавливаем состояние флажка в значение false, обеспечивая тем самым чистый старт для взаимодействия с пользователем. Затем вызываем функцию "createRecLabel", чтобы нарисовать основной контейнер ("SETUP_MAIN") в координатах "g_mainX" и "g_mainY" с размерами "g_mainWidth" и "g_mainHeight", используя темно-серый фон (C'20,20,20'), границу в 1 пиксель (C'40,40,40'), плоский тип рамки, сплошной стиль и выравнивание по верхнему левому углу.
Затем используем "createRecLabel" для создания фона заголовка ("SETUP_HEADER_BG") с той же координатой x и "g_mainY", охватывающей "g_mainWidth" и "g_headerHeight", с немного более светлым серым фоном (C'45,45,45') и рамкой (C'60,60,60'), сохраняя согласованность стиля, отрисовывая базовую визуальную структуру панели мастера. Теперь нам надо вызвать эту функцию в обработчике OnInit
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { int build = (int)TerminalInfoInteger(TERMINAL_BUILD); //--- Get terminal build number string os = TerminalInfoString(TERMINAL_OS_VERSION); //--- Get operating system version StringReplace(os, " ", "_"); //--- Replace spaces with underscores StringReplace(os, ".", "_"); //--- Replace dots with underscores StringReplace(os, ",", "_"); //--- Replace commas with underscores GV_SETUP = "EA_Setup_" + IntegerToString(build) + "_" + os; //--- Set global variable name CalculateDashboardDimensions(); //--- Calculate dashboard dimensions if (!GlobalVariableCheck(GV_SETUP)) { //--- Check if global variable exists Print("Global variable '" + GV_SETUP + "' not found. Creating new one with value FALSE (0.0)."); //--- Log variable creation GlobalVariableSet(GV_SETUP, 0.0); //--- Set variable to false ShowDashboard(); //--- Display dashboard } else { //--- Variable exists double val = GlobalVariableGet(GV_SETUP); //--- Get variable value if (val == 1.0) { //--- Check if set to never show // No dashboard } else { //--- Show dashboard ShowDashboard(); //--- Display dashboard } } ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //--- Enable mouse move events ChartSetInteger(0, CHART_EVENT_MOUSE_WHEEL, true); //--- Enable mouse wheel events ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Enable chart scrolling return(INIT_SUCCEEDED); //--- Return initialization success }
Переходим к реализации логики инициализации для управления поведением отображения в функции OnInit, где получаем номер сборки терминала MetaTrader 5, используя TerminalInfoInteger с помощью TERMINAL_BUILD и версию операционной системы, используя TerminalInfoString с параметром TERMINAL_OS_VERSION, заменяя пробелы, точки и запятые в строке ОС на подчеркивания с помощью StringReplace, чтобы создать чистое имя глобальной переменной ("GV_SETUP"), отформатированное как "EA_Setup_<build>_<OS>". Можно использовать любое другое имя, например, магическое число или имя программы и номер версии, но эта комбинация показалась гениальной и уникальной. Вызываем "CalculateDashboardDimensions" для настройки структуры панели в соответствии с размерами графика.
Затем проверяем, существует ли глобальная переменная, с помощью GlobalVariableCheck; если нет, выводим сообщение о ее создании с помощью "Print", устанавливаем ее значение равным 0.0 (false) с помощью GlobalVariableSet и отображаем панель с помощью "ShowDashboard". Если переменная существует, получаем её значение с помощью GlobalVariableGet, отображая панель только в том случае, если значение не равно 1,0 (что указывает на то, что пользователь решил не показывать её повторно). Наконец, включаем события перемещения мыши и прокрутки колесика мыши с помощью ChartSetInteger, используя CHART_EVENT_MOUSE_MOVE, "CHART_EVENT_MOUSE_WHEEL" и "CHART_MOUSE_SCROLL" для поддержки интерактивных функций, что будет полезно в обработчике OnChartEvent, и возвращаем INIT_SUCCEEDED для успешной инициализации. После компиляции получаем следующий результат.

Поскольку теперь у нас есть раздел заголовка, давайте добавим в него файл изображения. Нам нужно будет масштабировать изображение, поэтому давайте определим функции, которые будут выполнять тяжелую работу, а затем сможем вызвать их для масштабирования нашего файла изображения.
//+------------------------------------------------------------------+ //| Scale image using bicubic interpolation | //+------------------------------------------------------------------+ void ScaleImage(uint &pixels[], int original_width, int original_height, int new_width, int new_height) { uint scaled_pixels[]; //--- Declare scaled pixel array ArrayResize(scaled_pixels, new_width * new_height); //--- Resize scaled pixel array for (int y = 0; y < new_height; y++) { //--- Iterate through new height for (int x = 0; x < new_width; x++) { //--- Iterate through new width double original_x = (double)x * original_width / new_width; //--- Calculate original x double original_y = (double)y * original_height / new_height; //--- Calculate original y uint pixel = BicubicInterpolate(pixels, original_width, original_height, original_x, original_y); //--- Interpolate pixel scaled_pixels[y * new_width + x] = pixel; //--- Store scaled pixel } } ArrayResize(pixels, new_width * new_height); //--- Resize original pixel array ArrayCopy(pixels, scaled_pixels); //--- Copy scaled pixels } //+------------------------------------------------------------------+ //| Perform bicubic interpolation for a single pixel | //+------------------------------------------------------------------+ uint BicubicInterpolate(uint &pixels[], int width, int height, double x, double y) { int x0 = (int)x; //--- Get integer x coordinate int y0 = (int)y; //--- Get integer y coordinate double fractional_x = x - x0; //--- Calculate fractional x double fractional_y = y - y0; //--- Calculate fractional y int x_indices[4], y_indices[4]; //--- Declare index arrays for (int i = -1; i <= 2; i++) { //--- Calculate indices x_indices[i + 1] = MathMin(MathMax(x0 + i, 0), width - 1); //--- Clamp x indices y_indices[i + 1] = MathMin(MathMax(y0 + i, 0), height - 1); //--- Clamp y indices } uint neighborhood_pixels[16]; //--- Declare neighborhood pixels for (int j = 0; j < 4; j++) { //--- Iterate y indices for (int i = 0; i < 4; i++) { //--- Iterate x indices neighborhood_pixels[j * 4 + i] = pixels[y_indices[j] * width + x_indices[i]]; //--- Store pixel } } uchar alpha_components[16], red_components[16], green_components[16], blue_components[16]; //--- Declare color components for (int i = 0; i < 16; i++) { //--- Extract components GetArgb(neighborhood_pixels[i], alpha_components[i], red_components[i], green_components[i], blue_components[i]); //--- Get ARGB components } uchar alpha_out = (uchar)BicubicInterpolateComponent(alpha_components, fractional_x, fractional_y); //--- Interpolate alpha uchar red_out = (uchar)BicubicInterpolateComponent(red_components, fractional_x, fractional_y); //--- Interpolate red uchar green_out = (uchar)BicubicInterpolateComponent(green_components, fractional_x, fractional_y); //--- Interpolate green uchar blue_out = (uchar)BicubicInterpolateComponent(blue_components, fractional_x, fractional_y); //--- Interpolate blue return (alpha_out << 24) | (red_out << 16) | (green_out << 8) | blue_out; //--- Combine components } //+------------------------------------------------------------------+ //| Perform bicubic interpolation for a single color component | //+------------------------------------------------------------------+ double BicubicInterpolateComponent(uchar &components[], double fractional_x, double fractional_y) { double weights_x[4]; //--- Declare x weights double t = fractional_x; //--- Set x fraction weights_x[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate x weight 0 weights_x[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate x weight 1 weights_x[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate x weight 2 weights_x[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate x weight 3 double y_values[4]; //--- Declare y values for (int j = 0; j < 4; j++) { //--- Iterate y indices y_values[j] = weights_x[0] * components[j * 4 + 0] + weights_x[1] * components[j * 4 + 1] + weights_x[2] * components[j * 4 + 2] + weights_x[3] * components[j * 4 + 3]; //--- Calculate y value } double weights_y[4]; //--- Declare y weights t = fractional_y; //--- Set y fraction weights_y[0] = (-0.5 * t * t * t + t * t - 0.5 * t); //--- Calculate y weight 0 weights_y[1] = (1.5 * t * t * t - 2.5 * t * t + 1); //--- Calculate y weight 1 weights_y[2] = (-1.5 * t * t * t + 2 * t * t + 0.5 * t); //--- Calculate y weight 2 weights_y[3] = (0.5 * t * t * t - 0.5 * t * t); //--- Calculate y weight 3 double result = weights_y[0] * y_values[0] + weights_y[1] * y_values[1] + weights_y[2] * y_values[2] + weights_y[3] * y_values[3]; //--- Calculate final value return MathMax(0, MathMin(255, result)); //--- Clamp result to valid range } //+------------------------------------------------------------------+ //| Extract ARGB components from a pixel | //+------------------------------------------------------------------+ void GetArgb(uint pixel, uchar &alpha, uchar &red, uchar &green, uchar &blue) { alpha = (uchar)((pixel >> 24) & 0xFF); //--- Extract alpha component red = (uchar)((pixel >> 16) & 0xFF); //--- Extract red component green = (uchar)((pixel >> 8) & 0xFF); //--- Extract green component blue = (uchar)(pixel & 0xFF); //--- Extract blue component }
Здесь мы реализуем функции обработки изображений для повышения визуального качества. Сначала создаем функцию "ScaleImage", которая изменяет размер изображения, создавая новый массив пикселей с помощью ArrayResize для целевых размеров ("new_width" x "new_height"), выполняя перебор каждого пикселя, сопоставляя координаты с исходным изображением с помощью пропорционального масштабирования и вызывая "BicubicInterpolate" для вычисления интерполированного значения пикселя, сохраняя его в масштабированном массиве, а затем копируя обратно в исходный массив с помощью ArrayCopy.
Затем создаём функцию "BicubicInterpolate", которая вычисляет цвет пикселя в нецелочисленных координатах ("x", "y"), выбирая окрестность пикселей размером 4x4, ограничивая индексы с помощью MathMin и MathMax, чтобы оставаться в пределах рамки изображения, извлекая компоненты ARGB с помощью "GetArgb", интерполируя каждый компонент с помощью "BicubicInterpolateComponent" и объединяя их в итоговое значение пикселя с помощью побитовых операций. Далее реализуем функцию "BicubicInterpolateComponent", применяющую бикубическую интерполяцию к одному цветовому компоненту, вычисляя кубические веса для дробных координат x и y, вычисляя промежуточные y-значения из сетки 4x4 и комбинируя их с весами y, ограничивая результат значениями от 0 до 255. Наконец, функция "GetArgb" извлекает альфа-, красный, зеленый и синий компоненты из пикселя с помощью побитовых сдвигов и масок. Это позволит адаптировать подход плавного масштабирования наших изображений к приборной панели или выделенной области. Теперь мы можем вызвать эту функцию, чтобы масштабировать исходное изображение и отобразить его.
uint img_pixels[]; //--- Declare pixel array for image uint orig_width = 0, orig_height = 0; //--- Initialize image dimensions bool image_loaded = ResourceReadImage(resourceImg, img_pixels, orig_width, orig_height); //--- Load image resource if (image_loaded && orig_width > 0 && orig_height > 0) { //--- Check image load success ScaleImage(img_pixels, (int)orig_width, (int)orig_height, 40, 40); //--- Scale image to 40x40 g_scaled_image_resource = "::SetupHeaderImageScaled"; //--- Set scaled image resource name if (ResourceCreate(g_scaled_image_resource, img_pixels, 40, 40, 0, 0, 40, COLOR_FORMAT_ARGB_NORMALIZE)) { //--- Create scaled resource createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, g_scaled_image_resource, clrWhite, CORNER_LEFT_UPPER); //--- Create scaled image label } else { //--- Handle resource creation failure Print("Failed to create scaled image resource"); //--- Log failure createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, resourceImg, clrWhite, CORNER_LEFT_UPPER); //--- Use original image } } else { //--- Handle image load failure Print("Failed to load original image resource"); //--- Log failure createBitmapLabel(SETUP_HEADER_IMAGE, g_mainX + 5, g_mainY + (g_headerHeight - 40)/2, 40, 40, resourceImg, clrWhite, CORNER_LEFT_UPPER); //--- Use original image }
Чтобы реализовать логику загрузки и масштабирования изображения, объявляем массив пикселей "img_pixels" и инициализируем размеры "orig_width" и "orig_height" равными нулю, затем загружаем исходное изображение с помощью "ResourceReadImage", используя "resourceImg", проверяя, успешно ли это действие ("image_loaded" и размеры > 0); если значение true, вызываем "ScaleImage" для изменения размера до 40x40 пикселей, который вы можете изменить, устанавливаем для "g_scaled_image_resource" значение "::SetupHeaderImageScaled" и создаем новый ресурс с помощью ResourceCreate в формате ARGB, а затем "createBitmapLabel", чтобы отобразить масштабированное изображение в позиции заголовка белым цветом. Если создание ресурса завершается неудачей, выводим сообщение об экземпляре и возвращаемся к исходному изображению; если загрузка завершается неудачей, выводим сообщениев лог и используем исходный "resourceImg" напрямую с помощью функции "createBitmapLabel". После компиляции получаем следующий результат.

Теперь, когда у нас готов файл изображения, переходим к реализации других основных элементов следующим образом.
string truncated_name = truncateString(ea_name, 0, -1, 20, true); //--- Truncate EA name int titleFontSize = getFontSizeByDPI(14); //--- Calculate title font size createLabel(SETUP_HEADER_TITLE, g_mainX + 5 + 40 + 5, g_mainY + 5, truncated_name, clrWhite, titleFontSize, "Arial Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create title label int subtitleFontSize = getFontSizeByDPI(10); //--- Calculate subtitle font size createLabel(SETUP_HEADER_SUBTITLE, g_mainX + 5 + 40 + 5, g_mainY + 25, "Streamlined configuration for optimal performance", C'200,200,200', subtitleFontSize, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create subtitle label int headerCancelFontSize = getFontSizeByDPI(16); //--- Calculate cancel button font size createLabel(SETUP_HEADER_CANCEL, g_mainX + g_mainWidth - 25, g_mainY + 10, ShortToString(0x274C), C'150,150,150', headerCancelFontSize, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create header cancel button int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position createRecLabel(SETUP_BODY_BG, g_mainX, bodyY, g_mainWidth, g_displayHeight, C'25,25,25', 1, C'40,40,40', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create body background int footerY = bodyY + g_displayHeight + g_spacing; //--- Calculate footer y position createRecLabel(SETUP_FOOTER_BG, g_mainX, footerY, g_mainWidth, g_footerHeight, C'35,35,35', 1, C'50,50,50', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create footer background createRecLabel(SETUP_CHECKBOX_BG, g_mainX + 10, footerY + (g_footerHeight - 20)/2, 20, 20, C'60,60,60', 1, C'80,80,80', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create checkbox background int checkboxLabelFontSize = getFontSizeByDPI(17); //--- Calculate checkbox label font size createLabel(SETUP_CHECKBOX_LABEL, g_mainX + 10 + 2, footerY + (g_footerHeight - 20)/2, " ", clrWhite, checkboxLabelFontSize, "Wingdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create checkbox label int checkboxTextFontSize = getFontSizeByDPI(10); //--- Calculate checkbox text font size createLabel(SETUP_CHECKBOX_TEXT, g_mainX + 40, footerY + (g_footerHeight - 20)/2 + 2, "Do not show this guide again", clrWhite, checkboxTextFontSize, "Calibri Bold", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create checkbox text int buttonFontSize = getFontSizeByDPI(12); //--- Calculate button font size color buttonBg = C'60,60,60'; //--- Set button background color color buttonBorder = C'80,80,80'; //--- Set button border color createButton(SETUP_OK_BUTTON, g_mainX + g_mainWidth - 170 - 10, footerY + 5, 80, 30, "OK", clrWhite, buttonFontSize, buttonBg, buttonBorder, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, false); //--- Create OK button createButton(SETUP_CANCEL_BUTTON, g_mainX + g_mainWidth - 80 - 10, footerY + 5, 80, 30, "Cancel", clrWhite, buttonFontSize, buttonBg, buttonBorder, "Arial Rounded MT Bold", CORNER_LEFT_UPPER, false); //--- Create Cancel button int textFontSize = getFontSizeByDPI(10); //--- Calculate text font size for (int i = 0; i < MAX_LINES; i++) { //--- Create text line labels string lineName = "Setup_ResponseLine_" + IntegerToString(i); //--- Generate line name createLabel(lineName, 0, -100, " ", clrWhite, textFontSize, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create line label }
Здесь мы реализуем остальные компоненты панели, чтобы мастер завершил работу с пользовательским интерфейсом. Для улучшения читаемости сокращаем название программы до 20 символов с помощью функции "truncateString" и создаём метку заголовка ("SETUP_HEADER_TITLE") с помощью функции "createLabel" в вычислённых координатах, используя размер шрифта, скорректированный по DPI, из функции "getFontSizeByDPI" (основа - 14) и шрифт "Arial Bold". Далее добавляем метку подзаголовка ("SETUP_HEADER_SUBTITLE") с фиксированным текстом и меньшим размером шрифта, скорректированным по DPI (основа - 10), а затем кнопку отмены в заголовке ("SETUP_HEADER_CANCEL"), используя символ крестика Unicode (0x274C) шрифтом "Arial Rounded MT Bold". Вот описание.

Вычисляем Y-координату тела ("bodyY") и создаем его фон ("SETUP_BODY_BG") с помощью функции "createRecLabel", используя темно-серый фон (C'25,25,25') и рамку, охватывающие "g_mainWidth" и "g_displayHeight". Затем вычисляем Y-координату нижней панели ("footerY") и создаем для него фон ("SETUP_FOOTER_BG") светло-серого цвета, за которым следует фон флажка ("SETUP_CHECKBOX_BG") в виде квадрата 20x20, метка флажка ("SETUP_CHECKBOX_LABEL") с пустым символом Wingdings и текст ("SETUP_CHECKBOX_TEXT"), гласящий "Не показывать это руководство снова" шрифтом Calibri Bold. Добавляем кнопки «ОК» и «Отмена» ("SETUP_OK_BUTTON", "SETUP_CANCEL_BUTTON") с помощью функции "createButton" с размером шрифта, скорректированным по DPI (основа - 12), используя совместимые оттенки серого. Наконец, создаём в цикле в количестве до "MAX_LINES" текстовые метки ("Setup_ResponseLine_") с помощью функции "createLabel", изначально скрытых за пределами экрана, для динамического отображения текста. Это создает систему для отрисовки интерактивной и визуально целостной панели мастера. В результате выполнения программы мы получаем следующее.

Поскольку теперь у нас есть панель с основными элементами, нам нужно обновить дисплей, чтобы на нем отображались назначенные мастером метки. Поскольку создание многострочных текстовых меток в MQL5 не так просто, поскольку прямого способа достижения этой цели нет, нам потребуется использовать подход переноса текста.
//+------------------------------------------------------------------+ //| Get line color based on content | //+------------------------------------------------------------------+ color GetLineColor(string lineText) { if (StringLen(lineText) == 0 || lineText == " ") return C'25,25,25'; //--- Set invisible for empty lines if (StringFind(lineText, "mutiiriallan.forex@gmail.com") >= 0) return C'255,100,100'; //--- Set light red for email if (StringFind(lineText, "https://t.me/Forex_Algo_Trader") >= 0) return C'150,100,200'; //--- Set light purple for group link if (StringFind(lineText, "@ForexAlgo-Trader") >= 0) return C'100,150,255'; //--- Set light blue for channel link if (StringFind(lineText, "http") >= 0 || StringFind(lineText, "t.me") >= 0) return C'100,150,255'; //--- Set light blue for general links string start3 = StringSubstr(lineText, 0, 3); //--- Get first three characters if ((start3 == "1. " || start3 == "2. " || start3 == "3. " || start3 == "4. " || start3 == "5. ") && StringFind(lineText, "Initial Setup Instructions") < 0) { //--- Check instruction lines return C'255,200,100'; //--- Set light yellow for instructions } return clrWhite; //--- Default to white } //+------------------------------------------------------------------+ //| Wrap text with colors | //+------------------------------------------------------------------+ void WrapText(const string inputText, const string font, const int fontSize, const int maxWidth, string &wrappedLines[], color &wrappedColors[], int offset = 0) { const int maxChars = 60; //--- Set maximum characters per line ArrayResize(wrappedLines, 0); //--- Clear wrapped lines array ArrayResize(wrappedColors, 0); //--- Clear wrapped colors array TextSetFont(font, fontSize); //--- Set font string paragraphs[]; //--- Declare paragraphs array int numParagraphs = StringSplit(inputText, '\n', paragraphs); //--- Split text into paragraphs for (int p = 0; p < numParagraphs; p++) { //--- Iterate through paragraphs string para = paragraphs[p]; //--- Get current paragraph color paraColor = GetLineColor(para); //--- Get paragraph color if (StringLen(para) == 0) { //--- Check empty paragraph int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = " "; //--- Add empty line ArrayResize(wrappedColors, size + 1); //--- Resize colors array wrappedColors[size] = C'25,25,25'; //--- Set invisible color continue; //--- Skip to next } string words[]; //--- Declare words array int numWords = StringSplit(para, ' ', words); //--- Split paragraph into words string currentLine = ""; //--- Initialize current line for (int w = 0; w < numWords; w++) { //--- Iterate through words string testLine = currentLine + (StringLen(currentLine) > 0 ? " " : "") + words[w]; //--- Build test line uint wid, hei; //--- Declare width and height TextGetSize(testLine, wid, hei); //--- Get test line size int textWidth = (int)wid; //--- Get text width if (textWidth + offset <= maxWidth && StringLen(testLine) <= maxChars) { //--- Check line fits currentLine = testLine; //--- Update current line } else { //--- Line exceeds limits if (StringLen(currentLine) > 0) { //--- Check non-empty line int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = currentLine; //--- Add line ArrayResize(wrappedColors, size + 1); //--- Resize colors array wrappedColors[size] = paraColor; //--- Add color } currentLine = words[w]; //--- Start new line TextGetSize(currentLine, wid, hei); //--- Get new line size textWidth = (int)wid; //--- Update text width if (textWidth + offset > maxWidth || StringLen(currentLine) > maxChars) { //--- Check word too long string wrappedWord = ""; //--- Initialize wrapped word for (int c = 0; c < StringLen(words[w]); c++) { //--- Iterate through characters string testWord = wrappedWord + StringSubstr(words[w], c, 1); //--- Build test word TextGetSize(testWord, wid, hei); //--- Get test word size int wordWidth = (int)wid; //--- Get word width if (wordWidth + offset > maxWidth || StringLen(testWord) > maxChars) { //--- Check word fits if (StringLen(wrappedWord) > 0) { //--- Check non-empty word int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = wrappedWord; //--- Add wrapped word ArrayResize(wrappedColors, size + 1); //--- Resize colors array wrappedColors[size] = paraColor; //--- Add color } wrappedWord = StringSubstr(words[w], c, 1); //--- Start new word } else { //--- Word fits wrappedWord = testWord; //--- Update wrapped word } } currentLine = wrappedWord; //--- Set current line to wrapped word if (StringLen(currentLine) > 0) { //--- Check non-empty line int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = currentLine; //--- Add line ArrayResize(wrappedColors, size + 1); //--- Resize colors array wrappedColors[size] = paraColor; //--- Add color } currentLine = ""; //--- Reset current line } } } if (StringLen(currentLine) > 0) { //--- Check remaining line int size = ArraySize(wrappedLines); //--- Get current size ArrayResize(wrappedLines, size + 1); //--- Resize lines array wrappedLines[size] = currentLine; //--- Add line ArrayResize(wrappedColors, size + 1); //--- Resize colors array wrappedColors[size] = paraColor; //--- Add color } } }
Здесь мы применяем логику форматирования текста и цветового кодирования, чтобы улучшить читаемость руководства. В функции "GetLineColor" назначаем цвета в зависимости от содержимого: пустые строки получают невидимый темно-серый цвет (C'25,25,25'), адреса электронной почты — светло-красный (C'255,100,100'), ссылки на группы — светло-фиолетовый (C'150,100,200'), ссылки на авторов и другие унифицированные указатели ресурсов (URL) — светло-голубой (C'100,150,255'), строки инструкций, начинающиеся с "1." до "5." (за исключением заголовка), — светло-желтый (C'255,200,100'), а все остальные по умолчанию — белый. Вы можете задать любой параметр по своему усмотрению; мы включили это только для того, чтобы показать, как можно реализовать расширенное кодирование текста.
В функции "WrapText" разбиваем входной текст на абзацы, используя StringSplit для перевода строк, устанавливаем шрифт с помощью TextSetFont и для каждого абзаца извлекаем его цвет с помощью "GetLineColor", добавляя пустые абзацы в качестве пробелов с невидимым цветом. Разбиваем абзацы на слова с помощью "StringSplit", создавая строки путем добавления слов, если они соответствуют "maxWidth" и ограничению в 60 символов, хотя максимальное ограничение равно 63, используя TextGetSize, в противном случае начинаем новую строку. Для слишком больших слов разбиваем посимвольно, добавляя сегменты к новым строкам если они превышают установленные ограничения, гарантируя, что каждая строка хранится в "wrappedLines", а ее цвет - в "wrappedColors", используя функцию ArrayResize. С помощью этой функции мы можем обновить дисплей. Позаботимся о том, чтобы логика была функцией.
//+------------------------------------------------------------------+ //| Get text height | //+------------------------------------------------------------------+ int TextGetHeight(string text, string font, int fontSize) { uint wid, hei; //--- Declare width and height TextSetFont(font, fontSize); //--- Set font TextGetSize(text, wid, hei); //--- Get text size return (int)hei; //--- Return height } //+------------------------------------------------------------------+ //| Check if line is a heading | //+------------------------------------------------------------------+ bool IsHeading(string lineText) { if (StringLen(lineText) == 0) return false; //--- Return false for empty lines if (StringGetCharacter(lineText, StringLen(lineText) - 1) == ':') return true; //--- Check for colon if (StringFind(lineText, "Expert Advisor Initialization Guide") >= 0) return true; //--- Check main heading if (StringFind(lineText, "Key Features") >= 0) return true; //--- Check features heading if (StringFind(lineText, "Initial Setup Instructions") >= 0) return true; //--- Check instructions heading if (StringFind(lineText, "Important Notes") >= 0) return true; //--- Check notes heading if (StringFind(lineText, "Contact Methods") >= 0) return true; //--- Check contact heading if (StringFind(lineText, "NB:") >= 0) return true; //--- Check NB heading return false; //--- Default to false } //+------------------------------------------------------------------+ //| Update body display with scrollable text | //+------------------------------------------------------------------+ void UpdateBodyDisplay() { int textX = g_mainX + g_padding + g_textPadding; //--- Set text x position int textY = g_mainY + g_headerHeight + g_spacing; //--- Set text y position int fullMaxWidth = g_mainWidth - 2 * g_padding - 2 * g_textPadding; //--- Calculate max text width string font = "Arial"; //--- Set font int fontSize = getFontSizeByDPI(10); //--- Calculate font size int lineHeight = TextGetHeight("A", font, fontSize); //--- Get line height int adjustedLineHeight = lineHeight + g_lineSpacing; //--- Calculate adjusted line height g_adjustedLineHeight = adjustedLineHeight; //--- Store adjusted line height int visibleHeight = g_displayHeight; //--- Set visible height g_visible_height = visibleHeight; //--- Store visible height static string wrappedLines[]; //--- Store wrapped text lines static color wrappedColors[]; //--- Store line colors static bool wrapped = false; //--- Track if text wrapped if (!wrapped) { //--- Check if text needs wrapping WrapText(setup_text, font, fontSize, fullMaxWidth, wrappedLines, wrappedColors); //--- Wrap text wrapped = true; //--- Set wrapped flag } int numLines = ArraySize(wrappedLines); //--- Get number of lines g_total_height = numLines * adjustedLineHeight; //--- Calculate total text height bool need_scroll = g_total_height > visibleHeight; //--- Check if scrollbar needed bool should_show_scrollbar = false; //--- Initialize scrollbar visibility int reserved_width = 0; //--- Initialize reserved width if (need_scroll && ScrollbarMode != SCROLL_NEVER) { //--- Check scrollbar mode should_show_scrollbar = true; //--- Enable scrollbar reserved_width = 16; //--- Reserve scrollbar width } if (reserved_width > 0 && fullMaxWidth - reserved_width != fullMaxWidth) { //--- Check width change WrapText(setup_text, font, fontSize, fullMaxWidth - reserved_width, wrappedLines, wrappedColors); //--- Rewrap text numLines = ArraySize(wrappedLines); //--- Update line count g_total_height = numLines * adjustedLineHeight; //--- Update total height } int startLine = scroll_pos / adjustedLineHeight; //--- Calculate start line int currentY = textY; //--- Set current y position int labelIndex = 0; //--- Initialize label index for (int line = startLine; line < numLines; line++) { //--- Iterate visible lines string lineText = wrappedLines[line]; //--- Get line text if (StringLen(lineText) == 0) lineText = " "; //--- Set empty lines to space color lineColor = wrappedColors[line]; //--- Get line color if (IsHeading(lineText)) lineColor = clrBlue; //--- Set blue for headings if (currentY + adjustedLineHeight > textY + visibleHeight) break; //--- Prevent overflow string lineName = "Setup_ResponseLine_" + IntegerToString(labelIndex); //--- Generate line name if (ObjectFind(0, lineName) >= 0) { //--- Check if label exists ObjectSetString(0, lineName, OBJPROP_TEXT, lineText); //--- Set line text ObjectSetInteger(0, lineName, OBJPROP_XDISTANCE, textX); //--- Set x position ObjectSetInteger(0, lineName, OBJPROP_YDISTANCE, currentY); //--- Set y position ObjectSetInteger(0, lineName, OBJPROP_COLOR, lineColor); //--- Set line color string lineFont = IsHeading(lineText) ? "Arial Bold" : "Arial"; //--- Set font ObjectSetString(0, lineName, OBJPROP_FONT, lineFont); //--- Set font type ObjectSetInteger(0, lineName, OBJPROP_FONTSIZE, fontSize); //--- Set font size ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, false); //--- Show label } currentY += adjustedLineHeight; //--- Increment y position labelIndex++; //--- Increment label index } for (int i = labelIndex; i < MAX_LINES; i++) { //--- Hide unused labels string lineName = "Setup_ResponseLine_" + IntegerToString(i); //--- Generate line name if (ObjectFind(0, lineName) >= 0) { //--- Check if label exists ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, true); //--- Hide label } } ChartRedraw(); //--- Redraw chart }
Чтобы реализовать логику рендеринга текста и прокрутки для динамического отображения руководства, в функции "TextGetHeight" устанавливаем шрифт и размер с помощью TextSetFont и используем TextGetSize для вычисления высоты символа образца, возвращая его для обеспечения единообразного межстрочного интервала. Функция "IsHeading" идентифицирует заголовки, проверяя наличие пустых строк, двоеточий в конце или названий конкретных разделов руководства (например, "Основные функции") и возвращает значение true при совпадении. В функции "UpdateBodyDisplay" рассчитываем положение текстовой области ("textX", "textY") и ширину ("fullMaxWidth"), используя заполнение и размеры контейнера, устанавливаем шрифт Arial с размером, скорректированным по DPI с помощью "getFontSizeByDPI" и вычисляем высоту строки с помощью "TextGetHeight" плюс "g_lineSpacing", сохраняя её в "g_adjustedLineHeight".
Если это еще не сделано, переносим текст руководства с помощью функции "WrapText", вычисляем общую высоту текста ("g_total_height") и определяем видимость полосы прокрутки на основе параметра "ScrollbarMode" и переполнения текста, резервируя при необходимости 16 пикселей для полосы прокрутки и соответствующим образом перенося текст. Вычисляем начальную линию от "scroll_pos", обновляем видимые текстовые метки с помощью ObjectSetString и ObjectSetInteger, определяя их положение, цвет (синий для заголовков с помощью "IsHeading") и шрифт, скрываем неиспользуемые метки и перерисовываем график. При вызове функции внутри функции для отображения панели, получаем следующий результат.

Как видите, у нас готов дисплей, и текст идеально вписывается в отображаемую область. Нам нужно получить логику для отображения полосы прокрутки, когда это необходимо.
//+------------------------------------------------------------------+ //| Create scrollbar | //+------------------------------------------------------------------+ void CreateScrollbar() { int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position int textAreaY = bodyY; //--- Set text area y int textAreaHeight = g_displayHeight; //--- Set text area height int scrollbar_x = g_mainX + g_mainWidth - 16 - 1; //--- Calculate scrollbar x int scrollbar_width = 16; //--- Set scrollbar width int button_size = 16; //--- Set button size int scrollbar_y = textAreaY + 2; //--- Calculate scrollbar y int scrollbar_height = textAreaHeight - 2 - 2; //--- Calculate scrollbar height createRecLabel(SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'45,45,45', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll leader createRecLabel(SCROLL_UP_REC, scrollbar_x, scrollbar_y, scrollbar_width, button_size, C'60,60,60', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll up rectangle int scrollUpLabelFontSize = getFontSizeByDPI(10); //--- Calculate scroll up font size createLabel(SCROLL_UP_LABEL, scrollbar_x + 2, scrollbar_y - 2, CharToString(0x35), C'150,150,150', scrollUpLabelFontSize, "Webdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create scroll up label int down_rec_y = scrollbar_y + scrollbar_height - button_size; //--- Calculate scroll down y createRecLabel(SCROLL_DOWN_REC, scrollbar_x, down_rec_y, scrollbar_width, button_size, C'60,60,60', 1, C'60,60,60', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll down rectangle createLabel(SCROLL_DOWN_LABEL, scrollbar_x + 2, down_rec_y - 2, CharToString(0x36), C'150,150,150', scrollUpLabelFontSize, "Webdings", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); //--- Create scroll down label slider_height = CalculateSliderHeight(); //--- Calculate slider height createRecLabel(SCROLL_SLIDER, scrollbar_x, scrollbar_y + button_size, scrollbar_width, slider_height, C'80,80,80', 1, C'100,100,100', BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); //--- Create scroll slider } //+------------------------------------------------------------------+ //| Delete scrollbar | //+------------------------------------------------------------------+ void DeleteScrollbar() { string scroll_objects[] = {SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL, SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER}; //--- Define scroll objects for (int i = 0; i < ArraySize(scroll_objects); i++) { //--- Iterate through objects ObjectDelete(0, scroll_objects[i]); //--- Delete object } ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Calculate slider height | //+------------------------------------------------------------------+ int CalculateSliderHeight() { int textAreaHeight = g_displayHeight; //--- Get text area height int scroll_area_height = textAreaHeight - 32; //--- Calculate scroll area height int slider_min_height = 20; //--- Set minimum slider height if (g_total_height <= g_visible_height) return scroll_area_height; //--- Return full height if no scroll double visible_ratio = (double)g_visible_height / g_total_height; //--- Calculate visible ratio int height = (int)MathFloor(scroll_area_height * visible_ratio); //--- Calculate slider height return MathMax(slider_min_height, height); //--- Return minimum or calculated height } //+------------------------------------------------------------------+ //| Update slider position | //+------------------------------------------------------------------+ void UpdateSliderPosition() { int bodyY = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position int textAreaY = bodyY; //--- Set text area y int textAreaHeight = g_displayHeight; //--- Set text area height int scroll_area_height = textAreaHeight - 32; //--- Calculate scroll area height int slider_min_y = textAreaY + 16; //--- Set minimum slider y if (g_max_scroll <= 0) return; //--- Exit if no scroll double scroll_ratio = (double)scroll_pos / g_max_scroll; //--- Calculate scroll ratio int slider_max_y = slider_min_y + scroll_area_height - slider_height; //--- Calculate max slider y int new_y = slider_min_y + (int)MathRound(scroll_ratio * (slider_max_y - slider_min_y)); //--- Calculate new y ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Set slider y position } //+------------------------------------------------------------------+ //| Update scrollbar button colors | //+------------------------------------------------------------------+ void UpdateButtonColors() { int max_scroll = g_max_scroll; //--- Get max scroll color up_color = (scroll_pos == 0) ? C'80,80,80' : (scroll_up_hovered ? C'100,100,100' : C'150,150,150'); //--- Set up button color color down_color = (scroll_pos >= max_scroll) ? C'80,80,80' : (scroll_down_hovered ? C'100,100,100' : C'150,150,150'); //--- Set down button color ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, up_color); //--- Update up label color ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, down_color); //--- Update down label color ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, scroll_up_hovered ? C'70,70,70' : C'60,60,60'); //--- Update up rectangle color ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, scroll_down_hovered ? C'70,70,70' : C'60,60,60'); //--- Update down rectangle color } //+------------------------------------------------------------------+ //| Scroll up | //+------------------------------------------------------------------+ void ScrollUp() { if (g_adjustedLineHeight > 0 && scroll_pos > 0) { //--- Check scroll possible scroll_pos = MathMax(0, scroll_pos - g_adjustedLineHeight); //--- Decrease scroll position UpdateBodyDisplay(); //--- Update body display if (scroll_visible) { //--- Check scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } } } //+------------------------------------------------------------------+ //| Scroll down | //+------------------------------------------------------------------+ void ScrollDown() { int max_scroll = g_max_scroll; //--- Get max scroll if (g_adjustedLineHeight > 0 && scroll_pos < max_scroll) { //--- Check scroll possible scroll_pos = MathMin(max_scroll, scroll_pos + g_adjustedLineHeight); //--- Increase scroll position UpdateBodyDisplay(); //--- Update body display if (scroll_visible) { //--- Check scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } } }
Для реализации функциональности полосы прокрутки, обеспечивающей плавную навигацию по руководству по настройке, создаём функцию "CreateScrollbar", в которой рассчитываем положение и размеры полосы прокрутки на основе Y-координаты элемента body ("bodyY"), устанавливая "scrollbar_x" на правый край основного контейнера и используя ширину 16 пикселей, создавая прямоугольник-выноску ("SCROLL_LEADER") с помощью функции "createRecLabel" для дорожки полосы прокрутки, а также кнопки вверх/вниз ("SCROLL_UP_REC", "SCROLL_DOWN_REC") с метками ("SCROLL_UP_LABEL", "SCROLL_DOWN_LABEL") с использованием стрелок Webdings (0x35, 0x36). Вызываем функцию "CalculateSliderHeight", чтобы определить высоту ползунка на основе соотношения видимого текста, и создаем ползунок ("SCROLL_SLIDER") с помощью функции "createRecLabel". Функция "DeleteScrollbar" удаляет все объекты полосы прокрутки ("SCROLL_LEADER" и т. д.) с помощью ObjectDelete и перерисовывает график.
В функции "CalculateSliderHeight" высота ползунка вычисляется как отношение высоты экрана к общей высоте текста, при этом минимальное значение составляет 20 пикселей. Функция "UpdateSliderPosition" регулирует положение ползунка по оси Y, используя коэффициент прокрутки, определяемый параметрами "scroll_pos" и "g_max_scroll", и устанавливает его с помощью функции "ObjectSetInteger". В функции "UpdateButtonColors" обновляем цвета кнопок "вверх"/"вниз" в зависимости от положения прокрутки и состояния при наведении курсора для обеспечения динамической визуальной обратной связи. Функции "ScrollUp" и "ScrollDown" регулируют "scroll_pos" с помощью "g_adjustedLineHeight", ограничивая область прокрутки, и вызывают функции "UpdateBodyDisplay", "UpdateSliderPosition" и "UpdateButtonColors", если полоса прокрутки видна, обеспечивая плавную прокрутку. Теперь мы можем вызвать эти функции внутри функции обновления дисплея, чтобы добавить полосу прокрутки. Вот подход, которую мы используем для достижения этой цели.
int num_visible_lines = g_visible_height / g_adjustedLineHeight; //--- Calculate visible lines g_max_scroll = MathMax(0, (numLines - num_visible_lines) * g_adjustedLineHeight); //--- Calculate max scroll bool prev_scroll_visible = scroll_visible; //--- Store previous scrollbar state scroll_visible = should_show_scrollbar; //--- Update scrollbar visibility if (scroll_visible != prev_scroll_visible) { //--- Check scrollbar state change if (scroll_visible) { //--- Show scrollbar CreateScrollbar(); //--- Create scrollbar } else { //--- Hide scrollbar DeleteScrollbar(); //--- Delete scrollbar } } scroll_pos = MathMax(0, MathMin(scroll_pos, g_max_scroll)); //--- Clamp scroll position if (scroll_visible) { //--- Update scrollbar slider_height = CalculateSliderHeight(); //--- Calculate slider height ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE, slider_height); //--- Set slider height UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors }
Внутри функции "UpdateBodyDisplay" вычисляем количество видимых строк, деля "g_visible_height" на "g_adjustedLineHeight", и определяем максимальное расстояние прокрутки ("g_max_scroll") как избыточную высоту текста за пределами видимых строк, используя MathMax для предотвращения отрицательных значений. Храним предыдущее состояние видимости полосы прокрутки в переменной "prev_scroll_visible", обновляем "scroll_visible" в зависимости от того, нужна ли полоса прокрутки, и если состояние изменяется, вызываем "CreateScrollbar" для отрисовки полосы прокрутки или "DeleteScrollbar" для ее удаления. Ограничиваем значение параметра "scroll_pos" в диапазоне от 0 до "g_max_scroll" с помощью функций "MathMax" и MathMin для предотвращения переполнения. Если полоса прокрутки видна, обновляем значение параметра "slider_height" с помощью "CalculateSliderHeight", устанавливаем высоту ползунка с помощью ObjectSetInteger для параметра "SCROLL_SLIDER" и вызываем функции "UpdateSliderPosition" и "UpdateButtonColors", чтобы обновить внешний вид и положение полосы прокрутки. При компилировании получаем следующий результат.

На изображении видно, что элементы панели полностью готовы. Теперь необходимо убедиться, что мы удаляем панель при выходе из системы или деинициализации программы.
//+------------------------------------------------------------------+ //| Delete the dashboard | //+------------------------------------------------------------------+ void DeleteDashboard() { string objects[] = { //--- Define dashboard objects SETUP_MAIN, SETUP_HEADER_BG, SETUP_HEADER_IMAGE, SETUP_HEADER_TITLE, SETUP_HEADER_SUBTITLE, SETUP_HEADER_CANCEL, SETUP_BODY_BG, SETUP_FOOTER_BG, SETUP_CHECKBOX_BG, SETUP_CHECKBOX_LABEL, SETUP_CHECKBOX_TEXT, SETUP_OK_BUTTON, SETUP_CANCEL_BUTTON, SCROLL_LEADER, SCROLL_UP_REC, SCROLL_UP_LABEL, SCROLL_DOWN_REC, SCROLL_DOWN_LABEL, SCROLL_SLIDER }; for (int i = 0; i < ArraySize(objects); i++) { //--- Iterate through objects ObjectDelete(0, objects[i]); //--- Delete object } int total = ObjectsTotal(0); //--- Get total objects for (int j = total - 1; j >= 0; j--) { //--- Iterate through remaining objects string name = ObjectName(0, j); //--- Get object name if (StringFind(name, "Setup_ResponseLine_") == 0) { //--- Check for text lines ObjectDelete(0, name); //--- Delete text line } } ChartRedraw(); //--- Redraw chart } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { DeleteDashboard(); //--- Remove dashboard objects if (StringLen(g_scaled_image_resource) > 0) { //--- Check if scaled image exists ResourceFree(g_scaled_image_resource); //--- Free scaled image resource } }
Здесь мы реализуем функцию очистки для мастера, чтобы обеспечить надлежащее управление ресурсами. В функции "DeleteDashboard" определяем массив имен объектов панели, включая компоненты основного контейнера, заголовка, тела, нижней панели, кнопок, флажка и полосы прокрутки, и перебираем их с помощью ObjectDelete, чтобы удалить каждый из них с графика. Затем мы выполняем перебор в цикле по всем оставшимся объектам графика с помощью ObjectsTotal и ObjectName, удаляя все объекты текстовых строк, начинающиеся с "Setup_ResponseLine_", используя "ObjectDelete", и перерисовываем график с помощью ChartRedraw для получения чистого экрана. В "OnDeinit" вызываем функцию "DeleteDashboard", чтобы удалить все элементы панели и проверить, существует ли масштабированный ресурс изображения ("StringLen(g_scaled_image_resource) > 0"), освобождая его с помощью ResourceFree для освобождения памяти. Теперь можно перейти к оживлению панели. Нам надо сделать это так, чтобы при нажатии на кнопки они выполняли свои назначенные вызовы, а эффекты при наведения курсора отображали состояние курсора. Для простоты нам понадобится функция, позволяющая это сделать.
//+------------------------------------------------------------------+ //| Update hover effects | //+------------------------------------------------------------------+ void UpdateHoverEffects(int mouseX, int mouseY) { int ok_x = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_XDISTANCE); //--- Get OK button x int ok_y = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_YDISTANCE); //--- Get OK button y int ok_width = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_XSIZE); //--- Get OK button width int ok_height = (int)ObjectGetInteger(0, SETUP_OK_BUTTON, OBJPROP_YSIZE); //--- Get OK button height bool is_ok_hovered = (mouseX >= ok_x && mouseX <= ok_x + ok_width && mouseY >= ok_y && mouseY <= ok_y + ok_height); //--- Check OK button hover if (is_ok_hovered != ok_button_hovered) { //--- Check hover state change ok_button_hovered = is_ok_hovered; //--- Update hover state color hoverBg = is_ok_hovered ? C'40,80,40' : C'60,60,60'; //--- Set hover background color hoverBorder = is_ok_hovered ? C'60,100,60' : C'80,80,80'; //--- Set hover border ObjectSetInteger(0, SETUP_OK_BUTTON, OBJPROP_BGCOLOR, hoverBg); //--- Update background ObjectSetInteger(0, SETUP_OK_BUTTON, OBJPROP_BORDER_COLOR, hoverBorder); //--- Update border ChartRedraw(); //--- Redraw chart } int cancel_x = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_XDISTANCE); //--- Get Cancel button x int cancel_y = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_YDISTANCE); //--- Get Cancel button y int cancel_width = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_XSIZE); //--- Get Cancel button width int cancel_height = (int)ObjectGetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_YSIZE); //--- Get Cancel button height bool is_cancel_hovered = (mouseX >= cancel_x && mouseX <= cancel_x + cancel_width && mouseY >= cancel_y && mouseY <= cancel_y + cancel_height); //--- Check Cancel button hover if (is_cancel_hovered != cancel_button_hovered) { //--- Check hover state change cancel_button_hovered = is_cancel_hovered; //--- Update hover state color hoverBg = is_cancel_hovered ? C'80,40,40' : C'60,60,60'; //--- Set hover background color hoverBorder = is_cancel_hovered ? C'100,60,60' : C'80,80,80'; //--- Set hover border ObjectSetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_BGCOLOR, hoverBg); //--- Update background ObjectSetInteger(0, SETUP_CANCEL_BUTTON, OBJPROP_BORDER_COLOR, hoverBorder); //--- Update border ChartRedraw(); //--- Redraw chart } int checkbox_x = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_XDISTANCE); //--- Get checkbox x int checkbox_y = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_YDISTANCE); //--- Get checkbox y int checkbox_width = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_XSIZE); //--- Get checkbox width int checkbox_height = (int)ObjectGetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_YSIZE); //--- Get checkbox height bool is_checkbox_hovered = (mouseX >= checkbox_x && mouseX <= checkbox_x + checkbox_width && mouseY >= checkbox_y && mouseY <= checkbox_y + checkbox_height); //--- Check checkbox hover if (is_checkbox_hovered != checkbox_hovered) { //--- Check hover state change checkbox_hovered = is_checkbox_hovered; //--- Update hover state if (checkbox_checked) { //--- Check checkbox state ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, is_checkbox_hovered ? C'0,150,0' : C'0,128,0'); //--- Update background } else { //--- Unchecked state ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, is_checkbox_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background } ChartRedraw(); //--- Redraw chart } int header_cancel_x = (int)ObjectGetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_XDISTANCE); //--- Get header cancel x int header_cancel_y = (int)ObjectGetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_YDISTANCE); //--- Get header cancel y int header_cancel_width = 20; //--- Set header cancel width int header_cancel_height = 20; //--- Set header cancel height bool is_header_cancel_hovered = (mouseX >= header_cancel_x && mouseX <= header_cancel_x + header_cancel_width && mouseY >= header_cancel_y && mouseY <= header_cancel_y + header_cancel_height); //--- Check header cancel hover if (is_header_cancel_hovered != header_cancel_hovered) { //--- Check hover state change header_cancel_hovered = is_header_cancel_hovered; //--- Update hover state ObjectSetInteger(0, SETUP_HEADER_CANCEL, OBJPROP_COLOR, is_header_cancel_hovered ? C'255,100,100' : C'150,150,150'); //--- Update color ChartRedraw(); //--- Redraw chart } if (scroll_visible) { //--- Check scrollbar visible int scroll_up_x = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_XDISTANCE); //--- Get scroll up x int scroll_up_y = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_YDISTANCE); //--- Get scroll up y int scroll_up_width = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_XSIZE); //--- Get scroll up width int scroll_up_height = (int)ObjectGetInteger(0, SCROLL_UP_REC, OBJPROP_YSIZE); //--- Get scroll up height bool is_scroll_up_hovered = (mouseX >= scroll_up_x && mouseX <= scroll_up_x + scroll_up_width && mouseY >= scroll_up_y && mouseY <= scroll_up_y + scroll_up_height); //--- Check scroll up hover if (is_scroll_up_hovered != scroll_up_hovered) { //--- Check hover state change scroll_up_hovered = is_scroll_up_hovered; //--- Update hover state ObjectSetInteger(0, SCROLL_UP_REC, OBJPROP_BGCOLOR, is_scroll_up_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background ObjectSetInteger(0, SCROLL_UP_LABEL, OBJPROP_COLOR, (scroll_pos == 0) ? C'80,80,80' : (is_scroll_up_hovered ? C'100,100,100' : C'150,150,150')); //--- Update label color ChartRedraw(); //--- Redraw chart } int scroll_down_x = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_XDISTANCE); //--- Get scroll down x int scroll_down_y = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_YDISTANCE); //--- Get scroll down y int scroll_down_width = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_XSIZE); //--- Get scroll down width int scroll_down_height = (int)ObjectGetInteger(0, SCROLL_DOWN_REC, OBJPROP_YSIZE); //--- Get scroll down height bool is_scroll_down_hovered = (mouseX >= scroll_down_x && mouseX <= scroll_down_x + scroll_down_width && mouseY >= scroll_down_y && mouseY <= scroll_down_y + scroll_down_height); //--- Check scroll down hover if (is_scroll_down_hovered != scroll_down_hovered) { //--- Check hover state change scroll_down_hovered = is_scroll_down_hovered; //--- Update hover state ObjectSetInteger(0, SCROLL_DOWN_REC, OBJPROP_BGCOLOR, is_scroll_down_hovered ? C'70,70,70' : C'60,60,60'); //--- Update background ObjectSetInteger(0, SCROLL_DOWN_LABEL, OBJPROP_COLOR, (scroll_pos >= g_max_scroll) ? C'80,80,80' : (is_scroll_down_hovered ? C'100,100,100' : C'150,150,150')); //--- Update label color ChartRedraw(); //--- Redraw chart } int scroll_slider_x = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); //--- Get scroll slider x int scroll_slider_y = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get scroll slider y int scroll_slider_width = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE); //--- Get scroll slider width int scroll_slider_height = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); //--- Get scroll slider height bool is_scroll_slider_hovered = (mouseX >= scroll_slider_x && mouseX <= scroll_slider_x + scroll_slider_width && mouseY >= scroll_slider_y && mouseY <= scroll_slider_y + scroll_slider_height); //--- Check scroll slider hover if (is_scroll_slider_hovered != scroll_slider_hovered) { //--- Check hover state change scroll_slider_hovered = is_scroll_slider_hovered; //--- Update hover state ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, is_scroll_slider_hovered ? C'100,100,100' : C'80,80,80'); //--- Update background ChartRedraw(); //--- Redraw chart } } }
Здесь мы реализуем эффекты при наведении курсора мыши на интерактивные элементы, созданные нами для улучшения обратной связи с пользователем. Создаем функцию "UpdateHoverEffects" и в ней проверяем координаты мыши ("mouseX", "mouseY") на соответствие положениям и размерам кнопки OK ("SETUP_OK_BUTTON"), кнопки отмены ("SETUP_CANCEL_BUTTON"), флажка ("SETUP_CHECKBOX_BG"), кнопки отмены заголовка ("SETUP_HEADER_CANCEL") и компонентов полосы прокрутки ("SCROLL_UP_REC", "SCROLL_DOWN_REC", "SCROLL_SLIDER"), использующих ObjectGetInteger для своих размеров.
Для каждого элемента определяем наведение курсора, проверяя, находится ли мышь в пределах его границ, обновляя соответствующие состояния при наведении курсора ("ok_button_hovered", "cancel_button_hovered" и т.д.), если они изменены, и корректируем цвета с помощью ObjectSetInteger: Кнопка «ОК» использует зеленые оттенки (C'40,80,40'), кнопка «Отмена» — красные оттенки (C'80,40,40'), флажок — зеленый цвет, если установлен (C'0,150,0'), или серый цвет (C'70,70,70'), отмена заголовка переключается на ярко-красный цвет (C'255,100,100'), а кнопки/ползунок полосы прокрутки используют различные оттенки серого (C'70,70,70' или C'100,100,100') в зависимости от положения при наведении курсора и прокрутки ("scroll_pos"). Все эти цвета можно изменить по вашему вкусу. Мы просто снова использовали произвольные значения, чтобы получить преимущество перед визуальной обратной связью. Перерисовываем график с помощью ChartRedraw после каждого обновления. Теперь нам нужно просто реализовать это в обработчике OnChartEvent, который обрабатывает все события на графике, но в нашем случае нас интересуют события перемещения мыши и щелчка.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int mouseX = (int)lparam; //--- Get mouse x coordinate int mouseY = (int)dparam; //--- Get mouse y coordinate int body_inner_y = g_mainY + g_headerHeight + g_spacing; //--- Calculate body y position int textAreaEventY = body_inner_y; //--- Set text area y position int textAreaEventH = g_displayHeight; //--- Set text area height int bodyX = g_mainX + g_padding + g_textPadding; //--- Calculate body x position int bodyW = g_mainWidth - 2 * g_padding - 2 * g_textPadding - (scroll_visible ? 16 : 0); //--- Calculate body width if (id == CHARTEVENT_OBJECT_CLICK) { //--- Handle object click events if (sparam == SETUP_HEADER_CANCEL || sparam == SETUP_CANCEL_BUTTON) { //--- Check cancel button click GlobalVariableSet(GV_SETUP, 0.0); //--- Set global variable to false DeleteDashboard(); //--- Remove dashboard from chart } else if (sparam == SETUP_OK_BUTTON) { //--- Check OK button click double new_val = checkbox_checked ? 1.0 : 0.0; //--- Set global variable based on checkbox GlobalVariableSet(GV_SETUP, new_val); //--- Update global variable DeleteDashboard(); //--- Remove dashboard from chart } else if (sparam == SETUP_CHECKBOX_BG || sparam == SETUP_CHECKBOX_TEXT || sparam == SETUP_CHECKBOX_LABEL) { //--- Check checkbox click checkbox_checked = !checkbox_checked; //--- Toggle checkbox state string check_text = checkbox_checked ? CharToString(252) : " "; //--- Set checkbox symbol ObjectSetString(0, SETUP_CHECKBOX_LABEL, OBJPROP_TEXT, check_text); //--- Update checkbox label text color text_color = checkbox_checked ? C'173,216,230' : clrWhite; //--- Set text color based on state ObjectSetInteger(0, SETUP_CHECKBOX_TEXT, OBJPROP_COLOR, text_color); //--- Update checkbox text color if (checkbox_checked) { //--- Check if checkbox is selected ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, C'0,128,0'); //--- Set checked background color ObjectSetInteger(0, SETUP_CHECKBOX_LABEL, OBJPROP_COLOR, clrWhite); //--- Set checked label color } else { //--- Handle unchecked state ObjectSetInteger(0, SETUP_CHECKBOX_BG, OBJPROP_BGCOLOR, C'60,60,60'); //--- Set unchecked background color ObjectSetInteger(0, SETUP_CHECKBOX_LABEL, OBJPROP_COLOR, clrWhite); //--- Set unchecked label color } ChartRedraw(); //--- Redraw chart to reflect changes } else if (sparam == SCROLL_UP_REC || sparam == SCROLL_UP_LABEL) { //--- Check scroll up click ScrollUp(); //--- Execute scroll up action } else if (sparam == SCROLL_DOWN_REC || sparam == SCROLL_DOWN_LABEL) { //--- Check scroll down click ScrollDown(); //--- Execute scroll down action } } else if (id == CHARTEVENT_MOUSE_MOVE) { //--- Handle mouse move events int MouseState = (int)sparam; //--- Get mouse state bool is_in = (mouseX >= bodyX && mouseX <= bodyX + bodyW && mouseY >= textAreaEventY && mouseY <= textAreaEventY + textAreaEventH); //--- Check if mouse is in body mouse_in_body = is_in; //--- Update mouse in body status UpdateHoverEffects(mouseX, mouseY); //--- Update hover effects for elements static int prevMouseState = 0; //--- Store previous mouse state if (prevMouseState == 0 && MouseState == 1 && scroll_visible) { //--- Check for slider drag start int xd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XDISTANCE); //--- Get slider x position int yd_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get slider y position int xs_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_XSIZE); //--- Get slider width int ys_slider = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YSIZE); //--- Get slider height if (mouseX >= xd_slider && mouseX <= xd_slider + xs_slider && mouseY >= yd_slider && mouseY <= yd_slider + ys_slider) { //--- Check if mouse is over slider movingStateSlider = true; //--- Set slider drag state mlbDownX_Slider = mouseX; //--- Store mouse x position mlbDownY_Slider = mouseY; //--- Store mouse y position mlbDown_YD_Slider = yd_slider; //--- Store slider y position ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, C'100,100,100'); //--- Set drag color ChartSetInteger(0, CHART_MOUSE_SCROLL, false); //--- Disable chart scrolling } } if (movingStateSlider) { //--- Handle slider dragging int delta_y = mouseY - mlbDownY_Slider; //--- Calculate y displacement int new_y = mlbDown_YD_Slider + delta_y; //--- Calculate new slider y position int textAreaY_local = body_inner_y; //--- Set text area y position int textAreaHeight_local = g_displayHeight; //--- Set text area height int scroll_area_y_min = textAreaY_local + 16; //--- Set minimum slider y int scroll_area_y_max = textAreaY_local + textAreaHeight_local - 16 - slider_height; //--- Set maximum slider y new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); //--- Clamp new y position ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); //--- Update slider y position int max_scroll = g_max_scroll; //--- Get maximum scroll double scroll_ratio = (double)(new_y - scroll_area_y_min) / (scroll_area_y_max - scroll_area_y_min); //--- Calculate scroll ratio int new_scroll_pos = (int)MathRound(scroll_ratio * max_scroll); //--- Calculate new scroll position if (new_scroll_pos != scroll_pos) { //--- Check if scroll position changed scroll_pos = new_scroll_pos; //--- Update scroll position UpdateBodyDisplay(); //--- Update body text display } ChartRedraw(); //--- Redraw chart } if (MouseState == 0) { //--- Handle mouse release if (movingStateSlider) { //--- Check if slider was being dragged movingStateSlider = false; //--- Reset drag state int max_scroll = g_max_scroll; //--- Get maximum scroll int textAreaY_local = body_inner_y; //--- Set text area y position int textAreaHeight_local = g_displayHeight; //--- Set text area height int scroll_area_y_min_local = textAreaY_local + 16; //--- Set minimum slider y int scroll_area_y_max_local = textAreaY_local + textAreaHeight_local - 16 - slider_height; //--- Set maximum slider y int current_slider_y = (int)ObjectGetInteger(0, SCROLL_SLIDER, OBJPROP_YDISTANCE); //--- Get current slider y double scroll_ratio = (double)(current_slider_y - scroll_area_y_min_local) / (scroll_area_y_max_local - scroll_area_y_min_local); //--- Calculate scroll ratio int temp_scroll = (int)MathRound(scroll_ratio * max_scroll); //--- Calculate temporary scroll if (g_adjustedLineHeight > 0) { //--- Check if line height valid int snapped_line = (int)MathRound((double)temp_scroll / g_adjustedLineHeight); //--- Calculate snapped line scroll_pos = MathMax(0, MathMin(snapped_line * g_adjustedLineHeight, max_scroll)); //--- Snap to nearest line } else { //--- No valid line height scroll_pos = temp_scroll; //--- Use temporary scroll } UpdateBodyDisplay(); //--- Update body text display UpdateSliderPosition(); //--- Update slider position ObjectSetInteger(0, SCROLL_SLIDER, OBJPROP_BGCOLOR, scroll_slider_hovered ? C'100,100,100' : C'80,80,80'); //--- Reset slider color ChartSetInteger(0, CHART_MOUSE_SCROLL, true); //--- Re-enable chart scrolling } } prevMouseState = MouseState; //--- Update previous mouse state } else if (id == CHARTEVENT_MOUSE_WHEEL) { //--- Handle mouse wheel events int wheel_delta = (int)sparam; //--- Get wheel delta bool in_body = (mouseX >= bodyX && mouseX <= bodyX + bodyW && mouseY >= textAreaEventY && mouseY <= textAreaEventY + textAreaEventH); //--- Check if mouse in body if (in_body && g_total_height > g_visible_height && g_adjustedLineHeight > 0) { //--- Check scroll conditions int direction = (wheel_delta > 0) ? -1 : 1; //--- Determine scroll direction int notches = MathAbs(wheel_delta) / 120; //--- Calculate scroll notches int scroll_amount = g_adjustedLineHeight * direction * notches; //--- Calculate scroll amount scroll_pos += scroll_amount; //--- Update scroll position int max_scroll = g_max_scroll; //--- Get maximum scroll scroll_pos = MathMax(0, MathMin(scroll_pos, max_scroll)); //--- Clamp scroll position UpdateBodyDisplay(); //--- Update body text display if (scroll_visible) { //--- Check if scrollbar visible UpdateSliderPosition(); //--- Update slider position UpdateButtonColors(); //--- Update button colors } ChartRedraw(); //--- Redraw chart } } }
Наконец, в функции OnChartEvent обрабатываем события клика (CHARTEVENT_OBJECT_CLICK), проверяя объект, на который был совершен клик ("sparam"): если это отмена заголовка ("SETUP_HEADER_CANCEL") или кнопка отмены ("SETUP_CANCEL_BUTTON"), устанавливаем глобальную переменную "GV_SETUP" в значение 0.0 с помощью GlobalVariableSet и удаляем панель с помощью "DeleteDashboard"; если это кнопка OK ("SETUP_OK_BUTTON"), устанавливаем "GV_SETUP" в значение 1.0, если установлен флажок ("checkbox_checked"), или в значение 0.0 в противном случае, затем удаляем панель. Если это компоненты флажка ("SETUP_CHECKBOX_BG", "SETUP_CHECKBOX_TEXT", "SETUP_CHECKBOX_LABEL"), переключаем состояние "checkbox_checked", обновляем метку флажка на галочку (Unicode 252) или пробел с помощью ObjectSetString, устанавливаем цвет текста на светло-голубой (C'173,216,230') или белый с помощью ObjectSetInteger и настраиваем фон флажка на зеленый (C'0,128,0') или серый (C'60,60,60'). MQL5 предоставляет подробный список символов шрифта Wingdings, и именно его мы и использовали. Вы можете использовать любой из предложенных вариантов или другой подход. Вот визуализация возможных символов MQL5 Wingdings, которые вы можете использовать.

Для кликов прокрутки вверх ("SCROLL_UP_REC", "SCROLL_UP_LABEL") или вниз ("SCROLL_DOWN_REC", "SCROLL_DOWN_LABEL") вызываем "ScrollUp" или "ScrollDown". Для событий перемещения мыши (CHARTEVENT_MOUSE_MOVE) рассчитываем область тела, обновляем "mouse_in_body" и вызываем "UpdateHoverEffects" с помощью "mouseX" и "mouseY"; обнаруживаем начало перетаскивания ползунка ("MouseState" 1), проверяя, находится ли мышь над "SCROLL_SLIDER", устанавливая "movingStateSlider" и сохраняя положения мыши, и во время перетаскивания корректируем положение ползунка по оси y с помощью ObjectSetInteger, обновляем "scroll_pos" на основе коэффициента прокрутки и обновляем дисплей с помощью "UpdateBodyDisplay". При отпускании кнопки мыши ("MouseState" 0) привязываем "scroll_pos" к ближайшей строке, сбрасываем состояние ползунка и снова включаем прокрутку графика с помощью функции ChartSetInteger. Для событий, связанных с колесиком мыши (CHARTEVENT_MOUSE_WHEEL), корректируем "scroll_pos" на основе направления колесика и выемок, фиксируем его в пределах "g_max_scroll" и обновляем отображение и полосу прокрутки. При компилировании получаем следующий результат.

На изображении видно, что панель теперь полностью функциональна и интерактивна. При нажатии на кнопку "Ок" мы должны получить установленные глобальные переменные. Чтобы получить доступ к глобальной переменной, необходимо нажать "Tools" ("Сервис"), затем "Global Variables" ("Глобальные переменные") или просто нажать клавишу F3 на клавиатуре. Ниже приведена полная визуализация.

На изображениях видно, что мы корректно настроили мастер и достигли всех поставленных целей. Теперь осталось протестировать работоспособность проекта, и это рассматривается в предыдущем разделе.
Тестирование мастера начальной настройки
Мы провели тестирование, а ниже представлена скомпилированная визуализация в едином формате растрового изображения Graphics Interchange Format (GIF).

Заключение
В заключение отметим, что мы разработали мастер настройки советников на MQL5 для первого запуска, создав интерактивную панель с прокручиваемым руководством, динамическим форматированием текста и пользовательскими элементами управления, такими как кнопки и флажок для упрощения настройки программы в MetaTrader 5. Этот инструмент упрощает процесс адаптации трейдера путем предоставления четких указаний и механизма пропуска будущих отображений, обеспечивая эффективную настройку и адаптируемость к различным настройкам экрана. Это позволяет упростить инициализацию программы, однократно предоставляя индивидуальную информацию для пользователя. Программы готова для дальнейшей настройки в вашем торговом инструментарии. Удачной торговли!
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19714
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Знакомство с языком MQL5 (Часть 38): Освоение API и функции WebRequest в языке MQL5 (XII)
Искусство работы с логами (Часть 8): Самопереводящиеся записи об ошибках
Возможности Мастера MQL5, которые вам нужно знать (Часть 71): Использование паттернов MACD и OBV
Переосмысливаем классические стратегии (Часть 13): Обновление стратегии по пересечению скользящих (Часть 2)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования