Компоненты ввода и отображения текстовой, цифровой и иерархической информации. Компоненты выбора из списков
Фактически всё, что делает наш класс — это хранит и производит различные действия над списком (массивом) строк, Array<String> sItems, а за их отображение, представление пользователю, отвечает класс ListBox. Число элементов последнего меняется динамически, в зависимости от изменения числа элементов массива sItems (см. реализацию метода int TListBoxModel::getNumRows()). Отрисовка элементов списка в методе paintListBoxItem ( пример 9.4) осуществляется аналогично рассмотренному ранее примеру простого списка, лишь в качестве первого параметра функции drawText выступает не заранее заданная строка, а элемент массива sItems, взятый по индексу текущей строки.
Функции-члены нашего класса void TListBoxModel::backgroundClicked() и void TListBoxModel::listBoxItemClicked(int iRowNumber, const MouseEvent& Event) мы оставим пустыми; класс ListBox и без них хорошо справляется с выделением выбранных мышью элементов, однако, возможно, в будущем вы захотите предусмотреть вывод каких-то информирующих сообщений.
Как это сделать рассмотрим на примере реализации обработчика события двойного щелчка по элементу списка, listBoxItemDoubleClicked (см. пример 9.4). Мы формируем сообщение (строка sMessage), включающее текст выбранного пользователем элемента списка и передаём сообщение в качестве третьего параметра в функцию показа модального диалогового окна static void JUCE_CALLTYPE AlertWindow::showMessageBox(AlertIconType iconType, const String& title, const String& message, const String& buttonText = String::empty, Component* associatedComponent = 0). Подробнее класс AlertWindow рассмотрен в "Стандартные диалоги" .
Первый параметр функции — изображение (пиктограмма), которая будет отображаться на окне диалога ( рис. 9.2), второй и третий — заголовок сообщения и собственно сообщение, четвёртый — текст закрывающей диалог кнопки, а пятый — компонент, с которым будет ассоциирован диалог (в нашем случае лучше оставить значение по умолчанию).
Наши собственные функции, void TListBoxModel::AddItem(String sText) и void TListBoxModel::DeleteItem(int iItem), как понятно из их названий, соответственно, добавляют в список sItems строку с текстом sText и удаляют из него элемент с индексом iItem.
Теперь мы можем приступить к созданию нашего компонента содержимого ( пример 9.5).
#ifndef _TCentralComponent_h_ #define _TCentralComponent_h_ //-------------------------------------------------------- class TListBoxModel; //-------------------------------------------------------- #include "../JuceLibraryCode/JuceHeader.h" //-------------------------------------------------------- // Класс компонента содержимого. // Наследует класс слушателя кнопок // Наследует класс слушателя клавиш клавиатуры class TCentralComponent : public Component, public ButtonListener, public KeyListener { public: TCentralComponent(); ~TCentralComponent(); void paint(Graphics&); void resized(); // Функция, отслеживающая щелчки по кнопке void buttonClicked(Button*); // Функция, отслеживающая нажатие клавиш клавиатуры bool keyPressed(const KeyPress&, Component*); // Добавление элемента списка void AddItem(); // Удаление элемента списка void DeleteItem(); // Модель данных TListBoxModel* pListBoxModel; private: ListBox* pList; TextEditor* pInputEdit; TextButton* pAddButton; TextButton* pDeleteButton; // Предотвращает создание копии конструктора и оператора = TCentralComponent(const TCentralComponent&); const TCentralComponent& operator= (const TCentralComponent&); }; //----------------------------------------------------- #endifЛистинг 9.5. Объявление класса компонента содержимого TCentralComponent (файл TCentralComponent.h)
Поскольку виджет будет отвечать за отслеживание щелчков по кнопкам и нажатие пользователем клавиш клавиатуры, унаследуем класс TCentralComponent от ButtonListener и KeyListener. Предусмотрим переменную TListBoxModel* pListBoxModel для хранения адреса модели данных.
В качестве закрытых членов виджет будет включать список, одно поле ввода и две кнопки с текстом ( рис. 9.2, пример 9.5).
Кроме того, предусмотрим в классе две функции, ответственные за удаление и добавление элементов из списка: void TCentralComponent::DeleteItem() и TCentralComponent::AddItem().
Перейдём к реализации класса компонента содержимого ( пример 9.6).
#include "TCentralComponent.h" #include "TListBoxModel.h" //-------------------------------------------------------- #define tr(s) String::fromUTF8(s) //-------------------------------------------------------- TCentralComponent::TCentralComponent() : Component("Central Component"), pAddButton(0), pDeleteButton(0) { // Создаём экземпляр модели данных pListBoxModel = new TListBoxModel(); // Задаём эту модель в качестве источника данных при создании списка pList = new ListBox("List", pListBoxModel); pList->setOutlineThickness(2); pList->setMultipleSelectionEnabled(false); pList->addKeyListener(this); addAndMakeVisible(pList); pInputEdit = new TextEditor("Input Edit"); pInputEdit->setMultiLine(false, false); pInputEdit->addKeyListener(this); addAndMakeVisible(pInputEdit); pAddButton = new TextButton("Add Button"); pAddButton->setButtonText(tr("Добавить")); pAddButton->addListener(this); addAndMakeVisible(pAddButton); pDeleteButton = new TextButton("Delete Button"); pDeleteButton->setButtonText(tr("Удалить")); pDeleteButton->addListener(this); addAndMakeVisible(pDeleteButton); setSize (400, 200); } //--------------------------------------------------------- TCentralComponent::~TCentralComponent() { // Удаляем дочерние виджеты // и обнуляем их указатели deleteAllChildren(); if(pListBoxModel != 0) { delete pListBoxModel; } } //------------------------------------------------------- void TCentralComponent::paint(Graphics& Canvas) { Canvas.fillAll(Colours::azure); } //-------------------------------------------------------- void TCentralComponent::resized() { pList->setBounds(10, 10, proportionOfWidth(0.5000f), 150); pInputEdit->setBounds(10 + (proportionOfWidth(0.5000f)) - -18, 10, proportionOfWidth(0.4f), 25); pAddButton->setBounds(10 + (proportionOfWidth(0.5000f)) - -18, 40, 150, 25); pDeleteButton->setBounds(10 + (proportionOfWidth(0.5000f)) - -18, 70, 150, 25); } //--------------------------------------------------------- void TCentralComponent::AddItem() { // Если поле ввода не пусто... if(pInputEdit->getText().isNotEmpty()) { // добавляем текст из него в список строк модели pListBoxModel->AddItem(pInputEdit->getText()); // Обновляем содержимое списка для того, // чтобы отобразить изменение данных модели pList->updateContent(); // Очищаем поле ввода pInputEdit->clear(); } } //---------------------------------------------------------- void TCentralComponent::DeleteItem() { // Получаем последний выбранный элемент списка int iSelected = pList->getLastRowSelected(); // Если хотя бы один элемент выбран... if(iSelected != -1) { // удаляем строку с соответствущим индексом из массива модели pListBoxModel->DeleteItem(iSelected); // Обновляем содержимое списка для того, // чтобы отобразить изменение данных модели pList->updateContent(); } } //----------------------------------------------------- void TCentralComponent::buttonClicked(Button* pButton) { // Если нажата кнопка "Добавить"... if(pButton == pAddButton) { this->AddItem(); } // Если нажата кнопка "Удалить"... else if(pButton == pDeleteButton) { this->DeleteItem(); } } //------------------------------------------------------- bool TCentralComponent::keyPressed(const KeyPress& Key, Component* pComponent) { // Если над полем ввода нажата клавиша <ENTER>... if(Key == KeyPress::returnKey && pComponent == pInputEdit) { this->AddItem(); return true; } // Если над полем ввода нажата клавиша <DELETE>... else if(Key == KeyPress::deleteKey && pComponent == pList) { this->DeleteItem(); return true; } else return false; } //-----------------------------------------------------------Листинг 9.6. Объявление класса компонента содержимого TCentralComponent (файл TCentralComponent.cpp)
В конструкторе компонента содержимого TCentralComponent создадим указатель на экземпляр класса нашей модели (TListBoxModel), который передадим в качестве параметра конструктора списка ListBox. Все остальные виджеты добавляются в центральный компонент обычным образом.
Напишем реализацию функции добавления элемента в список (AddItem). Вначале мы проверяем, не пусто ли поле ввода pInputEdit, и если оно содержит какой-то текст, добавляем его в список строк модели с использованием ранее написанного нами метода void TListBoxModel::AddItem(String sText). Однако, если ограничиться только этим, то изменение модели не приведёт к изменению внешнего вида списка pList. Для того, чтобы список отобразил новую строку в соответствии с изменением модели, необходимо принудительно обновить его содержимое с помощью метода void ListBox::updateContent(). После этого очистим поле ввода ( пример 9.6).
Приступим к реализации функции удаления элемента из списка (DeleteItem). Вначале объявим целочисленную переменную int iSelected, в которой сохраним индекс последнего выбранного пользователем элемента списка. Для получения этого индекса воспользуемся методом getLastRowSelected. После этого удалим из массива строк модели элемент с найденным индексом, воспользовавшись ранее написанным методом void TListBoxModel::DeleteItem(int iItem). Обновим содержимое списка ( пример 9.6).
Теперь мы можем вызывать функции удаления или добавления элементов списка из обработчиков нажатий на соответствующие кнопки или нажатия клавиш клавиатуры. Обратите внимание, что в методе keyPressed отслеживается нажатие клавиш над целевыми виджетами: клавиши <ENTER> - над полем ввода pInputEdit, а клавиши <DELETE> - над списком pList ( пример 9.6).