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

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

< Лекция 9 || Лекция 10: 1234 || Лекция 11 >

В рассматриваемом примере добавление нового элемента в список будет осуществляться посредством вызова нашего метода void TcentralComponent::AddNewItem() при нажатии на кнопку pNewItemButton либо при нажатии клавиши <ENTER> клавиатуры ( пример 10.5).

void TCentralComponent::AddNewItem()
{
  // Если поле ввода не пусто...
  if(pTextEdit->getText().isNotEmpty())
  {
    // Находим корневой элемент списка и добавляем в его конец
    // дочерний элемент
    pGenreTreeView->getRootItem()->addSubItem(new 
    TTreeViewItem(pTextEdit->getText()));
    // Находим корневой элемент списка и выделяем последний
    // из его дочерних элементов
    pGenreTreeView->getRootItem()->getSubItem(
    pGenreTreeView->getRootItem()->getNumSubItems() - 1)->setSelected(
    true, true); 
    pTextEdit->clear();
  }
}
Листинг 10.5. Реализация метода AddNewItem класса TCentralComponent (файл TCentralComponent.cpp)

Мы сделали добавление нового элемента возможным лишь в том случае, если пользователь ввёл его имя в поле pTextEdit ( пример 10.5). В том случае, если текст поля не является пустой строкой, мы находим корневой элемент древовидного списка и добавляем дочерний элемент методом void TreeViewItem::addSubItem(TreeViewItem* newItem, int insertPosition = -1). Первый параметр метода — указатель на добавляемый элемент древовидного списка, а второй — позиция, в которую он будет добавлен. Если второй параметр принимает значение -1, то новый элемент добавляется в конец набора дочерних элементов узла. В качестве уникального имени добавляемого узла используем текст поля ввода pTextEdit.

После этого найдём добавленный элемент для того, чтобы выделить его. Позиция (индекс) последнего из добавленных дочерних элементов узла равна общему числу субэлементов минус один. Это число можно получить с помощью функции int TreeViewItem::getNumSubItems() const. Зная индекс элемента, можно получить указатель на него, воспользовавшись методом TreeViewItem* TreeViewItem::getSubItem(int index) const ( пример 10.5).

Рассмотрим реализацию обработчика на кнопки класса компонента содержимого ( пример 10.6).

void TCentralComponent::buttonClicked(Button* pButton)
{
  // Если нажата кнопка "Новый элемент"
  if(pButton == pNewItemButton)
  {
    // то добавляем его
    AddNewItem();
  }
  // Если нажата кнопка "Новый субэлемент"
  else if(pButton == pNewSubItemButton)
  {
    // и если имеется текст в поле ввода
    if(pTextEdit->getText().isNotEmpty())
    {
      // то находим выделенный элемент
      // и добавляем к нему дочерний элемент
      pGenreTreeView->getSelectedItem(0)->addSubItem(new
      TTreeViewItem(pTextEdit->getText()));
      // Очищаем поле ввода
      pTextEdit->clear();
    }
  }
  // Если нажата кнока "Удалить элемент"
  else if(pButton == pDeleteItemButton)
  {
    // Если выбранный элемент - корневой узел
    if(pGenreTreeView->getSelectedItem(0) == pGenreTreeView->getRootItem())
    {
      // то сообщаем пользователю о невозможности его удаления
      AlertWindow::showMessageBox(AlertWindow::WarningIcon, tr("Ошибка"),
            tr("Нельзя удалять корневой элемент!"), 
            tr("Принято"), 0);
      return;
    }

    // Запоминаем индекс выделенного элемента в наборе дочерних элементов 
    // родительского узла
    int i = pGenreTreeView->getSelectedItem(0)->getIndexInParent();
    // Удаляем субэлемент, пользуясь его индексом
    pGenreTreeView->getSelectedItem(0)->getParentItem()->removeSubItem(
            i, true);
    // Если ещё остались дочерние элементы в корневом узле
    if(pGenreTreeView->getRootItem()->getNumSubItems() != 0)
    {
      // то выделяем последний
      pGenreTreeView->getRootItem()->getSubItem(
      pGenreTreeView->getRootItem()->getNumSubItems() - 1)->setSelected(
      true, false);
    }
    // Иначе - выделяем корень
    else pGenreTreeView->getRootItem()->setSelected(true, false);
  }
  // Если отмечен флажок "Редактировать свойства"
  else if(pButton == pEditPropertiesCheckBox)
  {
    // Делаем доступной кнопку "Применить" в зависмости от состояния флажка
    pApplyButton->setEnabled(pEditPropertiesCheckBox->getToggleState());
    // Если флажок отмечен и если выбранный элемент - не корень
    if(pEditPropertiesCheckBox->getToggleState() && 
      pGenreTreeView->getSelectedItem(0) != pGenreTreeView->getRootItem())
    {
      // отображаем в поле ввода имя выбранного элемента 
      pTextEdit->setText(
      pGenreTreeView->getSelectedItem(0)->getUniqueName());
    }
    // Иначе - очищаем поле ввода
    else pTextEdit->clear();
  }
  // Если нажата кнопка "Применить"
  else if(pButton == pApplyButton)
  {
    // Если выбранный элемент - корневой узел
    if(pGenreTreeView->getRootItem() == pGenreTreeView->getSelectedItem(0)) 
    {
      // то сообщаем пользователю о невозможности редактирования 
      // его свойств
      AlertWindow::showMessageBox(AlertWindow::WarningIcon, tr("Ошибка"), 
            tr("Нельзя изменить свойства корневого элемента!"), 
            tr("Принято"), 0);
     return;
    }

    // Если не введено новое имя узла, выходим... 
    if(pTextEdit->getText().isEmpty()) return;
    // Назначаем новое имя выбранному элементу
    ((TTreeViewItem*)pGenreTreeView->getSelectedItem(0))->SetNewName(
            pTextEdit->getText());
    this->repaint();
  }
}
Листинг 10.6. Реализация метода buttonClicked класса TCentralComponent (файл TCentralComponent.cpp)

Добавление нового элемента в список осуществляется вызовом нашей функции AddNewItem(), реализацию которой мы рассматривали выше.

В нашей программе пользователь не может ни удалить корневой элемент древовидного списка, ни изменить его свойства. Поэтому в обработчике события нажатия на кнопку "Удалить элемент" мы делаем проверку, не является ли выбранный пользователем узел корневым. Для этого мы сравниваем указатели выбранного пользователем элемента и корневого узла ( пример 10.6). Получить адрес одного из выделенных элементов дерева позволяет метод TreeViewItem* TreeView::getSelectedItem(int index) const throw(), где index — это номер узла древовидного списка, находящийся в диапазоне от 0 до (getNumSelectedItems() - 1). Метод int TreeView::getNumSelectedItems(int maximumDepthToSearchTo = -1) const throw() возвращает число выделенных элементов списка, где maximumDepthToSearchTo — глубина поиска. В случае, если параметр принимает значение -1, то поиск проводится по всему дереву.

Поскольку в нашем примере мы не вызывали функцию void TreeView::setMultiSelectEnabled(bool canMultiSelect) (см. примеры со списками из предыдущей главы), то пользователь может выбирать один и только один элемент. Поэтому в качестве параметра в функцию getSelectedItem мы передаём ноль ( пример 10.6).

В классе TreeViewItem имеется один весьма полезный метод int TreeViewItem::getIndexInParent() const throw(), который возвращает индекс выделенного элемента в наборе дочерних элементов родительского узла. Получить указатель на родительский элемент позволяет другой метод, TreeViewItem* TreeViewItem::getParentItem() const throw(). Ими мы и воспользуемся для удаления выбранного пользователем элемента. Мы запоминаем индекс выбранного узла — возвращаемое значение функции getIndexInParent — в целочисленной переменной, после чего находим выбранный пользователем элемент, находим его родительский узел, после чего удаляем элемент вызовом метода void TreeViewItem::removeSubItem(int index, bool deleteItem = true), где index — это номер удаляемого элемента в ряду субэлементов вызывающего узла, а true — флаг того, следует ли, помимо удаления субэлемента, также очистить его указатель. В качестве параметра мы передаём в этот метод переменную, в которой ранее запомнили адрес выбранного пользователем элемента ( пример 10.6).

Работа обработчика события нажатия на кнопку "Новый субэлемент" принципиально ничем не отличается от таковой обработчика события нажатия на кнопку "Новый элемент", т.е. в случае, если пользователем был выбран корневой узел, то нажатие на кнопку pNewSubItemButton приведёт к добавлению нового элемента, который будет субэлементом по отношению к корню. Поэтому добавление нового элемента / субэлемента можно было бы объединить в одном обработчике, но реализация нашей программы сделана таким образом, чтобы проиллюстрировать различные подходы к добавлению субэлемента. В целом обработчик события нажатия на кнопку "Новый субэлемент" является более универсальным. Использующиеся в нём методы рассматривались нами выше и не нуждаются в комментариях.

Перейдём к рассмотрению группы виджетов, заключённых в рамку pPropertiesGroup ("Свойства элемента"). Это поле ввода pTextEdit, которое мы уже использовали для задания текста добавляемого элемента, флажок pEditPropertiesCheckBox и кнопка применения внесённых в элемент изменений pApplyButton, которая при первом запуске программы неактивна ( пример 10.4).

Рассмотрим реализацию обработчика щелчка по флажку pEditPropertiesCheckBox ( пример 10.6). Пусть кнопка pApplyButton становится активной при включении флажка редактирования свойств элемента. При этом метод void Component::setEnabled(bool shouldBeEnabled) в качестве аргумента принимает возвращаемое значение функции bool Button::getToggleState() const throw(). Последняя возвращает true, если кнопка нажата, "включена" (в нашем случае — если флажок отмечен). Подробнее см. "Компоненты ввода и отображения текстовой, цифровой и иерархической информации. Компоненты отображения состояния" .

В том случае, если флажок не сброшен, в поле ввода нашей программы должно отображаться свойство элемента, доступное для редактирования, - его имя. Корневой элемент недоступен для редактирования, поэтому его имя не отображается ( пример 10.6).

Завершим разбор нашей программы рассмотрением обработчика события нажатия на кнопку pApplyButton ("Применить"). Если изменяемый элемент — корневой узел, сообщаем пользователю о невозможности изменения его свойств и выходим из функции ( пример 10.6). В противном случае даём новое имя выбранному пользователем элементу, при условии, что текст в поле ввода — не пустая строка.

Мы находим выбранный пользователем элемент с помощью уже знакомого метода getSelectedItem. Поскольку его возвращаемое значениеуказатель на TreeViewItem, необходимо привести его к типу TTreeViewItem* для того, чтобы получить доступ к методу void TTreeViewItem::SetNewName(String) нашего класса элемента древовидного списка ( пример 10.6).

В завершение заметим, что загрузка данных в древовидный список возможна и из файла. В демонстрационном приложении, входящем в поставку Juce, приводится пример загрузки данных в дерево из файла XML. Посмотреть реализацию примера можно, перейдя в папку <каталог Juce>/juce/extras/JuceDemo/Source/demos и открыв файл TreeViewDemo.cpp.

Особенности Juce 2.0

В классе TreeViewItem произошли незначительные изменения в методе getUniqueName: virtual String TreeViewItem::getUniqueName() const

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

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

Упражнение

Напишите программу, отображающую категории товаров какого-то абстрактного магазина.

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

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

< Лекция 9 || Лекция 10: 1234 || Лекция 11 >