Статьи

Графічні інтерфейси X: Виділення тексту в багаторядковому поле введення (build 13)

  1. зміст Вступ
  2. Відстеження натискання клавіші Shift
  3. Комбінації клавіш для виділення тексту
  4. Методи для виділення тексту
  5. Методи для видалення виділеного тексту
  6. Клас для роботи з даними зображення
  7. Висновок

зміст

Вступ

Про те, для чого призначена ця бібліотека, більш детально можна прочитати в самій першій статті: Графічні інтерфейси I: Підготовка структури бібліотеки (Глава 1) . В кінці статей кожної частини представлений список глав з посиланнями і там же є можливість завантажити до себе на комп'ютер повну версію бібліотеки на поточній стадії розробки. Файли потрібно розмістити по тим же тек, як вони розташовані в архіві.

Для повноцінного використання многострочного текстового поля введення, яке ми розглядали в статтях зі списку нижче, залишилося реалізувати виділення тексту, оскільки видаляти текст по одному символу незручно.

Виділення тексту за допомогою клавішних комбінацій і видалення виділеного тексту буде виглядати так само, як в будь-якому іншому текстовому редакторі. Крім цього, продовжимо оптимізувати код і підготуємо класи для переходу до завершального процесу другого етапу розвитку бібліотеки, коли всі елементи управління будуть намальованими на окремих малюнках (полотнах для малювання).

Тут буде представлена ​​остаточна версія цього елемента інтерфейсу бібліотеки. Згодом зміни будуть вноситися в нього тільки в разі знаходження більш ефективних рішень відносного будь-якого алгоритму.

Відстеження натискання клавіші Shift

В першу чергу, доповнимо розглянутий раніше клас CKeys, призначений для роботи з клавіатурою, методом CKeys :: KeyShiftState () для визначення поточного стану клавіші Shift. Ця клавіша буде використовуватися в різних комбінаціях для виділення тексту. У лістингу нижче показаний код цього простого методу. Клавіша Shift вважається такою, що натискує, якщо функція :: TerminalInfoInteger () з ідентифікатором TERMINAL_KEYSTATE_SHIFT повертає значення менше нуля.

class CKeys {public: bool KeyShiftState (void); }; bool CKeys :: KeyShiftState (void) {return (:: TerminalInfoInteger (TERMINAL_KEYSTATE_SHIFT) <0); }

Комбінації клавіш для виділення тексту

Розглянемо всі комбінації клавіш для виділення тексту, які будуть реалізовані в нашому полі введення. Почнемо з поєднань двох клавіш.

  • Сполучення 'Shift + Left' і 'Shift + Right' зміщують текстовий курсор вліво і вправо відповідно на один символ. Текст при цьому виділяється іншим кольором фону і символу (їх користувач може налаштовувати):

Текст при цьому виділяється іншим кольором фону і символу (їх користувач може налаштовувати):

Мал. 1. Виділення тексту зі зміщенням на один символ вліво і вправо.

  • Сполучення 'Shift + Home' і 'Shift + End' зміщують текстовий курсор в початок і кінець рядка з виділенням всіх символів від початкового положення курсора.

Сполучення 'Shift + Home' і 'Shift + End' зміщують текстовий курсор в початок і кінець рядка з виділенням всіх символів від початкового положення курсора

Мал. 2. Виділення тексту зі зміщенням від початкового положення курсора до початку і кінця рядка.

  • Сполучення 'Shift + Up' і 'Shift + Down' зміщують текстовий курсор вгору і вниз відповідно на один рядок. Для поєднання 'Shift + Up' текст виділяється на початковому рядку від курсора до початку цього рядка і до курсора від кінця кінцевої рядка. Для 'Shift + Down' поведінку симетрично. Якщо між початковою і кінцевою виділеними рядками є ще рядка, то текст в них виділяється повністю.

Якщо між початковою і кінцевою виділеними рядками є ще рядка, то текст в них виділяється повністю

Мал. 3. Виділення тексту зі зміщенням на один рядок вгору і вниз.

Іноді для виділення тексту використовуються поєднання з трьох клавіш. Наприклад, коли потрібно швидко виділити кілька слів на одному рядку, то посимвольного виділення буде занадто виснажливим. Або якщо потрібно виділити текст, що складається з безлічі рядків, то навіть порядкове виділення буде незручним.

У сполученнях з трьох клавіш, окрім клавіші Shift, використовується Ctrl. Розглянемо всі такі комбінації, які будуть реалізовані в цій статті:

  • Комбінації 'Ctrl + Shift + Left' і 'Ctrl + Shift + Right' призначені для виділення тексту цілими словами вліво і вправо від поточного положення текстового курсора відповідно:

Комбінації 'Ctrl + Shift + Left' і 'Ctrl + Shift + Right' призначені для виділення тексту цілими словами вліво і вправо від поточного положення текстового курсора відповідно:

Мал. 4. Виділення тексту зі зміщенням на одне слово вліво і вправо.

  • Комбінації клавіш 'Ctrl + Shift + Home' і 'Ctrl + Shift + End' дозволяють виділяти весь текст до початку першої і кінця останньої рядків від поточного положення текстового курсора:

Комбінації клавіш 'Ctrl + Shift + Home' і 'Ctrl + Shift + End' дозволяють виділяти весь текст до початку першої і кінця останньої рядків від поточного положення текстового курсора:

Мал. 5. Виділення тексту зі зміщенням текстового курсора в початок і кінець документа.

У наступному розділі розглянемо, які методи використовуються для виділення тексту.


Методи для виділення тексту

За замовчуванням виділений текст відображається білими символами на синьому тлі. При необхідності кольору можна змінити за допомогою методів CTextBox :: SelectedBackColor () і CTextBox :: SelectedTextColor ().

class CTextBox: public CElement {private: color m_selected_back_color; color m_selected_text_color; private: void SelectedBackColor (const color clr) {m_selected_back_color = clr; } Void SelectedTextColor (const color clr) {m_selected_text_color = clr; }}; CTextBox :: CTextBox (void): m_selected_text_color (clrWhite), m_selected_back_color (C'51,153,255 ') {}

Щоб виділити текст, потрібні поля і методи для позначення початкових і кінцевих індексів рядків і символів виділеного тексту. Крім цього, знадобиться метод для скидання цих значень, коли виділення скасовується.

Щоразу після натискання комбінації клавіш для виділення тексту, перед зміщенням текстового курсора, буде викликатися метод CTextBox :: SetStartSelectedTextIndexes (). Він встановлює початкові значення індексів рядки і символу, на яких знаходиться текстовий курсор. Значення будуть встановлюватися тільки в тому випадку, якщо це перший виклик методу після останнього скидання цих значень. Після виклику цього методу текстовий курсор зміщується, потім викликається метод CTextBox :: SetEndSelectedTextIndexes (), який встановлює кінцеві значення індексів рядки і символу (тобто, поточний стан текстового курсора). Якщо в процесі переміщення текстового курсора в режимі виділення тексту вийде так, що він зараз знаходиться на тому ж місці, звідки почали, то значення скидаються викликом методу CTextBox :: ResetSelectedText (). Значення також скидаються при будь-якому переміщенні текстового курсора, видаленні виділеного тексту або при дезактивації поля введення.

class CTextBox: public CElement {private: int m_selected_line_from; int m_selected_line_to; int m_selected_symbol_from; int m_selected_symbol_to; private: void SetStartSelectedTextIndexes (void); void SetEndSelectedTextIndexes (void); void ResetSelectedText (void); }; void CTextBox :: SetStartSelectedTextIndexes (void) {if (m_selected_line_from == WRONG_VALUE) {m_selected_line_from = (int) m_text_cursor_y_pos; m_selected_symbol_from = (int) m_text_cursor_x_pos; }} Void CTextBox :: SetEndSelectedTextIndexes (void) {m_selected_line_to = (int) m_text_cursor_y_pos; m_selected_symbol_to = (int) m_text_cursor_x_pos; if (m_selected_line_from == m_selected_line_to && m_selected_symbol_from == m_selected_symbol_to) ResetSelectedText (); } Void CTextBox :: ResetSelectedText (void) {m_selected_line_from = WRONG_VALUE; m_selected_line_to = WRONG_VALUE; m_selected_symbol_from = WRONG_VALUE; m_selected_symbol_to = WRONG_VALUE; }

Блоки коду, які раніше використовувалися в методах для переміщення курсора, реалізовані тепер в окремих методах, так як будуть повторно використовуватися в методах для виділення тексту. Те ж саме стосується і коду для коригування смуг прокрутки у випадках, коли текстовий курсор виходить за видиму область.

class CTextBox: public CElement {private: void MoveTextCursorToLeft (void); void MoveTextCursorToRight (void); void MoveTextCursorToUp (void); void MoveTextCursorToDown (void); void CorrectingHorizontalScrollThumb (void); void CorrectingVerticalScrollThumb (void); };

Практично весь код в методах обробки натискання сполучень клавіш, одна з яких Shift, ідентичний, крім виклику методів для переміщення текстового курсора. Тому має сенс створити додатковий метод, в який можна просто передати напрямок переміщення текстового курсора. У файл Enums.mqh було додано перерахування ENUM_MOVE_TEXT_CURSOR з декількома ідентифікаторами (див. Лістинг коду нижче), за допомогою яких можна вказати, куди потрібно перемістити текстовий курсор:

  • TO_NEXT_LEFT_SYMBOL - на один символ вліво.
  • TO_NEXT_RIGHT_SYMBOL - на один символ вправо.
  • TO_NEXT_LEFT_WORD - на одне слово вліво.
  • TO_NEXT_RIGHT_WORD - на одне слово вправо.
  • TO_NEXT_UP_LINE - на один рядок вгору.
  • TO_NEXT_DOWN_LINE - на один рядок вниз.
  • TO_BEGIN_LINE - на початок поточного рядка.
  • TO_END_LINE - в кінець поточного рядка.
  • TO_BEGIN_FIRST_LINE - в початок першого рядка.
  • TO_END_LAST_LINE - в кінець останнього рядка.

enum ENUM_MOVE_TEXT_CURSOR {TO_NEXT_LEFT_SYMBOL = 0, TO_NEXT_RIGHT_SYMBOL = 1, TO_NEXT_LEFT_WORD = 2, TO_NEXT_RIGHT_WORD = 3, TO_NEXT_UP_LINE = 4, TO_NEXT_DOWN_LINE = 5, TO_BEGIN_LINE = 6, TO_END_LINE = 7, TO_BEGIN_FIRST_LINE = 8, TO_END_LAST_LINE = 9};

Тепер можна створити загальний метод для переміщення текстового курсора - CTextBox :: MoveTextCursor (), в який досить передати один з ідентифікаторів вищенаведеного списку. Цей же метод тепер буде використовуватися практично у всіх методах-обробниках подій натискання клавіш в елементі управління CTextBox.

class CTextBox: public CElement {private: void MoveTextCursor (const ENUM_MOVE_TEXT_CURSOR direction); }; void CTextBox :: MoveTextCursor (const ENUM_MOVE_TEXT_CURSOR direction) {switch (direction) {case TO_NEXT_LEFT_SYMBOL: MoveTextCursorToLeft (); break; case TO_NEXT_RIGHT_SYMBOL: MoveTextCursorToRight (); break; case TO_NEXT_LEFT_WORD: MoveTextCursorToLeft (true); break; case TO_NEXT_RIGHT_WORD: MoveTextCursorToRight (true); break; case TO_NEXT_UP_LINE: MoveTextCursorToUp (); break; case TO_NEXT_DOWN_LINE: MoveTextCursorToDown (); break; case TO_BEGIN_LINE: SetTextCursor (0, m_text_cursor_y_pos); break; case TO_END_LINE: {uint symbols_total = :: ArraySize (m_lines [m_text_cursor_y_pos] .m_symbol); SetTextCursor (symbols_total, m_text_cursor_y_pos); break; } Case TO_BEGIN_FIRST_LINE: SetTextCursor (0, 0); break; case TO_END_LAST_LINE: {uint lines_total = :: ArraySize (m_lines); uint symbols_total = :: ArraySize (m_lines [lines_total- 1] .m_symbol); SetTextCursor (symbols_total, lines_total- 1); break; }}}

Можна ще істотно скоротити код в цьому файлі, так як в методах-обробниках подій на переміщення текстового курсора і на виділення тексту багато повторюваних блоків коду.

Приклад з повторюваним блоком коду в методах для переміщення текстового курсора:

bool CTextBox :: OnPressedKeyLeft (const long key_code) {if (key_code! = KEY_LEFT || m_keys.KeyCtrlState () || m_keys.KeyShiftState ()) return (false); ResetSelectedText (); MoveTextCursor (TO_NEXT_LEFT_SYMBOL); CorrectingHorizontalScrollThumb (); CorrectingVerticalScrollThumb (); DrawTextAndCursor (true); :: EventChartCustom (m_chart_id, ON_MOVE_TEXT_CURSOR, CElementBase :: Id (), CElementBase :: Index (), TextCursorInfo ()); return (true); }

Приклад з повторюваним блоком коду в методах для виділення тексту:

bool CTextBox :: OnPressedKeyShiftAndLeft (const long key_code) {if (key_code! = KEY_LEFT || m_keys.KeyCtrlState () ||! m_keys.KeyShiftState ()) return (false); SetStartSelectedTextIndexes (); MoveTextCursor (TO_NEXT_LEFT_SYMBOL); SetEndSelectedTextIndexes (); CorrectingHorizontalScrollThumb (); CorrectingVerticalScrollThumb (); DrawTextAndCursor (true); :: EventChartCustom (m_chart_id, ON_MOVE_TEXT_CURSOR, CElementBase :: Id (), CElementBase :: Index (), TextCursorInfo ()); return (true); }

Реалізуємо ще один додатковий (перевантажений) метод CTextBox :: MoveTextCursor (), в який теж потрібно буде передавати ідентифікатор з напрямком переміщення, а також прапор, який вказує, (1) переміщення чи це текстового курсора або (2) виділення тексту.

class CTextBox: public CElement {private: void MoveTextCursor (const ENUM_MOVE_TEXT_CURSOR direction, const bool with_highlighted_text); }; void CTextBox :: MoveTextCursor (const ENUM_MOVE_TEXT_CURSOR direction, const bool with_highlighted_text) {if (! with_highlighted_text) {ResetSelectedText (); MoveTextCursor (direction); } Else {SetStartSelectedTextIndexes (); MoveTextCursor (direction); SetEndSelectedTextIndexes (); } CorrectingHorizontalScrollThumb (); CorrectingVerticalScrollThumb (); DrawTextAndCursor (true); :: EventChartCustom (m_chart_id, ON_MOVE_TEXT_CURSOR, CElementBase :: Id (), CElementBase :: Index (), TextCursorInfo ()); }

Методи обробки комбінацій клавіш для виділення тексту показані нижче. Їх код практично ідентичний (відмінність лише в параметрах), тому можете подивитися його в доданих до статті файлах:

class CTextBox: public CElement {private: bool OnPressedKeyShiftAndLeft (const long key_code); bool OnPressedKeyShiftAndRight (const long key_code); bool OnPressedKeyShiftAndUp (const long key_code); bool OnPressedKeyShiftAndDown (const long key_code); bool OnPressedKeyShiftAndHome (const long key_code); bool OnPressedKeyShiftAndEnd (const long key_code); bool OnPressedKeyCtrlShiftAndLeft (const long key_code); bool OnPressedKeyCtrlShiftAndRight (const long key_code); bool OnPressedKeyCtrlShiftAndHome (const long key_code); bool OnPressedKeyCtrlShiftAndEnd (const long key_code); };

До сих пір текст наносився на полотно цілими рядками. Але оскільки виділені символи і фон під ними змінюють колір, то текст потрібно виводити посимвольний. Для цього внесемо невеликі зміни в метод CTextBox :: TextOut ().

Знадобиться також додатковий метод CTextBox :: CheckSelectedText () для перевірки виділених символів. Ми вже знаємо, що при виділенні тексту запам'ятовуються індекси початкових і кінцевих рядків і символів текстового курсора. Тому, проходячи по символам в циклі, легко можна з'ясувати, чи виділений той чи інший символ в рядку. Логіка проста:

  1. Якщо початковий індекс рядка нижче, ніж кінцевий, то символ виділено:
  • Якщо це кінцева рядок і символ праворуч від кінцевого виділеного
  • Якщо це початкова рядок і символ зліва від початкового виділеного
  • На проміжних рядках виділені всі символи
  • Якщо початковий індекс рядка вище, ніж кінцевий, то символ виділено:
    • Якщо це кінцева рядок і символ зліва від кінцевого виділеного
    • Якщо початковий рядок і символ праворуч від початкового виділеного
    • На проміжних рядках виділені всі символи
  • Якщо текст виділений тільки на одному рядку, то символ виділено, якщо знаходиться в позначеному діапазоні між початковим і кінцевим індексами символів.
  • class CTextBox: public CElement {private: bool CheckSelectedText (const uint line_index, const uint symbol_index); }; bool CTextBox :: CheckSelectedText (const uint line_index, const uint symbol_index) {bool is_selected_text = false; if (m_selected_line_from == WRONG_VALUE) return (false); if (m_selected_line_from> m_selected_line_to) {if ((int) line_index == m_selected_line_to && (int) symbol_index> = m_selected_symbol_to) {is_selected_text = true; } Else if ((int) line_index == m_selected_line_from && (int) symbol_index <m_selected_symbol_from) {is_selected_text = true; } Else if ((int) line_index> m_selected_line_to && (int) line_index <m_selected_line_from) {is_selected_text = true; }} Else if (m_selected_line_from <m_selected_line_to) {if ((int) line_index == m_selected_line_to && (int) symbol_index <m_selected_symbol_to) {is_selected_text = true; } Else if ((int) line_index == m_selected_line_from && (int) symbol_index> = m_selected_symbol_from) {is_selected_text = true; } Else if ((int) line_index <m_selected_line_to && (int) line_index> m_selected_line_from) {is_selected_text = true; }} Else {if ((int) line_index> = m_selected_line_to && (int) line_index <= m_selected_line_from) {if (m_selected_symbol_from> m_selected_symbol_to) {if ((int) symbol_index> = m_selected_symbol_to && (int) symbol_index <m_selected_symbol_from) is_selected_text = true ; } Else {if ((int) symbol_index> = m_selected_symbol_from && (int) symbol_index <m_selected_symbol_to) is_selected_text = true; }}} Return (is_selected_text); }

    У методі CTextBox :: TextOut (), який призначений для виведення тексту, потрібно замість виведення цілого рядка додати внутрішній цикл з перебором символів рядка. У ньому визначається, чи виділений перевіряється символ. Якщо символ виділено, то визначається його колір, і під ним малюється зафарбований прямокутник. Тільки після цього виводиться сам символ.

    class CTextBox: public CElement {private: void TextOut (void); }; void CTextBox :: TextOut (void) {m_canvas.Erase (AreaColorCurrent ()); uint lines_total = :: ArraySize (m_lines); m_text_cursor_y_pos = (m_text_cursor_y_pos> = lines_total)? lines_total- 1: m_text_cursor_y_pos; uint symbols_total = :: ArraySize (m_lines [m_text_cursor_y_pos] .m_symbol); if (m_multi_line_mode || symbols_total> 0) {int line_width = (int) LineWidth (m_text_cursor_x_pos, m_text_cursor_y_pos); int line_height = (int) LineHeight (); for (uint i = 0; i <lines_total; i ++) {int x = m_text_x_offset; int y = m_text_y_offset + ((int) i * line_height); uint string_length = :: ArraySize (m_lines [i] .m_symbol); for (uint s = 0; s <string_length; s ++) {uint text_color = TextColorCurrent (); if (CheckSelectedText (i, s)) {text_color = :: ColorToARGB (m_selected_text_color); int x2 = x + m_lines [i] .m_width [s]; int y2 = y + line_height- 1; m_canvas.FillRectangle (x, y, x2, y2, :: ColorToARGB (m_selected_back_color)); } M_canvas. TextOut (x, y, m_lines [i] .m_symbol [s], text_color, TA_LEFT); x + = m_lines [i] .m_width [s]; }}} Else {if (m_default_text! = "") M_canvas. TextOut (m_area_x_size / 2, m_area_y_size / 2, m_default_text, :: ColorToARGB (m_default_text_color), TA_CENTER | TA_VCENTER); }}

    Методи для виділення тексту реалізовані, і ось як це виглядає в готовому додатку:

    Методи для виділення тексту реалізовані, і ось як це виглядає в готовому додатку:

    Мал. 6. Демонстрація виділення тексту в реалізованому текстовому полі MQL-додатки.


    Методи для видалення виділеного тексту

    Тепер розглянемо методи для видалення виділеного тексту. Тут потрібно враховувати, що при видаленні виділеного тексту, в залежності від того, виділено він на одному рядку або на декількох, будуть застосовуватися різні методи.

    Для видалення тексту, виділеного на одному рядку, буде викликатися метод CTextBox :: DeleteTextOnOneLine (). На початку цього методу визначається кількість символів для видалення. Потім, якщо початковий індекс символу виділеного тексту знаходиться праворуч, то символи від цієї початкової позиції зміщуються вліво на кількість символів для видалення, а після цього масив символів рядка зменшується на таку ж кількість.

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

    сlass CTextBox: public CElement {private: void DeleteTextOnOneLine (void); }; void CTextBox :: DeleteTextOnOneLine (void) {int symbols_total = :: ArraySize (m_lines [m_text_cursor_y_pos] .m_symbol); int symbols_to_delete = :: fabs (m_selected_symbol_from-m_selected_symbol_to); if (m_selected_symbol_to <m_selected_symbol_from) {MoveSymbols (m_text_cursor_y_pos, m_selected_symbol_from, m_selected_symbol_to); } Else {m_text_cursor_x_pos- = symbols_to_delete; MoveSymbols (m_text_cursor_y_pos, m_selected_symbol_to, m_selected_symbol_from); } ArraysResize (m_text_cursor_y_pos, symbols_total-symbols_to_delete); }

    Щоб видалити відразу кілька рядків виділеного тексту буде використовуватися метод CTextBox :: DeleteTextOnMultipleLines (). Тут алгоритм складніше. Спочатку потрібно визначити:

    • Загальна кількість символів на початковій і кінцевій рядках
    • Кількість проміжних рядків виділеного тексту (крім початкової і кінцевої рядків)
    • Кількість символів для видалення на початковій і кінцевій рядках.

    Послідовність подальших дій наведена нижче. Залежно від того, в якому напрямку (вгору або вниз) виділяється текст, будуть передаватися початкові і кінцеві індекси в інші методи.

    • До тімчасової Динамічний масив копіюються символи для перенесення, Які Залишайся после відалення, з одного рядка на іншу.
    • Встановлюється новий розмір масиву-приймач (рядки).
    • Додаються дані в масив Структури рядки-приймач.
    • Зміщуються рядки на Кількість відаляються рядків.
    • Масиву рядків встановлюється новий розмір (зменшується на Кількість відаляються рядків).
    • У разі, если початкова рядок вищє кінцевої (віділення тексту вниз), то текстовий курсор переміщається на Початкові Індекси (рядки и символи) віділеного тексту.

    class CTextBox: public CElement {private: void DeleteTextOnMultipleLines (void); }; void CTextBox :: DeleteTextOnMultipleLines (void) {uint symbols_total_line_from = :: ArraySize (m_lines [m_selected_line_from] .m_symbol); uint symbols_total_line_to = :: ArraySize (m_lines [m_selected_line_to] .m_symbol); uint lines_to_delete = :: fabs (m_selected_line_from-m_selected_line_to); uint symbols_to_delete_in_line_from = :: fabs (symbols_total_line_from-m_selected_symbol_from); uint symbols_to_delete_in_line_to = :: fabs (symbols_total_line_to-m_selected_symbol_to); if (m_selected_line_from> m_selected_line_to) {string array []; CopyWrapSymbols (m_selected_line_from, m_selected_symbol_from, symbols_to_delete_in_line_from, array); uint new_size = m_selected_symbol_to + symbols_to_delete_in_line_from; ArraysResize (m_selected_line_to, new_size); PasteWrapSymbols (m_selected_line_to, m_selected_symbol_to, array); uint lines_total = :: ArraySize (m_lines); MoveLines (m_selected_line_to + 1, lines_total-lines_to_delete, lines_to_delete, false); :: ArrayResize (m_lines, lines_total-lines_to_delete); } Else {string array []; CopyWrapSymbols (m_selected_line_to, m_selected_symbol_to, symbols_to_delete_in_line_to, array); uint new_size = m_selected_symbol_from + symbols_to_delete_in_line_to; ArraysResize (m_selected_line_from, new_size); PasteWrapSymbols (m_selected_line_from, m_selected_symbol_from, array); uint lines_total = :: ArraySize (m_lines); MoveLines (m_selected_line_from + 1, lines_total-lines_to_delete, lines_to_delete, false); :: ArrayResize (m_lines, lines_total-lines_to_delete); SetTextCursor (m_selected_symbol_from, m_selected_line_from); }}

    Який з вищеописаних методів викликати, визначається в основному методі для видалення тексту - CTextBox :: DeleteSelectedText (). Після того, як виділений текст буде видалений, скидаються значення початкових і кінцевих індексів. Потім потрібно розрахувати заново розміри текстового поля, так як, можливо, змінилася кількість рядків. Також могла змінитися максимальна ширина рядка, за якою розраховується ширина текстового поля. В кінці методу відправляється повідомлення про те, що текстовий курсор перемістився. Метод повертає true, якщо текст був виділений і видалений. Якщо ж при виклику методу виявляється, що виділеного тексту немає, то метод повертає false.

    class CTextBox: public CElement {private: void DeleteSelectedText (void); }; bool CTextBox :: DeleteSelectedText (void) {if (m_selected_line_from == WRONG_VALUE) return (false); if (m_selected_line_from == m_selected_line_to) DeleteTextOnOneLine (); else DeleteTextOnMultipleLines (); ResetSelectedText (); CalculateTextBoxSize (); ChangeTextBoxSize (); CorrectingHorizontalScrollThumb (); CorrectingVerticalScrollThumb (); DrawTextAndCursor (true); :: EventChartCustom (m_chart_id, ON_MOVE_TEXT_CURSOR, CElementBase :: Id (), CElementBase :: Index (), TextCursorInfo ()); return (true); }

    Метод CTextBox :: DeleteSelectedText () викликається не тільки при натисканні клавіші Backspace, але також: (1) при введенні нового символу і (2) при натисканні клавіші Enter. У цих випадках текст спочатку видаляється, а потім здійснюється дію, відповідне утримуючи клавішу.

    Ось як це виглядає в готовому додатку:

    Мал. 7. Демонстрація видалити вибраний текст.

    Клас для роботи з даними зображення

    Як доповнення в цій статті розглянемо новий клас (CImage) для роботи з даними зображення. Він багато разів буде використовуватися в багатьох класах елементів управління бібліотеки, де потрібно намалювати картинку. Клас міститься в файлі Objects.mqh.

    Властивості класу:

    • масив пікселів зображення;
    • ширина зображення;
    • висота зображення;
    • шлях до файлу зображення.

    class CImage {protected: uint m_image_data []; uint m_image_width; uint m_image_height; string m_bmp_path; public: uint DataTotal (void) {return (:: ArraySize (m_image_data)); } Uint Data (const uint data_index) {return (m_image_data [data_index]); } Void Data (const uint data_index, const uint data) {m_image_data [data_index] = data; } Void Width (const uint width) {m_image_width = width; } Uint Width (void) {return (m_image_width); } Void Height (const uint height) {m_image_height = height; } Uint Height (void) {return (m_image_height); } Void BmpPath (const string bmp_file_path) {m_bmp_path = bmp_file_path; } String BmpPath (void) {return (m_bmp_path); }}; CImage :: CImage (void): m_image_width (0), m_image_height (0), m_bmp_path ( "") {} CImage :: ~ CImage (void) {}

    Для збереження зображення і його властивостей призначений метод CImage :: ReadImageData (). Цей метод читає зображення за вказаним шляхом і зберігає його дані.

    class CImage {public: bool ReadImageData (const string bmp_file_path); }; bool CImage :: ReadImageData (const string bmp_file_path) {:: ResetLastError (); m_bmp_file_path = bmp_file_path; if (! :: ResourceReadImage (m_bmp_file_path, m_image_data, m_image_width, m_image_height)) {:: Print (__FUNCTION__, "> error:", :: GetLastError ()); return (false); } Return (true); }

    Іноді може знадобитися зробити копію зображення такого ж типу (CImage). Для цих цілей реалізований метод CImage :: CopyImageData (). На початку методу масиву-приймача встановлюється розмір масиву-джерела. Потім в циклі копіюються дані з масиву-джерела в масив-приймач.

    class CImage {public: void CopyImageData (CImage & array_source); }; void CImage :: CopyImageData (CImage & array_source) {uint data_total = DataTotal (); uint source_data_total = :: GetPointer (array_source) .DataTotal (); :: ArrayResize (m_image_data, source_data_total); for (uint i = 0; i <source_data_total; i ++) m_image_data [i] = :: GetPointer (array_source) .Data (i); }

    До цього оновлення в класі CCanvasTable використовувалася структура для зберігання даних зображення. Тепер з наявністю класу CImage внесені відповідні зміни.


    Висновок

    У цій статті ми завершили розробку елемента "багаторядкова текстове поле введення". Його головна відмітна особливість в тому, що тепер в ньому немає обмежень на кількість введених символів і можна ввести кілька рядків, чого так не вистачало в стандартному графічному об'єкті типу OBJ_EDIT . У наступній статті ми продовжимо розвивати тему "Елементи управління в осередках таблиці": додамо можливість змінювати значення в осередках таблиці за допомогою елемента, розглянутого в цій статті. Крім цього, переведемо кілька елементів в новий режим: вони будуть малюватися, а не збиратися з декількох стандартних графічних об'єктів.

    На поточному етапі розробки бібліотеки для створення графічних інтерфейсів її загальна схема виглядає так:

    На поточному етапі розробки бібліотеки для створення графічних інтерфейсів її загальна схема виглядає так:

    Мал. 8. Структура бібліотеки на поточній стадії розробки.

    Нижче ви можете завантажити до себе на комп'ютер останню версію бібліотеки і файли для тестів, які були продемонстровані в статті.

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

    Erase (AreaColorCurrent ()); uint lines_total = :: ArraySize (m_lines); m_text_cursor_y_pos = (m_text_cursor_y_pos> = lines_total)?

    Новости