Разработка кроссплатформенных приложений с использованием Juce

Ввод и отображение текстовой информации

Разбить на страницы
Показывать лекцию целиком

Цель лекции: Научиться применять компоненты ввода и отображения текстовой информации

В библиотеке Juce имеется немного компонентов, позволяющих отображать, вводить и редактировать текстовую информацию. Связано это с тем, что, во-первых, в отличие от других GUI toolkit'ов компоненты Juce обладают внушительным числом свойств и могут использоваться с различными целями: например, уже знакомый нам класс Label может использоваться не только для отображения текста, но и для пользовательского ввода; класс TextEditor, в зависимости от программных настроек, может выступать и как однострочное, и многострочное поле редактирования и т.п. Во-вторых, Juce обладает богатыми возможностями рисования, в том числе вывода текста (см. лекцию 7 ). Рисовать можно не только на любом визуальном компоненте, но и на полосе заголовка главного окна, что выгодно отличает Juce от других кроссплатформенных библиотек.

Ниже приведён перечень классов компонентов Juce, ориентированных на ввод и отображение текста с их краткими характеристиками:

  • Label (метка, ярлык) — класс преимущественно отвечает за отображение текста, который не изменятся пользователем. Но в зависимости от настроек может отображать и редактируемый текст (переход в режим редактирования, в зависимости от выбора программиста, возможен как по одинарному, так и по двойному щелчку мышью).
  • TextEditor (текстовый редактор) — класс с довольно богатыми возможностями. В зависимости от настроек объект этого класса может вступать как
  • компонент для отображения, ввода и редактирования однострочных текстов;
  • поле для ввода символов в соответствии с шаблоном (окно ввода пароля, окно для ввода цифр и т.п.);
  • компонент для отображения, ввода и редактирования многострочных текстов.
  • CodeEditorComponent – компонент текстовый редактор, ориентированный на отображение исходного кода программы в соответствии с заданным шаблоном.
  • Класс Label используется в основном для отображения различных поясняющих надписей, как с возможностью их редактирования пользователем, так и без. Примеры вывода текстов с помощью этого компонента мы видели в предыдущих лекциях, начиная с самых простых программ. Пожалуй, это один из наиболее часто используемых компонентов.

    Возможность редактирования надписи ярлыка задаётся методом void Label::setEditable(bool editOnSingleClick, bool editOnDoubleClick = false, bool lossOfFocusDiscardsChanges = false). В случае, если параметр lossOfFocusDiscardsChanges принимает значение true, то щелчок мышью где-либо во время редактирования ярлыка, отменяет внесённые в текст изменения.

    Текст, отображаемый компонентом класса Label, имеет тип строки String и задаётся в конструкторе Label::Label(const String componentName = String::empty, const String labelText = String::empty), где componentName — название ярлыка, необходимое для нужд отладки, а labelText — отображаемая надпись. Кроме того, задать текст ярлыка можно с помощью функции void Label::setText(const String newText, bool broadcastChangeMessage), где newText — новый текст. Второй параметр необходим для генерации сообщения об измнении текста ярлыка (в том случае, если он принимает значение true), однако в большинстве случаев ему можно присвоить значение false.

    Получить текущий текст ярлыка можно с помощью метода const String Label::getText(bool returnActiveEditorContents = false) const. Принимаемый параметр необходим лишь в том случае, если ярлык находится в стадии редактирования текста пользователем. Если параметр принимает значение true, функция возвращает текст, отображаемый ярлыком на момент её вызова. В том случае, если параметр принимает значение false, функция вернёт текст не раньше, чем пользователь закончит его набор и нажмёт клавишу <ENTER>. В том случае, если возможность редактирования надписи запрещена, принимаемый функцией параметр неважен.

    Для ввода в ярлык смешанной информации, например, строк и цифр, необходимо воспользоваться оператором или методом склеивания (конкатенации) строк класса ):

    Label* pWorkersLabel = new Label("WorkersLabel");
    String sWorkers = String(T("Число рабочих: ")) += String(iWorkers);
    pWorkersLabel->setText(sWorkers, false);
    

    Каждый ярлык имеет следующие цветовые характеристики: цвет границы, цвет фона и цвет отображаемого текста. Тот или иной цвет задаётся методом, наследуемым классом Label от Component, void Component::setColour(int colourId, const Colour colour). Первый параметр, принимаемый функцией, показывает, какой именно цвет (границы, фона или текста) ярлыка следует изменить, а второй задаёт собственно этот цвет.

    Значения ID цвета можно задавать с помощью элементов нумерованного списка, входящего в состав класса Label: enum ColourIds {backgroundColourId = 0x1000280, textColourId = 0x1000281, outlineColourId = 0x1000282} (цвет фона, текста и внешней границы надписи, соответственно).

    Кроме того, оформление надписи можно изменить, задав характеристики шрифта, Font (см. лекцию 6 ). Новый шрифт ярлыка задаётся методом void Label::setFont(const Font newFont). Получить текущий шрифт надписи можно с помощью функции const Font Label::getFont() const throw().

    Выравнивание текста надписи относительно границ компонента осуществляет его метод void Label::setJustificationType(const Justification justification). Характер выравнивания задаётся объектом класса Justification, который включает в себя нумерованный список

    enum
    { 
      left = 1, right = 2, horizontallyCentred = 4, top = 8, 
      bottom = 16, verticallyCentred = 32, horizontallyJustified = 64, centred = 36, 
      centredLeft = 33, centredRight = 34, centredTop = 12, centredBottom = 20, 
      topLeft = 9, topRight = 10, bottomLeft = 17, bottomRight = 18 
    }
    

    Элементы списка — флаги выравнивания — могут комбинироваться между собой. Их значения приведены ниже:

  • left — флаг означает, что объект (в нашем случае текст ярлыка) должен быть выровнен по левому краю доступного пространства компонента;
  • right — по правому краю;
  • horizontallyCentred - объект (текст) должен быть выровнен по горизонтали между левым и правым краями краю доступного пространства компонента;
  • top — по верхему краю;
  • bottom — по нижнему краю;
  • verticallyCentred — объект (текст) должен быть выровнен по вертикали (между верхним и нижним краями ярлыка)
  • horizontallyJustified — флаг показывает, что текст должен быть растянут таким образом, чтобы заполнить всё доступное пространство между правым и левым краями ярлыка;
  • centred — выровнен как по вертикали, так и по горизонтали;
  • centredLeft — объект должен быть выровнен по вертикали; по горизонтали текст выравнивается по левому краю;
  • centredRight — то же, но горизонтальное выравнивание — по правому краю;
  • centredTop — по горизонтали текст выровнен по центру, по вертикали — у верхнего края ярлыка;
  • centredBottom — то же, но вертикальное выравнивание — по нижнему краю компонента;
  • topLeft — показывает, что объект должен быть размещён в верхне-левом углу компонента;
  • topRight — в верхнем правом углу;
  • bottomLeft — в нижнем левом углу;
  • bottomRight — в нижнем правом углу.
  • Размещение ярлыка в компоненте-контейнере (обычно это компонент содержимого), определяется свойствами и методами, наследуемыми от класса Component (см. лекцию 3 ).

    Класс TextEditor

    В большинстве GUI framework-ов имеются отдельные компоненты или виджеты для создания однострочного и многострочного полей ввода (например, классы QLineEdit и QTextEdit в Qt). В библиотеке Juce для этих целей используется один и тот же класс, TextEditor, внешний вид и поведение экземпляров которого может значительно различаться, в зависимости от настроек, задаваемых программистом.

    Виджеты этого класса предоставляют пользователю возможности для ввода и редактирования данных, текста и чисел, а также обмена данными с буфером обмена (вырезание, копирование и вставка), что избавляет разработчика от дополнительной реализации. Текст можно выделять с помощью мыши, клавиатуры и контекстного меню.

    Ввод текста пользователем в виджете TextEditor может осуществляться как в одну строку, так и в несколько строк. Режим ввода задаётся методом void TextEditor::setMultiLine(bool shouldBeMultiLine, bool shouldWordWrap = true). Эту функцию необходимо вызвать сразу же после объявления объекта класса TextEditor (впрочем, если вы хотите получить однострочное поле ввода, то её можно не вызывать). Если первый из её параметров принимает значение false, то виджет принимает вид однострочного поля ввода, а если true, то появляется возможность вводить несколько строк. Если значение второго параметра равно true, то в виджете перенос строк будет осуществляться автоматически по достижении его края при наборе текста. В противном случае перенос строки будет осуществляться исключительно по нажатию клавиши <ENTER>.

    Текст, находящийся в виджете, возвращает метод const String TextEditor::getText() const. Новый текст в нём задаёт функция void TextEditor::setText(const String newText, bool sendTextChangeMessage = true). Она очищает поле ввода и вставляет в виджет текст newText. В том случае, если параметр sendTextChangeMessage принимает значение true, то посылается сообщение об изменении текста всем слушателям (listeners) поля ввода.

    Для помещения текста в область виджета, помимо метода setText, можно воспользоваться функцией void TextEditor::insertTextAtCaret(const String textToInsert), которая вставляет текст textToInsert на текущей позиции курсора. В случае, если какой-то участок текста был выделен, то при вызове функции он заменяется на строку-параметр. Этим можно воспользоваться для программного удаления какого-либо участка текста: вызвать вначале функцию-член void TextEditor::setHighlightedRegion(const Range< int > newSelection), чтобы выделить участок текста, а затем — метод insertTextAtCaret с путой строкой (String::empty) в качестве параметра.

    Как правило, нажатие пользователем клавиши <ENTER> на клавиатуре означает завершение ввода строки и переход на новую. Однако в некоторых случаях требуется иная реакция на нажатие этой клавиши. С помощью функции void TextEditor::setReturnKeyStartsNewLine(bool shouldStartNewLine) можно изменить поведение текстового поля ввода на нажатие клавиши <ENTER>. Если параметр shouldStartNewLine принимает значение false, то нажатие на клавишу не приводит к переходу на новую строку, а сообщение об этом событии будет обработано в слушателе поля ввода (TextEditor::Listener). В случае, если поле ввода является однострочным, метод setReturnKeyStartsNewLine можно не вызывать.

    Вызов метода void TextEditor::setReadOnly(bool shouldBeReadOnly) с параметром true устанавливает режим, который даёт возможность только просмотра текста, но не редактирования его пользователем. В случае необходимости изменить текст, его можно передать виджет программно, например, функцией setText.

    В случае, если объект класса TextEditor используется как однострочное поле ввода, можно включить режим ввода пароля. Этот режим устанавливается в конструкторе компонента TextEditor::TextEditor(const String componentName = String::empty, juce_wchar passwordCharacter = 0). В случае, если второй параметр принимает значение, отличное от нуля, то все вводимые пользователем символы будут замещаться каким-либо другим. Параметр passwordCharacter, собственно, и содержит код замещающего символа. Подробнее с использованием TextEditor в качестве поля ввода пароля можно познакомиться на примере, приведённом ниже.

    В случае, если вы используете объект класса TextEditor в качестве многострочного поля ввода, отображаемый текст может превышать размеры виджета. При этом просмотр текста возможен с помощью полос прокрутки. Для их автоматического отображения необходимо вызвать метод void TextEditor::setScrollbarsShown(bool shouldBeEnabled) с параметром true. Если толщина полос прокрутки по умолчанию вас по каким-либо причинам не устраивает, её можно задать программно вызовом функции void TextEditor::setScrollBarThickness(int newThicknessPixels), где параметр — толщина используемых полос прокрутки в пикселях.

    Класс TextEditor включает ряд методов для работы с буфером обмена:

  • void TextEditor::copy() - копирует выделенную область в буфер обмена;
  • void TextEditor::cut() - также копирует выделенную область в буфер обмена, но с последующим её удалением;
  • void TextEditor::paste() - вставляет содержимое буфера обмена в поле ввода за позицией курсора.
  • Также класс TextEditor включает ряд методов редактирования текста:
  • void TextEditor::doUndoRedo(bool isRedo) — в зависимости от параметра повторяет или отменяет проделанное действие пользователя по редактированию;
  • void TextEditor::setSelectAllWhenFocused(bool b) — выделяет весь текст в поле ввода, когда оно в фокусе (параметр должен принимать значение true);
  • void TextEditor::clear() - очищает поле ввода.
  • Зачастую возникает ситуация, когда необходимо разрешить пользователю ввод только определённого набора символов (шаблон или маска ввода). Задать маску ввода можно вызовом метода void TextEditor::setInputRestrictions(int maxTextLength, const String allowedCharacters = String::empty), где maxTextLength — максимально допустимая длина вводимого текста (в символах), а allowedCharacters — строка, содержащая допустимые символы. В случае, если первый параметр принимает значение 0, ограничений на длину ввода нет. Подробнее с использованием маски ввода можно познакомиться на примере, приведённом ниже.

    Рассмотрим использование компонентов Label и TextEditor на примере программы, внешний вид которой показан на рисунке 8.1 .

    (рис 8.1) Программа, демонстрирующая ввод и отображение текстовой информации

    Окно программы разделено на три секции. В качестве разделителей используются два объекта класса ).

    // Создание ярлыка, надпись которого содержит пустую строку
    pSeparator = new Label("Separator", String::empty);
    // Запрет на редактирование
    pSeparator->setEditable(false, false, false);
    // Задаём цвет контура ярлыка
    pSeparator->setColour(Label::backgroundColourId, Colour(0x3d000000));
    addAndMakeVisible(pSeparator);
    
    // Реализация метода TCentralComponent::resize();
    // Высота виджета — 1 пиксель
    pSeparator->setBounds(8, 136, 704, 1);
    

    Первая секция нашей программы будет содержать многострочное поле ввода без возможности непосредственного редактирования текста пользователем (только чтение), TextEditor* pTextEditor; однострочное поле, TextEditor* pAppendTextEdit, для ввода пользователем текста, который будет добавляться в TextEditor после нажатия на кнопку, а также сами кнопки:

  • TextButton* pClearButton — очищает поле ввода;
  • TextButton* pAppendTextButton — добавляет текст из pAppendTextEdit в конец имеющегося в компоненте pTextEditor;
  • TextButton* pSetTextButton — замещает текст компонента pTextEditor текстом из pAppendTextEdit.
  • Кроме того, в секции имеется выпадающий список ).
  • #include "TCentralComponent.h"
    //----------------------------------------------------
    #define tr(s) String::fromUTF8(s)
    //----------------------------------------------------
    TCentralComponent::TCentralComponent() : Component("Central Component"),
                pTextEditor(0),
                pAppendTextEdit(0),
                pFontSelectionBox(0),
                pClearButton(0),
                pAppendTextButton(0),
                pSetTextButton(0),
                pSeparator(0),
                pLabel(0),
                pLabelGroup(0),
                pReadOnlyButton(0),
                pEditByOneClickButton(0),
                pEditByDoubleClickButton(0),
                pSeparator1(0)
    {
      // Создаём новое поле ввода (текстовый редактор)
      pTextEditor = new TextEditor("TextEditor");
      // Разрешаем ввод более одной строки
      // и разрешаем автоматический перенос строк
      pTextEditor->setMultiLine(true, true);
      // Запрет на редактирование
      pTextEditor->setReadOnly(true);
      // Показываем полосы прокрутки
      pTextEditor->setScrollbarsShown(true);
      addAndMakeVisible(pTextEditor);
    
      // Шрифт для ранее созданного поля ввода
      Font TextEditorFont;
      // Гарнитура - шрифт Sans-Serif по умолчанию
      TextEditorFont.setTypefaceName(Font::getDefaultSerifFontName());
      TextEditorFont.setHeight(20);
      // Устанавливаем шрифт TextEditorFont в качестве
      // шрифта поля ввода
      pTextEditor->setFont(TextEditorFont);
      pTextEditor->setTextToShowWhenEmpty("Text Editor", Colours::grey);
    
      // Создаём поле ввода для текста, добавляемого
      // в многострочное поле pTextEditor
      pAppendTextEdit = new TextEditor("AppendTextEdit");
      // Ввод текста - в одну строку
      pAppendTextEdit->setMultiLine(false);
      // Разрешаем ввод текста
      pAppendTextEdit->setReadOnly(false);
      // Показываем полосы прокрутки
      pAppendTextEdit->setScrollbarsShown(false);
      // Делаем видимым курсор
      pAppendTextEdit->setCaretVisible(true);
      // Разрешаем стандартное контекстное меню
      pAppendTextEdit->setPopupMenuEnabled(true);
      // Текст поля - пустая строка
      pAppendTextEdit->setText(String::empty);
      addAndMakeVisible(pAppendTextEdit);
    
      // Выпадающий список выбора шрифта
      pFontSelectionBox = new ComboBox("FontSelectionBox");
      pFontSelectionBox->setEditableText(false);
      pFontSelectionBox->setJustificationType(Justification::centredLeft);
      pFontSelectionBox->setTextWhenNothingSelected(tr("Выбрать"));
      // Устанавливаем в качестве слушателя выпадающего списка
      // сам компонент-контейнер
      pFontSelectionBox->addListener(this);
      addAndMakeVisible(pFontSelectionBox);
    
      // Получаем имена системных шрифтов, сохраняем их 
      // в строковом массиве...
      StringArray sSystemFonts = Font::findAllTypefaceNames();
      // и добавляем в выпадающий список
      for(int i = 0; i < sSystemFonts.size(); i++)
      {
        pFontSelectionBox->addItem(sSystemFonts[i], i + 1);
      }
    
      // Кнопка, очищающая поле pTextEditor
      pClearButton = new TextButton("ClearButton");
      pClearButton->setButtonText(tr("Очистить"));
      // Устанавливаем в качестве слушателя кнопки
      // сам компонент-контейнер
      pClearButton->addListener(this);
      addAndMakeVisible(pClearButton);
    
      // Кнопка, добавляющая текст в поле pTextEditor
      pAppendTextButton = new TextButton("AppendTextButton");
      pAppendTextButton->setButtonText(tr("Добавить текст"));
      pAppendTextButton->addListener(this);
      addAndMakeVisible(pAppendTextButton);
    
      // Кнопка, замещающая текст в поле pTextEditor
      pSetTextButton = new TextButton("SetTextButton");
      pSetTextButton->setButtonText(tr("Заменить текст"));
      pSetTextButton->addListener(this);
      addAndMakeVisible(pSetTextButton);
    

    Практически все методы класса TextEditor, использованные в примере, были рассмотрены нами ранее. Незнакома только функция void TextEditor::setTextToShowWhenEmpty(const String text, const Colour colourToUse), которая отображает текст text (в нашем случае это надпись "Text Editor") цветом colourToUse в том случае, если компонент пуст, т.е. не содержит другого текста.

    При запуске демонстрационной программы текст в виджете ).

    void TCentralComponent::comboBoxChanged(ComboBox* pComboBox)
    {
      // Изменён выпадающий список гарнитуры шрифта
      if(pComboBox == pFontSelectionBox)
      {
        // Устанавливаем в качестве шрифта текста поля ввода
        // шрифт, выбранный пользователем из списка
        Font CurrentFont;
        CurrentFont.setTypefaceName(pFontSelectionBox->getText());
        CurrentFont.setHeight(20);
        // Задаём шрифт для отображения текста
        pTextEditor->applyFontToAllText(CurrentFont);
      }
    }
    

    В примере для задания свойств текста мы использовали метод void TextEditor::applyFontToAllText(const Font newFont), который применяет характеристики шрифта newFont ко всему содержимому поля ввода.

    Однако на практике гораздо чаще требуется, чтобы различные части текста отображались разными шрифтами. В этом случае можно воспользоваться методом void TextEditor::setFont(const Font newFont), который применяет свойства шрифта newFont только к вновь добавляемому тексту (как вводимому пользователем, так и добавляемому программно).

    Реализация обработчика событий щелчка по кнопке компонента содержимого представлена в листинге 8.5 ; она понятна без каких-либо дополнительных комментариев.

    void TCentralComponent::buttonClicked(Button* pButton)
    {
      // Если нажата кнопка "Очистить"...
      if(pButton == pClearButton)
      {
        // Очистить поле ввода
        pTextEditor->clear();
      }
      // Если нажата кнопка "Добавить текст"...
      else if(pButton == pAppendTextButton)
      {
        // Вставить текст за курсором
        pTextEditor->insertTextAtCaret(pAppendTextEdit->getText());
        pTextEditor->insertTextAtCaret("\n");
      }
      // Если нажата кнопка "Заменить текст"...
      else if(pButton == pSetTextButton)
      {
        // Сделать замену
        pTextEditor->setText(pAppendTextEdit->getText());
        pTextEditor->insertTextAtCaret("\n");
      }
      // ...
    } 
    

    Вторая секция нашей программы демонстрирует работу с режимами редактирования ярлыка ). Подчеркнём, что объединение радиокнопок в одну группу произошло лишь графически, т.е. они рисуются на поверхности компонента ).

      // Ярлык с возможностью ввода текста
      pLabel = new Label("Label", "Label");
      pLabel->setFont(Font(15.0000f, Font::bold));
      pLabel->setJustificationType(Justification::centred);
      // Запрет на редактирование
      pLabel->setEditable(false, false, false);
      // Цвет текста надписи
      pLabel->setColour(Label::textColourId, Colour(0xff0f1465));
      // Цвет рамки
      pLabel->setColour(Label::outlineColourId, Colour(0xf6615e5e));
      pLabel->setColour(Label::backgroundColourId, Colour(0x0));
      addAndMakeVisible(pLabel);
    
      pLabelGroup = new GroupComponent();
      pLabelGroup->setText(tr("Редактирование"));
      addAndMakeVisible(pLabelGroup);
    
      // Радиокнопка - запрет на редактирование ярлыка
      pReadOnlyButton = new ToggleButton("ReadOnlyButton");
      pReadOnlyButton->setButtonText(tr("Только чтение"));
      // Устанавливаем в качестве слушателя кнопки
      // сам компонент-контейнер
      pReadOnlyButton->addListener(this);
      // Радиокнопка отмечена
      pReadOnlyButton->setToggleState(true, false);
      pReadOnlyButton->setRadioGroupId(1234);
      addAndMakeVisible(pReadOnlyButton);
    
      // Радиокнопка - редактирование ярлыка по щелчку
      pEditByOneClickButton = new ToggleButton("EditByOneClickButton");
      pEditByOneClickButton->setButtonText(tr("Одним щелчком"));
      pEditByOneClickButton->addListener(this);
      // Радиокнопка не отмечена
      pEditByOneClickButton->setToggleState(false, false);
      pEditByOneClickButton->setRadioGroupId(1234);
      addAndMakeVisible(pEditByOneClickButton);
    
      // Радиокнопка - редактирование ярлыка по двойному щелчку
      pEditByDoubleClickButton = new ToggleButton("EditByDoubleClickButton");
      pEditByDoubleClickButton->setButtonText(tr("Двойным щелчком"));
      pEditByDoubleClickButton->addListener(this);
      pEditByDoubleClickButton->setToggleState(false, false);
      pEditByDoubleClickButton->setRadioGroupId(1234);
      addAndMakeVisible(pEditByDoubleClickButton);
    

    Переключение режимов редактирования реализовано в методе нашего класса компонента содержимого )

      else if(pButton == pReadOnlyButton)
      {
        pLabel->setEditable(false, false, false);
      }
      else if(pButton == pEditByOneClickButton)
      {
        pLabel->setEditable(true, false, false);
      }
      else if(pButton == pEditByDoubleClickButton)
      {
        pLabel->setEditable(false, true, false);
      }
    

    И наконец рассмотрим использование объекта класса ). Сделаем строку ).

    pPasswordLabel = new Label("PasswordLabel", tr("Введите пароль: "));
      pPasswordLabel->setEditable(false, false, false);
      addAndMakeVisible(pPasswordLabel);
    
      // Поле ввода пароля
      pPasswordEdit = new TextEditor("PasswordEdit", 0x2022);
      // Разрешаем ввод цифр; ограничений на длину вводимой строки нет
      pPasswordEdit->setInputRestrictions(0, "1234567890");
      pPasswordEdit->setMultiLine(false);
      pPasswordEdit->setScrollbarsShown(false);
      pPasswordEdit->setReturnKeyStartsNewLine(false);
      pPasswordEdit->addKeyListener(this);
      addAndMakeVisible(pPasswordEdit);
    
      // Кнопка ввода пароля
      pSubmitButton = new TextButton("SubmitButton");
      pSubmitButton->setButtonText(tr("Ввод"));
      pSubmitButton->addListener(this);
      addAndMakeVisible(pSubmitButton);
    
      setSize(728, 400);
    
      // Задаём пароль
      sPassword = "1234";
    

    Числовой код ), выбран не случайно. Это код символа так называемой пули или дробины (bullet), которая одинаково отображается как в Linux, так и в Windows.

    Проверку корректности введённого пароля реализуем в виде отдельной функции-члена класса ).

    void TCentralComponent::SubmitPassword()
    {
      // Если введённый текст не содержит пустую строку
      // или строку, заполненную пробелами
      if(pPasswordEdit->getText().containsNonWhitespaceChars())
      {
        // И если пароль соответствует введённому тексту
        if(pPasswordEdit->getText() == sPassword)
        {
          // Выводим информационное сообщение
          pPasswordLabel->setColour(Label::textColourId, Colours::blue);
          pPasswordLabel->setText(tr("Принято!"), false);
          // Делаем недоступными кнопку и поле ввода
          pPasswordEdit->setEnabled(false);
          pSubmitButton->setEnabled(false);
        }
        else
        {
          pPasswordLabel->setColour(Label::textColourId, Colours::red);
          pPasswordLabel->setText(tr(
                "Пароль неверен! Введите повторно:"), false);
          pPasswordEdit->clear();
        }
      }
    }
    

    В этом примере мы вначале проверяем не является ли введённый пользователем текст пустой (в том числе заполненной одними пробелами) строкой. Собственно, эта проверка является излишней, мы, посредством функции setInputRestrictions (см. листинг 8.8 ) задали набор символов, разрешённых к вводу, где нет пароля, так что можно было бы ограничиться проверкой

    if(pPasswordEdit->getText() != String::empty
    

    Метод containsNonWhitespaceChars был использован исключительно в качестве демонстрации.

    Затем, собственно, производится проверка валидности пароля и вывод информирующих сообщений: синим цветом, если пароль правильный или красным — в противном случае.

    Теперь остаётся только вызвать функцию ).

      else if(pButton == pSubmitButton)
      {
        SubmitPassword();
      }
    

    Многие пользователи, в том числе и автор этой лекции, предпочитают по завершении ввода пароля не щёлкать мышью по кнопке, а нажимать клавишу <ENTER> для ввода. Реализуем эту возможность в нашей программе (рассмотрен лишь один из возможных способов). Для того, чтобы наш компонент содержимого мог отслеживать нажатия клавиш клавиатуры пользователем, унаследуем его класс от слушателя клавиш KeyListener ().

    class TCentralComponent  : public Component,
               public ComboBoxListener,
               public ButtonListener,
               public KeyListener // слушатель клавиш
    

    Напишем реализацию чистой виртуальной функции-члена класса слушателя .

    bool TCentralComponent::keyPressed(const KeyPress Key, 
                      Component* pComponent)
    {
      // Если в фокусе другое поле ввода - выходим
      if(pComponent != pPasswordEdit) return false;
      // Если нажата не клавиша <ENTER> - выходим
      else if(Key != KeyPress::returnKey) return false;
      else SubmitPassword();
      return true;
    }
    

    Запустим откомпилированную демонстрационную программу и убедимся, что ввод пароля осуществляется как при щелчке по кнопке, так и при нажатии клавиши <ENTER>.

    Краткие итоги

    В Juce для отображения текстовой информации на компонентах используется преимущественно два виджета: Label и TextEditor. Они могут иметь различный внешний вид и реакцию на действия пользователя за счёт опций, задаваемых программистом.

    Упражнение

    Напишите простейший текстовый редактор с возможностью автоматического переноса строк при достижении границы поля ввода. Предусмотрите возможность изменения параметров шрифта вводимого текста (с помощью кнопок).

    Дополнительные материал

    Архив с исходными текстами примера Вы можете скачать здесь

    Страницы:

    Цель лекции: Научиться применять компоненты ввода и отображения текстовой информации

    В библиотеке Juce имеется немного компонентов, позволяющих отображать, вводить и редактировать текстовую информацию. Связано это с тем, что, во-первых, в отличие от других GUI toolkit'ов компоненты Juce обладают внушительным числом свойств и могут использоваться с различными целями: например, уже знакомый нам класс Label может использоваться не только для отображения текста, но и для пользовательского ввода; класс TextEditor, в зависимости от программных настроек, может выступать и как однострочное, и многострочное поле редактирования и т.п. Во-вторых, Juce обладает богатыми возможностями рисования, в том числе вывода текста (см. лекцию 7 ). Рисовать можно не только на любом визуальном компоненте, но и на полосе заголовка главного окна, что выгодно отличает Juce от других кроссплатформенных библиотек.

    Ниже приведён перечень классов компонентов Juce, ориентированных на ввод и отображение текста с их краткими характеристиками:

  • Label (метка, ярлык) — класс преимущественно отвечает за отображение текста, который не изменятся пользователем. Но в зависимости от настроек может отображать и редактируемый текст (переход в режим редактирования, в зависимости от выбора программиста, возможен как по одинарному, так и по двойному щелчку мышью).
  • TextEditor (текстовый редактор) — класс с довольно богатыми возможностями. В зависимости от настроек объект этого класса может вступать как
  • компонент для отображения, ввода и редактирования однострочных текстов;
  • поле для ввода символов в соответствии с шаблоном (окно ввода пароля, окно для ввода цифр и т.п.);
  • компонент для отображения, ввода и редактирования многострочных текстов.
  • CodeEditorComponent – компонент текстовый редактор, ориентированный на отображение исходного кода программы в соответствии с заданным шаблоном.
  • Класс Label используется в основном для отображения различных поясняющих надписей, как с возможностью их редактирования пользователем, так и без. Примеры вывода текстов с помощью этого компонента мы видели в предыдущих лекциях, начиная с самых простых программ. Пожалуй, это один из наиболее часто используемых компонентов.

    Возможность редактирования надписи ярлыка задаётся методом void Label::setEditable(bool editOnSingleClick, bool editOnDoubleClick = false, bool lossOfFocusDiscardsChanges = false). В случае, если параметр lossOfFocusDiscardsChanges принимает значение true, то щелчок мышью где-либо во время редактирования ярлыка, отменяет внесённые в текст изменения.

    Текст, отображаемый компонентом класса Label, имеет тип строки String и задаётся в конструкторе Label::Label(const String componentName = String::empty, const String labelText = String::empty), где componentName — название ярлыка, необходимое для нужд отладки, а labelText — отображаемая надпись. Кроме того, задать текст ярлыка можно с помощью функции void Label::setText(const String newText, bool broadcastChangeMessage), где newText — новый текст. Второй параметр необходим для генерации сообщения об измнении текста ярлыка (в том случае, если он принимает значение true), однако в большинстве случаев ему можно присвоить значение false.

    Получить текущий текст ярлыка можно с помощью метода const String Label::getText(bool returnActiveEditorContents = false) const. Принимаемый параметр необходим лишь в том случае, если ярлык находится в стадии редактирования текста пользователем. Если параметр принимает значение true, функция возвращает текст, отображаемый ярлыком на момент её вызова. В том случае, если параметр принимает значение false, функция вернёт текст не раньше, чем пользователь закончит его набор и нажмёт клавишу <ENTER>. В том случае, если возможность редактирования надписи запрещена, принимаемый функцией параметр неважен.

    Для ввода в ярлык смешанной информации, например, строк и цифр, необходимо воспользоваться оператором или методом склеивания (конкатенации) строк класса ):

    Label* pWorkersLabel = new Label("WorkersLabel");
    String sWorkers = String(T("Число рабочих: ")) += String(iWorkers);
    pWorkersLabel->setText(sWorkers, false);
    

    Каждый ярлык имеет следующие цветовые характеристики: цвет границы, цвет фона и цвет отображаемого текста. Тот или иной цвет задаётся методом, наследуемым классом Label от Component, void Component::setColour(int colourId, const Colour colour). Первый параметр, принимаемый функцией, показывает, какой именно цвет (границы, фона или текста) ярлыка следует изменить, а второй задаёт собственно этот цвет.

    Значения ID цвета можно задавать с помощью элементов нумерованного списка, входящего в состав класса Label: enum ColourIds {backgroundColourId = 0x1000280, textColourId = 0x1000281, outlineColourId = 0x1000282} (цвет фона, текста и внешней границы надписи, соответственно).

    Кроме того, оформление надписи можно изменить, задав характеристики шрифта, Font (см. лекцию 6 ). Новый шрифт ярлыка задаётся методом void Label::setFont(const Font newFont). Получить текущий шрифт надписи можно с помощью функции const Font Label::getFont() const throw().

    Выравнивание текста надписи относительно границ компонента осуществляет его метод void Label::setJustificationType(const Justification justification). Характер выравнивания задаётся объектом класса Justification, который включает в себя нумерованный список

    enum
    { 
      left = 1, right = 2, horizontallyCentred = 4, top = 8, 
      bottom = 16, verticallyCentred = 32, horizontallyJustified = 64, centred = 36, 
      centredLeft = 33, centredRight = 34, centredTop = 12, centredBottom = 20, 
      topLeft = 9, topRight = 10, bottomLeft = 17, bottomRight = 18 
    }
    

    Элементы списка — флаги выравнивания — могут комбинироваться между собой. Их значения приведены ниже:

  • left — флаг означает, что объект (в нашем случае текст ярлыка) должен быть выровнен по левому краю доступного пространства компонента;
  • right — по правому краю;
  • horizontallyCentred - объект (текст) должен быть выровнен по горизонтали между левым и правым краями краю доступного пространства компонента;
  • top — по верхему краю;
  • bottom — по нижнему краю;
  • verticallyCentred — объект (текст) должен быть выровнен по вертикали (между верхним и нижним краями ярлыка)
  • horizontallyJustified — флаг показывает, что текст должен быть растянут таким образом, чтобы заполнить всё доступное пространство между правым и левым краями ярлыка;
  • centred — выровнен как по вертикали, так и по горизонтали;
  • centredLeft — объект должен быть выровнен по вертикали; по горизонтали текст выравнивается по левому краю;
  • centredRight — то же, но горизонтальное выравнивание — по правому краю;
  • centredTop — по горизонтали текст выровнен по центру, по вертикали — у верхнего края ярлыка;
  • centredBottom — то же, но вертикальное выравнивание — по нижнему краю компонента;
  • topLeft — показывает, что объект должен быть размещён в верхне-левом углу компонента;
  • topRight — в верхнем правом углу;
  • bottomLeft — в нижнем левом углу;
  • bottomRight — в нижнем правом углу.
  • Размещение ярлыка в компоненте-контейнере (обычно это компонент содержимого), определяется свойствами и методами, наследуемыми от класса Component (см. лекцию 3 ).

    Класс TextEditor

    В большинстве GUI framework-ов имеются отдельные компоненты или виджеты для создания однострочного и многострочного полей ввода (например, классы QLineEdit и QTextEdit в Qt). В библиотеке Juce для этих целей используется один и тот же класс, TextEditor, внешний вид и поведение экземпляров которого может значительно различаться, в зависимости от настроек, задаваемых программистом.

    Виджеты этого класса предоставляют пользователю возможности для ввода и редактирования данных, текста и чисел, а также обмена данными с буфером обмена (вырезание, копирование и вставка), что избавляет разработчика от дополнительной реализации. Текст можно выделять с помощью мыши, клавиатуры и контекстного меню.

    Ввод текста пользователем в виджете TextEditor может осуществляться как в одну строку, так и в несколько строк. Режим ввода задаётся методом void TextEditor::setMultiLine(bool shouldBeMultiLine, bool shouldWordWrap = true). Эту функцию необходимо вызвать сразу же после объявления объекта класса TextEditor (впрочем, если вы хотите получить однострочное поле ввода, то её можно не вызывать). Если первый из её параметров принимает значение false, то виджет принимает вид однострочного поля ввода, а если true, то появляется возможность вводить несколько строк. Если значение второго параметра равно true, то в виджете перенос строк будет осуществляться автоматически по достижении его края при наборе текста. В противном случае перенос строки будет осуществляться исключительно по нажатию клавиши <ENTER>.

    Текст, находящийся в виджете, возвращает метод const String TextEditor::getText() const. Новый текст в нём задаёт функция void TextEditor::setText(const String newText, bool sendTextChangeMessage = true). Она очищает поле ввода и вставляет в виджет текст newText. В том случае, если параметр sendTextChangeMessage принимает значение true, то посылается сообщение об изменении текста всем слушателям (listeners) поля ввода.

    Для помещения текста в область виджета, помимо метода setText, можно воспользоваться функцией void TextEditor::insertTextAtCaret(const String textToInsert), которая вставляет текст textToInsert на текущей позиции курсора. В случае, если какой-то участок текста был выделен, то при вызове функции он заменяется на строку-параметр. Этим можно воспользоваться для программного удаления какого-либо участка текста: вызвать вначале функцию-член void TextEditor::setHighlightedRegion(const Range< int > newSelection), чтобы выделить участок текста, а затем — метод insertTextAtCaret с путой строкой (String::empty) в качестве параметра.

    Как правило, нажатие пользователем клавиши <ENTER> на клавиатуре означает завершение ввода строки и переход на новую. Однако в некоторых случаях требуется иная реакция на нажатие этой клавиши. С помощью функции void TextEditor::setReturnKeyStartsNewLine(bool shouldStartNewLine) можно изменить поведение текстового поля ввода на нажатие клавиши <ENTER>. Если параметр shouldStartNewLine принимает значение false, то нажатие на клавишу не приводит к переходу на новую строку, а сообщение об этом событии будет обработано в слушателе поля ввода (TextEditor::Listener). В случае, если поле ввода является однострочным, метод setReturnKeyStartsNewLine можно не вызывать.

    Вызов метода void TextEditor::setReadOnly(bool shouldBeReadOnly) с параметром true устанавливает режим, который даёт возможность только просмотра текста, но не редактирования его пользователем. В случае необходимости изменить текст, его можно передать виджет программно, например, функцией setText.

    В случае, если объект класса TextEditor используется как однострочное поле ввода, можно включить режим ввода пароля. Этот режим устанавливается в конструкторе компонента TextEditor::TextEditor(const String componentName = String::empty, juce_wchar passwordCharacter = 0). В случае, если второй параметр принимает значение, отличное от нуля, то все вводимые пользователем символы будут замещаться каким-либо другим. Параметр passwordCharacter, собственно, и содержит код замещающего символа. Подробнее с использованием TextEditor в качестве поля ввода пароля можно познакомиться на примере, приведённом ниже.

    В случае, если вы используете объект класса TextEditor в качестве многострочного поля ввода, отображаемый текст может превышать размеры виджета. При этом просмотр текста возможен с помощью полос прокрутки. Для их автоматического отображения необходимо вызвать метод void TextEditor::setScrollbarsShown(bool shouldBeEnabled) с параметром true. Если толщина полос прокрутки по умолчанию вас по каким-либо причинам не устраивает, её можно задать программно вызовом функции void TextEditor::setScrollBarThickness(int newThicknessPixels), где параметр — толщина используемых полос прокрутки в пикселях.

    Класс TextEditor включает ряд методов для работы с буфером обмена:

  • void TextEditor::copy() - копирует выделенную область в буфер обмена;
  • void TextEditor::cut() - также копирует выделенную область в буфер обмена, но с последующим её удалением;
  • void TextEditor::paste() - вставляет содержимое буфера обмена в поле ввода за позицией курсора.
  • Также класс TextEditor включает ряд методов редактирования текста:
  • void TextEditor::doUndoRedo(bool isRedo) — в зависимости от параметра повторяет или отменяет проделанное действие пользователя по редактированию;
  • void TextEditor::setSelectAllWhenFocused(bool b) — выделяет весь текст в поле ввода, когда оно в фокусе (параметр должен принимать значение true);
  • void TextEditor::clear() - очищает поле ввода.
  • Зачастую возникает ситуация, когда необходимо разрешить пользователю ввод только определённого набора символов (шаблон или маска ввода). Задать маску ввода можно вызовом метода void TextEditor::setInputRestrictions(int maxTextLength, const String allowedCharacters = String::empty), где maxTextLength — максимально допустимая длина вводимого текста (в символах), а allowedCharacters — строка, содержащая допустимые символы. В случае, если первый параметр принимает значение 0, ограничений на длину ввода нет. Подробнее с использованием маски ввода можно познакомиться на примере, приведённом ниже.

    Рассмотрим использование компонентов Label и TextEditor на примере программы, внешний вид которой показан на рисунке 8.1 .

    (рис 8.1) Программа, демонстрирующая ввод и отображение текстовой информации

    Окно программы разделено на три секции. В качестве разделителей используются два объекта класса ).

    // Создание ярлыка, надпись которого содержит пустую строку
    pSeparator = new Label("Separator", String::empty);
    // Запрет на редактирование
    pSeparator->setEditable(false, false, false);
    // Задаём цвет контура ярлыка
    pSeparator->setColour(Label::backgroundColourId, Colour(0x3d000000));
    addAndMakeVisible(pSeparator);
    
    // Реализация метода TCentralComponent::resize();
    // Высота виджета — 1 пиксель
    pSeparator->setBounds(8, 136, 704, 1);
    

    Первая секция нашей программы будет содержать многострочное поле ввода без возможности непосредственного редактирования текста пользователем (только чтение), TextEditor* pTextEditor; однострочное поле, TextEditor* pAppendTextEdit, для ввода пользователем текста, который будет добавляться в TextEditor после нажатия на кнопку, а также сами кнопки:

  • TextButton* pClearButton — очищает поле ввода;
  • TextButton* pAppendTextButton — добавляет текст из pAppendTextEdit в конец имеющегося в компоненте pTextEditor;
  • TextButton* pSetTextButton — замещает текст компонента pTextEditor текстом из pAppendTextEdit.
  • Кроме того, в секции имеется выпадающий список ).
  • #include "TCentralComponent.h"
    //----------------------------------------------------
    #define tr(s) String::fromUTF8(s)
    //----------------------------------------------------
    TCentralComponent::TCentralComponent() : Component("Central Component"),
                pTextEditor(0),
                pAppendTextEdit(0),
                pFontSelectionBox(0),
                pClearButton(0),
                pAppendTextButton(0),
                pSetTextButton(0),
                pSeparator(0),
                pLabel(0),
                pLabelGroup(0),
                pReadOnlyButton(0),
                pEditByOneClickButton(0),
                pEditByDoubleClickButton(0),
                pSeparator1(0)
    {
      // Создаём новое поле ввода (текстовый редактор)
      pTextEditor = new TextEditor("TextEditor");
      // Разрешаем ввод более одной строки
      // и разрешаем автоматический перенос строк
      pTextEditor->setMultiLine(true, true);
      // Запрет на редактирование
      pTextEditor->setReadOnly(true);
      // Показываем полосы прокрутки
      pTextEditor->setScrollbarsShown(true);
      addAndMakeVisible(pTextEditor);
    
      // Шрифт для ранее созданного поля ввода
      Font TextEditorFont;
      // Гарнитура - шрифт Sans-Serif по умолчанию
      TextEditorFont.setTypefaceName(Font::getDefaultSerifFontName());
      TextEditorFont.setHeight(20);
      // Устанавливаем шрифт TextEditorFont в качестве
      // шрифта поля ввода
      pTextEditor->setFont(TextEditorFont);
      pTextEditor->setTextToShowWhenEmpty("Text Editor", Colours::grey);
    
      // Создаём поле ввода для текста, добавляемого
      // в многострочное поле pTextEditor
      pAppendTextEdit = new TextEditor("AppendTextEdit");
      // Ввод текста - в одну строку
      pAppendTextEdit->setMultiLine(false);
      // Разрешаем ввод текста
      pAppendTextEdit->setReadOnly(false);
      // Показываем полосы прокрутки
      pAppendTextEdit->setScrollbarsShown(false);
      // Делаем видимым курсор
      pAppendTextEdit->setCaretVisible(true);
      // Разрешаем стандартное контекстное меню
      pAppendTextEdit->setPopupMenuEnabled(true);
      // Текст поля - пустая строка
      pAppendTextEdit->setText(String::empty);
      addAndMakeVisible(pAppendTextEdit);
    
      // Выпадающий список выбора шрифта
      pFontSelectionBox = new ComboBox("FontSelectionBox");
      pFontSelectionBox->setEditableText(false);
      pFontSelectionBox->setJustificationType(Justification::centredLeft);
      pFontSelectionBox->setTextWhenNothingSelected(tr("Выбрать"));
      // Устанавливаем в качестве слушателя выпадающего списка
      // сам компонент-контейнер
      pFontSelectionBox->addListener(this);
      addAndMakeVisible(pFontSelectionBox);
    
      // Получаем имена системных шрифтов, сохраняем их 
      // в строковом массиве...
      StringArray sSystemFonts = Font::findAllTypefaceNames();
      // и добавляем в выпадающий список
      for(int i = 0; i < sSystemFonts.size(); i++)
      {
        pFontSelectionBox->addItem(sSystemFonts[i], i + 1);
      }
    
      // Кнопка, очищающая поле pTextEditor
      pClearButton = new TextButton("ClearButton");
      pClearButton->setButtonText(tr("Очистить"));
      // Устанавливаем в качестве слушателя кнопки
      // сам компонент-контейнер
      pClearButton->addListener(this);
      addAndMakeVisible(pClearButton);
    
      // Кнопка, добавляющая текст в поле pTextEditor
      pAppendTextButton = new TextButton("AppendTextButton");
      pAppendTextButton->setButtonText(tr("Добавить текст"));
      pAppendTextButton->addListener(this);
      addAndMakeVisible(pAppendTextButton);
    
      // Кнопка, замещающая текст в поле pTextEditor
      pSetTextButton = new TextButton("SetTextButton");
      pSetTextButton->setButtonText(tr("Заменить текст"));
      pSetTextButton->addListener(this);
      addAndMakeVisible(pSetTextButton);
    

    Практически все методы класса TextEditor, использованные в примере, были рассмотрены нами ранее. Незнакома только функция void TextEditor::setTextToShowWhenEmpty(const String text, const Colour colourToUse), которая отображает текст text (в нашем случае это надпись "Text Editor") цветом colourToUse в том случае, если компонент пуст, т.е. не содержит другого текста.

    При запуске демонстрационной программы текст в виджете ).

    void TCentralComponent::comboBoxChanged(ComboBox* pComboBox)
    {
      // Изменён выпадающий список гарнитуры шрифта
      if(pComboBox == pFontSelectionBox)
      {
        // Устанавливаем в качестве шрифта текста поля ввода
        // шрифт, выбранный пользователем из списка
        Font CurrentFont;
        CurrentFont.setTypefaceName(pFontSelectionBox->getText());
        CurrentFont.setHeight(20);
        // Задаём шрифт для отображения текста
        pTextEditor->applyFontToAllText(CurrentFont);
      }
    }
    

    В примере для задания свойств текста мы использовали метод void TextEditor::applyFontToAllText(const Font newFont), который применяет характеристики шрифта newFont ко всему содержимому поля ввода.

    Однако на практике гораздо чаще требуется, чтобы различные части текста отображались разными шрифтами. В этом случае можно воспользоваться методом void TextEditor::setFont(const Font newFont), который применяет свойства шрифта newFont только к вновь добавляемому тексту (как вводимому пользователем, так и добавляемому программно).

    Реализация обработчика событий щелчка по кнопке компонента содержимого представлена в листинге 8.5 ; она понятна без каких-либо дополнительных комментариев.

    void TCentralComponent::buttonClicked(Button* pButton)
    {
      // Если нажата кнопка "Очистить"...
      if(pButton == pClearButton)
      {
        // Очистить поле ввода
        pTextEditor->clear();
      }
      // Если нажата кнопка "Добавить текст"...
      else if(pButton == pAppendTextButton)
      {
        // Вставить текст за курсором
        pTextEditor->insertTextAtCaret(pAppendTextEdit->getText());
        pTextEditor->insertTextAtCaret("\n");
      }
      // Если нажата кнопка "Заменить текст"...
      else if(pButton == pSetTextButton)
      {
        // Сделать замену
        pTextEditor->setText(pAppendTextEdit->getText());
        pTextEditor->insertTextAtCaret("\n");
      }
      // ...
    } 
    

    Вторая секция нашей программы демонстрирует работу с режимами редактирования ярлыка ). Подчеркнём, что объединение радиокнопок в одну группу произошло лишь графически, т.е. они рисуются на поверхности компонента ).

      // Ярлык с возможностью ввода текста
      pLabel = new Label("Label", "Label");
      pLabel->setFont(Font(15.0000f, Font::bold));
      pLabel->setJustificationType(Justification::centred);
      // Запрет на редактирование
      pLabel->setEditable(false, false, false);
      // Цвет текста надписи
      pLabel->setColour(Label::textColourId, Colour(0xff0f1465));
      // Цвет рамки
      pLabel->setColour(Label::outlineColourId, Colour(0xf6615e5e));
      pLabel->setColour(Label::backgroundColourId, Colour(0x0));
      addAndMakeVisible(pLabel);
    
      pLabelGroup = new GroupComponent();
      pLabelGroup->setText(tr("Редактирование"));
      addAndMakeVisible(pLabelGroup);
    
      // Радиокнопка - запрет на редактирование ярлыка
      pReadOnlyButton = new ToggleButton("ReadOnlyButton");
      pReadOnlyButton->setButtonText(tr("Только чтение"));
      // Устанавливаем в качестве слушателя кнопки
      // сам компонент-контейнер
      pReadOnlyButton->addListener(this);
      // Радиокнопка отмечена
      pReadOnlyButton->setToggleState(true, false);
      pReadOnlyButton->setRadioGroupId(1234);
      addAndMakeVisible(pReadOnlyButton);
    
      // Радиокнопка - редактирование ярлыка по щелчку
      pEditByOneClickButton = new ToggleButton("EditByOneClickButton");
      pEditByOneClickButton->setButtonText(tr("Одним щелчком"));
      pEditByOneClickButton->addListener(this);
      // Радиокнопка не отмечена
      pEditByOneClickButton->setToggleState(false, false);
      pEditByOneClickButton->setRadioGroupId(1234);
      addAndMakeVisible(pEditByOneClickButton);
    
      // Радиокнопка - редактирование ярлыка по двойному щелчку
      pEditByDoubleClickButton = new ToggleButton("EditByDoubleClickButton");
      pEditByDoubleClickButton->setButtonText(tr("Двойным щелчком"));
      pEditByDoubleClickButton->addListener(this);
      pEditByDoubleClickButton->setToggleState(false, false);
      pEditByDoubleClickButton->setRadioGroupId(1234);
      addAndMakeVisible(pEditByDoubleClickButton);
    

    Переключение режимов редактирования реализовано в методе нашего класса компонента содержимого )

      else if(pButton == pReadOnlyButton)
      {
        pLabel->setEditable(false, false, false);
      }
      else if(pButton == pEditByOneClickButton)
      {
        pLabel->setEditable(true, false, false);
      }
      else if(pButton == pEditByDoubleClickButton)
      {
        pLabel->setEditable(false, true, false);
      }
    

    И наконец рассмотрим использование объекта класса ). Сделаем строку ).

    pPasswordLabel = new Label("PasswordLabel", tr("Введите пароль: "));
      pPasswordLabel->setEditable(false, false, false);
      addAndMakeVisible(pPasswordLabel);
    
      // Поле ввода пароля
      pPasswordEdit = new TextEditor("PasswordEdit", 0x2022);
      // Разрешаем ввод цифр; ограничений на длину вводимой строки нет
      pPasswordEdit->setInputRestrictions(0, "1234567890");
      pPasswordEdit->setMultiLine(false);
      pPasswordEdit->setScrollbarsShown(false);
      pPasswordEdit->setReturnKeyStartsNewLine(false);
      pPasswordEdit->addKeyListener(this);
      addAndMakeVisible(pPasswordEdit);
    
      // Кнопка ввода пароля
      pSubmitButton = new TextButton("SubmitButton");
      pSubmitButton->setButtonText(tr("Ввод"));
      pSubmitButton->addListener(this);
      addAndMakeVisible(pSubmitButton);
    
      setSize(728, 400);
    
      // Задаём пароль
      sPassword = "1234";
    

    Числовой код ), выбран не случайно. Это код символа так называемой пули или дробины (bullet), которая одинаково отображается как в Linux, так и в Windows.

    Проверку корректности введённого пароля реализуем в виде отдельной функции-члена класса ).

    void TCentralComponent::SubmitPassword()
    {
      // Если введённый текст не содержит пустую строку
      // или строку, заполненную пробелами
      if(pPasswordEdit->getText().containsNonWhitespaceChars())
      {
        // И если пароль соответствует введённому тексту
        if(pPasswordEdit->getText() == sPassword)
        {
          // Выводим информационное сообщение
          pPasswordLabel->setColour(Label::textColourId, Colours::blue);
          pPasswordLabel->setText(tr("Принято!"), false);
          // Делаем недоступными кнопку и поле ввода
          pPasswordEdit->setEnabled(false);
          pSubmitButton->setEnabled(false);
        }
        else
        {
          pPasswordLabel->setColour(Label::textColourId, Colours::red);
          pPasswordLabel->setText(tr(
                "Пароль неверен! Введите повторно:"), false);
          pPasswordEdit->clear();
        }
      }
    }
    

    В этом примере мы вначале проверяем не является ли введённый пользователем текст пустой (в том числе заполненной одними пробелами) строкой. Собственно, эта проверка является излишней, мы, посредством функции setInputRestrictions (см. листинг 8.8 ) задали набор символов, разрешённых к вводу, где нет пароля, так что можно было бы ограничиться проверкой

    if(pPasswordEdit->getText() != String::empty
    

    Метод containsNonWhitespaceChars был использован исключительно в качестве демонстрации.

    Затем, собственно, производится проверка валидности пароля и вывод информирующих сообщений: синим цветом, если пароль правильный или красным — в противном случае.

    Теперь остаётся только вызвать функцию ).

      else if(pButton == pSubmitButton)
      {
        SubmitPassword();
      }
    

    Многие пользователи, в том числе и автор этой лекции, предпочитают по завершении ввода пароля не щёлкать мышью по кнопке, а нажимать клавишу <ENTER> для ввода. Реализуем эту возможность в нашей программе (рассмотрен лишь один из возможных способов). Для того, чтобы наш компонент содержимого мог отслеживать нажатия клавиш клавиатуры пользователем, унаследуем его класс от слушателя клавиш KeyListener ().

    class TCentralComponent  : public Component,
               public ComboBoxListener,
               public ButtonListener,
               public KeyListener // слушатель клавиш
    

    Напишем реализацию чистой виртуальной функции-члена класса слушателя .

    bool TCentralComponent::keyPressed(const KeyPress Key, 
                      Component* pComponent)
    {
      // Если в фокусе другое поле ввода - выходим
      if(pComponent != pPasswordEdit) return false;
      // Если нажата не клавиша <ENTER> - выходим
      else if(Key != KeyPress::returnKey) return false;
      else SubmitPassword();
      return true;
    }
    

    Запустим откомпилированную демонстрационную программу и убедимся, что ввод пароля осуществляется как при щелчке по кнопке, так и при нажатии клавиши <ENTER>.

    Краткие итоги

    В Juce для отображения текстовой информации на компонентах используется преимущественно два виджета: Label и TextEditor. Они могут иметь различный внешний вид и реакцию на действия пользователя за счёт опций, задаваемых программистом.

    Упражнение

    Напишите простейший текстовый редактор с возможностью автоматического переноса строк при достижении границы поля ввода. Предусмотрите возможность изменения параметров шрифта вводимого текста (с помощью кнопок).

    Дополнительные материал

    Архив с исходными текстами примера Вы можете скачать здесь

    Вернуться к учебному плану