Разработка собственных компонентов
Цель лекции: Научиться создавать произвольные компоненты на основе стандартных
Графический интерфейс пользователя давно стал стандартом de facto при разработке прикладных программ для различных операционных систем. Компоненты — это основа для его создания. Они делятся на компоненты ввода-вывода текстовой, цифровой и иерархической информации и управляющие. С одним компонентов отображения текстовой информации, ярлыком (Label), вы уже познакомились в предыдущей лекции.
Как правило, компонент интерфейса программы — это не просто область на экране, отображающая текст и / или изображение; это элемент, реагирующий на события системы и действия пользователя тем или иным образом (генерация сообщений другим компонентам, изменение внешнего вида и размера, выполнение вычислений и т.д. и т.п.). Взаимодействие подобных компонентов основывается на работе передатчиков событий (event broadcaster) и приёмников или слушателей событий (event listener).
Элементы интерфейса, имеющие стандартный внешний вид и выполняющие стандартные действия (кнопки, выпадающие меню и списки, флажки, переключатели и т.п.) называются виджетами — термин, пришедший из Linux. Конечно, "стандартный" внешний вид виджетов Juce — понятие относительное, поскольку, как упоминалось выше, библиотека не использует элементы управления целевой платформы, а рисует все элементы пользовательского интерфейса самостоятельно, пользуясь только системными функциями низкого уровня.
Слово "widget" является шуточным сокращением фразы "which it, gadget" - "эта, как её, штуковина". В Windows системах более привычным является название "элемент управления" (control). Виджеты включаются в компоненты-контейнеры, которые должны "слушать" события, генерируемые первыми, т.е. являться приёмниками (listener). Это достигается наследованием класса компонента-контейнера от нужного класса слушателя (ButtonListener, ComboBoxListener, SliderListener и т.п.). Классы слушателей являются абстрактными и содержат чистые виртуальные (pure virtual) функции, которые в классе компонента-контейнера должны быть переопределены.
Как правило, в качестве компонента-контейнера выступает упоминавшийся в предыдущей лекции компонент содержимого (content component), который предоставляет доступ к клиентской части окна приложения. Класс компонента содержимого наследуется от класса Component и класса (классов) слушателей.
Вообще, множественное наследование весьма характерно для Juce, поскольку довольно большое число классов библиотеки являются абстрактными. Так, например, в отличие от библиотеки Qt, в Juce невозможно создать таймер (объект класса Timer), поэтому для того, чтобы воспользоваться функциями класса, необходимо создать компонент, унаследовав его класс от классов Component и Timer.
Класс Component является основой для всех виджетов, включая окна и диалоги. Сам класс Component не является абстрактным, поэтому создание экземпляра данного класса вполне возможно. Однако в "чистом" виде объект класса Component бесполезен. В случае, если мы добавим в него какие-либо виджеты с помощью метода Component::addAndMakeVisible(Component* child, int zOrder = -1), код будет откомпилирован, однако на этапе выполнения возникнет ошибка, связанная с тем, что в Juce программист должен сам позаботиться об уничтожении дочерних виджетов при завершении работы программы, переопределив деструктор класса компонента-контейнера. Уничтожение дочерних виджетов достигается вызовом метода void Component::SafePointer<ComponentType>::deleteAndZero(), который удаляет из контейнера дочерний компонент и обнуляет указатель на него. Однако для того, чтобы не вызывать эту функцию для каждого из дочерних виджетов, проще воспользоваться функцией void Component::deleteAllChildren(), которая удаляет из контейнера все включённые в него компоненты.
Рассмотрим всё вышеизложенное на простом примере. Создадим собственный компонент, который будет включать в себя два дочерних виджета: ярлык (Label) и кнопку с текстом (TextButton). Мы унаследуем класс нашего компонента от ButtonListener для того, чтобы отслеживать щелчок пользователя по кнопке, и переопределим функцию virtual void Button::Listener::buttonClicked(Button*), которая будет вызывать метод класса приложения static void JUCEApplication::quit(), завершающий работу программы ( пример 4.1, пример 4.2).
#ifndef _TCentralComponent_h_ #define _TCentralComponent_h_ //--------------------------------------------------------------------- #include "../JuceLibraryCode/JuceHeader.h" //--------------------------------------------------------------------- // Класс компонента содержимого. // Наследует класс слушателя кнопок class TCentralComponent : public Component, public ButtonListener { public: TCentralComponent(); ~TCentralComponent(); void paint(Graphics&); void resized(); // Функция, отслеживающая щелчки по кнопке void buttonClicked(Button*); private: Label* pHelloLabel; TextButton* pCloseButton; // Предотвращает создание копии конструктора и оператора = TCentralComponent(const TCentralComponent&); const TCentralComponent& operator= (const TCentralComponent&); }; //--------------------------------------------------------------------- #endifЛистинг 4.1. Объявление класса компонента содержимого (файл TCentralComponent.h)
#include "TCentralComponent.h" //--------------------------------------------------------------------- #define tr(s) String::fromUTF8(s) //--------------------------------------------------------------------- TCentralComponent::TCentralComponent() : Component("Central Component"), pHelloLabel(0), pCloseButton(0) { pHelloLabel = new Label("Hello Label", tr("Привет, мир!")); addAndMakeVisible(pHelloLabel); pHelloLabel->setFont(Font(38.0000f, Font::bold)); // Ориентация текста ярлыка - по центру виджета pHelloLabel->setJustificationType(Justification::centred); // Зпрет на редактирование содержимого ярлыка pHelloLabel->setEditable(false, false, false); // Синий цвет шрифта pHelloLabel->setColour(Label::textColourId, Colours::blue); pHelloLabel->setColour(TextEditor::backgroundColourId, Colours::azure); pCloseButton = new TextButton("Close Button"); addAndMakeVisible(pCloseButton); pCloseButton->setButtonText(tr("Закрыть")); // Устанавливаем в качестве слушателя кнопки // сам компонент-контейнер pCloseButton->addListener(this); setSize (400, 200); } //--------------------------------------------------------------------- TCentralComponent::~TCentralComponent() { // Удаляем дочерние виджеты // и обнуляем их указатели deleteAndZero(pHelloLabel); deleteAndZero(pCloseButton); } //--------------------------------------------------------------------- void TCentralComponent::paint(Graphics& Canvas) { Canvas.fillAll(Colours::azure); } //--------------------------------------------------------------------- void TCentralComponent::resized() { pHelloLabel->setBounds(0, 0, proportionOfWidth(1.0000f), proportionOfHeight(0.9000f)); pCloseButton->setBounds(getWidth() - 20 - 100, getHeight() - 35, 100, 25); } //--------------------------------------------------------------------- void TCentralComponent::buttonClicked(Button* pButton) { // Если нажата кнопка "Закрыть"... if(pButton == pCloseButton) { // выходим из программы JUCEApplication::quit(); } } //---------------------------------------------------------------------Листинг 4.2. Реализация класса компонента содержимого (файл TCentralComponent.cpp