Разработка собственных компонентов
Цель лекции: Научиться создавать произвольные компоненты на основе стандартных
Графический интерфейс пользователя давно стал стандартом 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