Опубликован: 13.07.2012 | Доступ: свободный | Студентов: 461 / 9 | Оценка: 5.00 / 5.00 | Длительность: 18:06:00
Специальности: Программист
Лекция 9:

Компоненты ввода и отображения текстовой, цифровой и иерархической информации. Компоненты выбора из списков

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >

Класс ComboBox

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

Чтобы добавить новый элемент в список, используется метод void ComboBox::addItem(const String& newItemText, int newItemId), где newItemText — текст нового элемента списка, а newItemIdиндекс, с которым он будет ассоциироваться. Обратите внимание, что значение индекса не должно быть равным нулю.

Обращение к элементам списка осуществляется по их индексам. Получить индекс элемента списка, который выбрал пользователь, можно с помощью метода int ComboBox::getSelectedId() const throw(). Число элементов списка позволяет получить функция int ComboBox::getNumItems() const throw().

В предыдущих примерах мы использовали виджеты класса ComboBox, которые предоставляли пользователю возможность выбора значений из выпадающего списка, не не позволяли редактировать отображаемый текст. Можно сделать так, чтобы текст, отображаемый в выпадающем списке мог быть изменён пользователем. Для установки виджета в этот режим вызывается метод void ComboBox::setEditableText(bool isEditable), где isEditable — флаг включения / отключения режима редактирования. Если он принимает значение true, то двойной щелчок мышью покажет текстовый редакторполе отображения текста выпадающего списка появится курсор, а текущий элемент списка будет выделен). Вместо двойного щелчка можно вызвать редактор программно с помощью метода void ComboBox::showEditor().

К сожалению, в отличие от других инструментов для построения графического интерфейса пользователя, например, Qt, в Juce пользователь не может сам добавлять элементы в список. Это отражает позицию создателя библиотеки, Julian Storer, который пишет: "Я никогда не испытывал необходимости изменять или удалять элементы выпадающего списка. Компонент графического интерфейса — это не место для хранения и манипулирования списками данных. В принятом дизайне "модель — представление" выпадающий список — это просто способ отображения вашего набора элементов. Когда ваш набор изменяется, вы должны очистить и заново сгенерировать содержимое вашего выпадающего списка, чтобы отобразить новые элементы".

Для того, чтобы очистить выпадающий список, необходимо вызвать метод void ComboBox::clear(bool dontSendChangeMessage = false), где dontSendChangeMessage — флаг отправки сообщения об изменении списка. В случае, если список будет очищен, это сообщение будет всё равно отправлено, даже в том случае, если параметр dontSendChangeMessage примет значение true.

Классы TableListBox и TableListBoxMode

унаследованы от ListBox и ListBoxModel и включают в себя многие функции-члены, описанные выше. Поэтому не будем останавливаться на их описании подробно и сразу перейдём к примеру простейшей таблицы ( рис. 9.3).

Пример использования виджета таблицы

Рис. 9.3. Пример использования виджета таблицы

Компонент TableListBox представляет собой таблицу с числом строк и столбцов, задаваемых моделью (TableListBox). Число строк определяется значением, возвращаемым методом virtual int TableListBoxModel::getNumRows(), а число столбцов — хранимыми в модели данными. Однако для того, чтобы эти данные отображались в таблице, необходимо включить в неё соответствующее число столбцов. За это отвечает компонент класса TableHeaderComponent, который отображает строку заголовков столбцов таблицы, а также отвечает за отображение, изменение размеров последних и т.п. Кроме того, этот компонент отображает контекстное меню. Получить ссылку на компонент заголовка, использующийся в таблице, можно с помощью метода TableHeaderComponent& TableListBox::getHeader() const.

В нашем примере мы создадим класс TTableListBox, который унаследуем от TableListBox и TableListBoxModel ( пример 9.7). Виджет нашего класса послужит в качестве компонента содержимого.

#ifndef _TTableListBox_h_
#define _TTableListBox_h_
//------------------------------------------------------
#include "../JuceLibraryCode/JuceHeader.h"
//------------------------------------------------------
class TTableListBox : public TableListBox, TableListBoxModel
{
public:
  TTableListBox(const String& sName = String::empty, 
        TableListBoxModel* pModel = 0);
  ~TTableListBox();

  int getNumRows();
  // Рисует фон строки
  void paintRowBackground(Graphics&, int, int, int, bool);
  // Рисует ячейку таблицы
  void paintCell(Graphics&, int, int, int, int, bool);
  // Функция обработки двойного щелчка по ячейки
  void cellDoubleClicked(int, int, const MouseEvent&);
  // Функция обработки щелчка по свободному от ячеек
  // пространству таблицы
  void backgroundClicked();
  
private:
  String sCells[4][5];
  // Предотвращает создание копии конструктора и оператора =
  TTableListBox(const TTableListBox&);
  const TTableListBox& operator= (const TTableListBox&);
};
//--------------------------------------------------------
#endif
Листинг 9.7. Объявление класса TTableListBox (файл TTableListBox.h)

Наш класс унаследовал от TableListBoxModel ряд виртуальных методов ( пример 9.7):

  • virtual void TableListBoxModel::paintRowBackground(Graphics& g, int rowNumber, int width, int height, bool rowIsSelected), где g — контекст рисования, rowNumber — номер строки, width и height — размеры области рисования, а rowIsSelected — флаг выбора строки. Функция, как понятно из названия, отвечает за прорисовку фона строки rowNumber.
  • virtual void TableListBoxModel::paintCell(Graphics& g, int rowNumber, int columnId, int width, int height, bool rowIsSelected), где columnId — номер столбца. Значения остальных параметров те же, что и в предыдущей функции. Метод отвечает за прорисовку ячейки таблицы.
  • virtual void TableListBoxModel::cellDoubleClicked(int rowNumber, int columnId, const MouseEvent& e) — обрабатывает двойной щелчок по ячейке; кроме того, в классе TableListBoxModel есть функция обработки одинарного щелчка — virtual void TableListBoxModel::cellClicked(int rowNumber, int columnId, const MouseEvent& e).
  • virtual void TableListBoxModel::backgroundClicked() — обработчик щелчка по свободному от ячеек пространству таблицы.

Данные таблицы будут храниться в двумерном строковом массиве sCells[4][5].

Приступим к реализации класса TTableListBox ( пример 9.8).

#include "TTableListBox.h"
//-------------------------------------------------------
#define tr(s) String::fromUTF8(s)
//-------------------------------------------------------
TTableListBox::TTableListBox(const String& sName, TableListBoxModel* pModel) 
  : TableListBox(sName, pModel)
{
  setModel(this);
  setOutlineThickness(1);
  setSize(500, 200);
  
  // Добавляем колонки в таблицу
  this->getHeader().addColumn(tr("№"), 1, 50, 30, 400,
            TableHeaderComponent::defaultFlags);
  this->getHeader().addColumn(tr("Имя"), 2, 100, 50, 400,
            TableHeaderComponent::defaultFlags);
  this->getHeader().addColumn(tr("Телефон"), 3, 100, 50, 400,
            TableHeaderComponent::defaultFlags);
  this->getHeader().addColumn(tr("Email"), 4, 200, 50, 400,
            TableHeaderComponent::defaultFlags);

  this->getHeader().setStretchToFitActive(true);

  setMultipleSelectionEnabled(true);
  
  sCells[0][1] += "1"; 
  sCells[1][1] += "2";
  sCells[2][1] += "3";
  sCells[3][1] += "4";
  
  sCells[0][2] += tr("Винни-Пух"); 
  sCells[1][2] += tr("Иа-Иа"); 
  sCells[2][2] += tr("Пятачок"); 
  sCells[3][2] += tr("Тигра");
  
  sCells[0][3] += "55-55-01";
  sCells[1][3] += "55-55-02";
  sCells[2][3] += "55-55-03";
  sCells[3][3] += "55-55-04";

  sCells[0][4] += tr("thepooh@wonderwood.uk"); 
  sCells[1][4] += tr("eyeore@wonderwood.uk"); 
  sCells[2][4] += tr("piglet@wonderwood.uk"); 
  sCells[3][4] += tr("tiger@wonderwood.uk");
}
//------------------------------------------------
TTableListBox::~TTableListBox()
{
}
//-------------------------------------------------
int TTableListBox::getNumRows()
{
  return 4;
}
//------------------------------------------------
Листинг 9.8. Часть реализации класса TTableListBox (файл TTableListBox.cpp)

В конструкторе класса мы устанавливаем его самого в качестве модели таблицы.

Затем добавляем колонки таблицы с использованием метода класса TableHeaderComponent void TableHeaderComponent::addColumn(const String& columnName, int columnId, int width, int minimumWidth = 30, int maximumWidth = -1, int propertyFlags = defaultFlags, int insertIndex = -1), где columnName — название колонки (отображается в заголовке таблицы), columnId — номер колонки, width — начальная ширина колонки в пикселях, minimumWidth и maximumWidth — соответственно, минимальная и максимальная ширина колонки, которую она может принимать при изменении её размеров пользователем; propertyFlags — комбинация наборов значений из нумерованного списка enum ColumnPropertyFlags, которые определяют поведение колонок таблицы; и, наконец, insertIndex — положение, в которое будет добавлена колонка. Если параметр insertIndex принимает значение 0, колонка будет добавлена в начало (верхне-левый угол таблицы), а если -1, то в конец.

Обратите внимание, что каждая колонка должна иметь уникальный номер, при этом columnId должно быть целым, отличным от нуля. Именно поэтому в нашем примере матрица для хранения данных sCells имеет размерность 4 на 5, а не 4 на 4.

Рассмотрим подробнее флаги свойств колонок таблицы (ColumnPropertyFlags):

  • visible — если это свойство установлено, то колонка будет видима; в противном случае она будет скрыта до тех пор, пока пользователь не выберет её название в контекстном меню заголовка таблицы (для этого должно быть также установлено свойство appearsOnColumnMenu);
  • resizable — размер колонки может быть изменён протягиванием мышью;
  • draggable — если это свойство установлено, то колонка может быть перемещена пользователем на другую позицию в таблице;
  • appearsOnColumnMenu — разрешает отображение названия колонки в контекстном меню заголовка таблицы;
  • sortable — если это свойство установлено, то щелчок по заголовку таблицы приведёт к сортировке её элементов, а повторный щелчок — к изменению — порядка сортировки;
  • sortedForwards — элементы колонки будут отсортированы по возрастанию;
  • sortedBackwards — элементы колонки будут отсортированы по убыванию;
  • defaultFlags — флаги по умолчанию (соответствует установленным флагам visible | resizable | draggable | appearsOnColumnMenu | sortable);
  • notResizable — запрет на изменение размеров колонки (соответствует установленным флагам visible | draggable | appearsOnColumnMenu | sortable);
  • notResizableOrSortable — запрет на изменение размеров колонки и сортировку (соответствует установленным флагам visible | draggable | appearsOnColumnMenu);
  • notSortable — запрет на сортировку (соответствует установленным флагам visible | resizable | draggable | appearsOnColumnMenu).

В нашем примере при добавлении колонок мы установили свойства по умолчанию (TableHeaderComponent::defaultFlags пример 9.8).

Далее в конструкторе мы вызываем метод void TableHeaderComponent::setStretchToFitActive(bool shouldStretchToFit). Если функция принимает значение true, ширина колонок изменяется таким образом, чтобы заполнить всю видимую часть таблицы (по умолчанию это свойство TableHeaderComponent отключено).

Так же, как и в предыдущих примерах со списками, мы разрешаем множественный выбор строк таблицы с помощью функции setMultipleSelectionEnabled.

После этого заполняем матрицу sCells строковыми данными, оставляя нулевую колонку пустой (ещё раз напомним, что параметр columnId не должен равняться нулю).

Число строк нашей таблицы равно 4 ( пример 9.8); в том случае, если бы мы задали размерность матрицы большую, чем число строк в таблице, то отобразилась бы лишь соответствующая часть данных модели.

Рассмотрим реализацию методов отрисовки ячеек таблицы нашего класса ( пример 9.9).

void TTableListBox::paintRowBackground(Graphics& Painter, int iRowNumber, 
            int iWidth, int iHeight, bool bIsRowSelected)
{
  // Если строка выбрана...
  if(bIsRowSelected)
  {
  // Закрашиваем её фон бледно-голубым
  Painter.fillAll(Colours::lightblue);
  }
}
//----------------------------------------------------
void TTableListBox::paintCell(Graphics& Painter, int iRowNumber, int iColumnID,
          int iWidth, int iHeight, bool bIsRowSelected)
{
  Painter.setColour(Colours::black);
  Painter.setFont(iHeight * 0.7f);
  
  // Рисуем текст из строковой матрицы модели в соответствующей
  // ячейке таблицы
  Painter.drawText(sCells[iRowNumber][iColumnID], 2, 0, iWidth — 4,
          iHeight, Justification::centredLeft, true);

  // Рисуем границы ячейки серым цветом
  Painter.setColour(Colours::black.withAlpha(0.2f));
  Painter.fillRect(iWidth - 1, 0, 1, iHeight);
}
//----------------------------------------------------
void TTableListBox::backgroundClicked()
{
  // Отменяем выделение строк, если они были выбраны
  deselectAllRows();
}
//----------------------------------------------------
Листинг 9.9. Реализация методов отрисовки ячеек таблицы класса TTableListBox (файл TTableListBox.cpp)

Реализация функции paintRowBackground довольно проста: если строка таблицы была выбрана пользователем (параметр bIsRowSelected принимает значение true), то с помощью контекста рисования Painter закрашиваем фон ячеек строки в светло-голубой цвет.

Отрисовка собственно ячейки таблицы заключается в отображении на её фоне текста, полученного из строковой матрицы модели, и рисование границ ячейки. Вначале в функции paintCell мы задаём цвет (в нашем примере чёрный) и размер шрифта контекста рисования Painter. После этого рисуем текст элемента массива sCells модели с индексами, соответствующими номерам колонки и строки текущей ячейки таблицы ( пример 9.9). Затем рисуем прямоугольник границ ячейки заданным цветом.

Действие обработчика щелчка по свободной от ячеек части таблицы понятно из комментариев в листинге.

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

void TTableListBox::cellDoubleClicked(int iRowNumber, int iColumn, 
            const MouseEvent& Event)
{
  String sTitle;
  if(iColumn == 1)
  {
    sTitle = tr("Номер:");
  }
  if(iColumn == 2)
  {
    sTitle = tr("Имя:");
  }
  if(iColumn == 3)
  {
    sTitle = tr("Телефон:");
  }
  if(iColumn == 4)
  {
    sTitle = tr("Электронная почта:");
  }

  // Выводим текст соответствующей ячейки в диалоговом окне
  AlertWindow::showMessageBox(AlertWindow::InfoIcon, 
             sTitle, sCells[iRowNumber][iColumn], 
             tr("Да"), 0);
}
Листинг 9.10. Реализация обработчика двойного щелчка мышью по ячейке таблицы класса TTableListBox (файл TTableListBox.cpp)

Поскольку метод в качестве параметра получает номера строки (iRow)Number и колонки (iColumn), мы задаём заголовок диалогового окна класса AlertWindow, соответствующий номеру колонки. Текст сообщения окна — элемент матрицы sCells с индексами iRowNumber и iColumn ( рис. 9.3).

Рассмотренный пример малополезен именно в силу своей простоты: единственное, что может делать программа — это отображать один, раз и навсегда заданный, набор данных. Однако сведений, приведённых в данной главе, достаточно, для того, чтобы читатель мог самостоятельно написать более сложную программу, позволяющую хранить произвольный набор строк с возможностью их добавления и удаления пользователем.

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

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

Упражнение

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

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

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

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >