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

Разработка собственных компонентов

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

Цель лекции: Научиться создавать произвольные компоненты на основе стандартных

Графический интерфейс пользователя давно стал стандартом 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(), которая удаляет из контейнера все включённые в него компоненты.

Рассмотрим всё вышеизложенное на простом примере. Создадим собственный компонент, который будет включать в себя два дочерних виджета: ярлык (, ).

#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
#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();
  }
}
//---------------------------------------------------------------------

Любой компонент (виджет) представляет собой прямоугольную область на экране компьютера, характеризующийся координатами верхне-левого угла, а также длиной и шириной. При изменении размеров главного окна программы (). Координаты виджетов задаются функцией void Component::setBounds(int x, int y, int width, int height), где x и y — координаты верхне-левого угла дочернего компонента относительно верхне-левого угла компонента-родителя, а width и height — ширина и высота виджета.

Однако задание абсолютных размеров виджетов не всегда удобно, поскольку они оказываются "привязанными" к определённому месту компонента-контейнера, что не всегда выглядит красиво при распахивании окна программы или при растягивании его мышью.

Для того, чтобы задать размеры и позицию виджета относительно компонента родителя в Juce используются функции-члены класса Rectangle: getWidth() и getHeight(), возвращающие значения ширины и высоты родительского компонента.

Также можно использовать функции ) означает, что ширина дочернего виджета будет соответствовать таковой родительского компонента (аналогично использованию функции getWidth()), а proportionOfHeight(0.9000f) означает, что высота виджета должна составлять 90% высоты родительского компонента.

Теперь добавим экземпляр класса нашего компонента ().

#ifndef _TMainForm_h_
#define _TMainForm_h_
//---------------------------------------------------------------------
#include "../JuceLibraryCode/JuceHeader.h"
//---------------------------------------------------------------------
class TCentralComponent;
//---------------------------------------------------------------------
class TMainForm : public DocumentWindow
{
public:
  TMainForm();
  ~TMainForm();

  void closeButtonPressed();
  
private:
  // Компонент содержимого
  TCentralComponent* pCentralComponent;
};
//---------------------------------------------------------------------
#endif

В реализации класса главного окна установим наш компонент в качестве компонента содержимого ().

#include "TMainForm.h"
//---------------------------------------------------------------------
#include "TCentralComponent.h"
//---------------------------------------------------------------------
TMainForm::TMainForm() : DocumentWindow(
    JUCEApplication::getInstance()->getApplicationName(),
    Colours::azure,
    DocumentWindow::allButtons)
{
  centreWithSize(400, 200);
  setResizable(true, false);
  setVisible(true);
  
  // Динамически создаём экземпляр класса нашего компонента
  pCentralComponent = new TCentralComponent();
  // Устанавливаем его в качестве компонента содержимого
  setContentOwned(pCentralComponent, true);
}
//---------------------------------------------------------------------
TMainForm::~TMainForm()
{
}
//---------------------------------------------------------------------
void TMainForm::closeButtonPressed()
{
  JUCEApplication::getInstance()->systemRequestedQuit();
}
//---------------------------------------------------------------------

Внешний вид программы представлен на рисунке 4.1 .

(рис 4.1) Приложение Juce, использующее в качестве компонента содержимого компонент-контейнер, содержащий ярлык и кнопку

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

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

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

Родительским классом для всех компонентов является Component.

Упражнение

Поэкспериментируйте со свойствами компонентов из примера (размер, цвет).

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

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

Страницы:

Цель лекции: Научиться создавать произвольные компоненты на основе стандартных

Графический интерфейс пользователя давно стал стандартом 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(), которая удаляет из контейнера все включённые в него компоненты.

Рассмотрим всё вышеизложенное на простом примере. Создадим собственный компонент, который будет включать в себя два дочерних виджета: ярлык (, ).

#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
#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();
  }
}
//---------------------------------------------------------------------

Любой компонент (виджет) представляет собой прямоугольную область на экране компьютера, характеризующийся координатами верхне-левого угла, а также длиной и шириной. При изменении размеров главного окна программы (). Координаты виджетов задаются функцией void Component::setBounds(int x, int y, int width, int height), где x и y — координаты верхне-левого угла дочернего компонента относительно верхне-левого угла компонента-родителя, а width и height — ширина и высота виджета.

Однако задание абсолютных размеров виджетов не всегда удобно, поскольку они оказываются "привязанными" к определённому месту компонента-контейнера, что не всегда выглядит красиво при распахивании окна программы или при растягивании его мышью.

Для того, чтобы задать размеры и позицию виджета относительно компонента родителя в Juce используются функции-члены класса Rectangle: getWidth() и getHeight(), возвращающие значения ширины и высоты родительского компонента.

Также можно использовать функции ) означает, что ширина дочернего виджета будет соответствовать таковой родительского компонента (аналогично использованию функции getWidth()), а proportionOfHeight(0.9000f) означает, что высота виджета должна составлять 90% высоты родительского компонента.

Теперь добавим экземпляр класса нашего компонента ().

#ifndef _TMainForm_h_
#define _TMainForm_h_
//---------------------------------------------------------------------
#include "../JuceLibraryCode/JuceHeader.h"
//---------------------------------------------------------------------
class TCentralComponent;
//---------------------------------------------------------------------
class TMainForm : public DocumentWindow
{
public:
  TMainForm();
  ~TMainForm();

  void closeButtonPressed();
  
private:
  // Компонент содержимого
  TCentralComponent* pCentralComponent;
};
//---------------------------------------------------------------------
#endif

В реализации класса главного окна установим наш компонент в качестве компонента содержимого ().

#include "TMainForm.h"
//---------------------------------------------------------------------
#include "TCentralComponent.h"
//---------------------------------------------------------------------
TMainForm::TMainForm() : DocumentWindow(
    JUCEApplication::getInstance()->getApplicationName(),
    Colours::azure,
    DocumentWindow::allButtons)
{
  centreWithSize(400, 200);
  setResizable(true, false);
  setVisible(true);
  
  // Динамически создаём экземпляр класса нашего компонента
  pCentralComponent = new TCentralComponent();
  // Устанавливаем его в качестве компонента содержимого
  setContentOwned(pCentralComponent, true);
}
//---------------------------------------------------------------------
TMainForm::~TMainForm()
{
}
//---------------------------------------------------------------------
void TMainForm::closeButtonPressed()
{
  JUCEApplication::getInstance()->systemRequestedQuit();
}
//---------------------------------------------------------------------

Внешний вид программы представлен на рисунке 4.1 .

(рис 4.1) Приложение Juce, использующее в качестве компонента содержимого компонент-контейнер, содержащий ярлык и кнопку

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

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

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

Родительским классом для всех компонентов является Component.

Упражнение

Поэкспериментируйте со свойствами компонентов из примера (размер, цвет).

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

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

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