БлогNot. QT: строим дерево строк на основе QTreeWidget

QT: строим дерево строк на основе QTreeWidget

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

Создадим проект-потомок QWidget, компоненты интерфейса разместим при помощи "волшебных комбинаций клавиш" Ctrl+H и Ctrl+L:

  • в режиме дизайна добавим на форму 2 кнопки, выделим обе при зажатой клавише Ctrl, нажмём комбинацию Ctrl+H, получили горизонтальное "обрамление" QHBoxLayout;
  • выше кнопок добавим на форму компоненту QTreeWidget, выделим её и красную рамку вокруг кнопок при зажатой клавише Ctrl, нажмём комбинацию Ctrl+L, получили вертикальный QVBoxLayout;
  • щёлкнули по форме вне всех компонент, нажали Ctrl+H - растянули компоненты на всю форму.

На рисунке видно, что должно выйти, а также подписи на кнопках.

форма приложения QTree
форма приложения QTree

Выбирать элементы дерева мы будем кликом по ним, так что нажмём правую кнопку мыши на пустом treeWidget и создадим единственный слот (команды Перейти к слоту - itemClicked(QTreeWidgetItem *, int)).

А для кнопок аналогично выберем самый простой слот clicked() и создадим ещё 2 пустые функции.

В приватной секции файла widget.h допишем имена своих методов и свойств, которые мы добавляем в класс, вот что получится:

private:
    Ui::Widget *ui;
    //ниже - наши данные
    int treeCount(QTreeWidget *, QTreeWidgetItem *); //подсчёт количества элементов в QTreeWidget
    void DeleteItem (QTreeWidgetItem *currentItem); //удаление элемента из QTreeWidget
    void InsertItem (QTreeWidgetItem *, QString); //добавление элемента в QTreeWidget
    void showAll(void); //вывод информации о QTreeWidget
    int count; //переменная для хранения номера очередного узла
    QTreeWidgetItem *currentItem; //текущий элемент, запоминается при клике в QTreeWidget
    int currentColumn; //номер столбца, на самом деле будет = 0, т.к. у нас 1 столбец

Не забудем также подключить библиотеку QtWidgets:

#include <QWidget>
#include <QtWidgets>

Теперь напишем код файла widget.cpp. В конструктор добавим инициализацию наших данных:

Widget::Widget(QWidget *parent) :  QWidget(parent), ui(new Ui::Widget) {
 ui->setupUi(this);
 //ниже наша часть конструктора
 count = 0;
 ui->treeWidget->setColumnCount(1);
 QStringList headers;
 headers << tr("Объекты");
 ui->treeWidget->setHeaderLabels(headers);
 currentItem = NULL;
 currentColumn = 0;
}

Метод подсчёта количества узлов в QTreeWidget нам придётся сделать довольно громоздким, так как готового свойства нет и не может быть (нужно с видом использовать модель, и тогда всё будет, но для начинающих сразу начать это делать сложновато):

int Widget::treeCount(QTreeWidget *tree, QTreeWidgetItem *parent = 0) {
/*
 не учтёт свёрнутые ветви; потому что правильно было бы делать через модель
*/
 tree->expandAll(); //а это "костыль"
 int count = 0;
 if (parent == 0) {
  int topCount = tree->topLevelItemCount();
  for (int i = 0; i < topCount; i++) {
   QTreeWidgetItem *item = tree->topLevelItem(i);
   if (item->isExpanded()) {
    count += treeCount(tree, item);
   }
  }
  count += topCount;
 }
 else {
  int childCount = parent->childCount();
  for (int i = 0; i < childCount; i++) {
   QTreeWidgetItem *item = parent->child(i);
   if (item->isExpanded()) {
    count += treeCount(tree, item);
   }
  }
  count += childCount;
 }
 return count;
}

Удаление элемента учтёт случаи, когда элемент имеет родителя и когда является корневым:

void Widget::DeleteItem (QTreeWidgetItem *currentItem) {
 QTreeWidgetItem *parent = currentItem->parent();
 int index;
 if (parent) {
  index = parent->indexOfChild(ui->treeWidget->currentItem());
  delete parent->takeChild(index);
 }
 else {
  index = ui->treeWidget->indexOfTopLevelItem(ui->treeWidget->currentItem());
  delete ui->treeWidget->takeTopLevelItem(index);
 }
}

Вставка будет предполагать, что родитель есть, иначе придётся писать 2 разных метода вставки с различными параметрами:

void Widget::InsertItem (QTreeWidgetItem *parent, QString text) {
 if (parent->isExpanded()==false) parent->setExpanded(true);
 QTreeWidgetItem *newItem = new QTreeWidgetItem(parent, ui->treeWidget->currentItem());
 newItem->setText (currentColumn, text);
 newItem->setExpanded(true);
}

По кнопке "Добавить" будет выполняться следующее:

void Widget::on_pushButton_clicked() { //кнопка Добавить
 if (currentItem) {
  QString word = currentItem->text(currentColumn);
  InsertItem  (currentItem, word + " " + QString("%1").arg(++count));
 }
 else {
  QTreeWidgetItem *newItem = new QTreeWidgetItem(ui->treeWidget, ui->treeWidget->currentItem());
   //указываем 2-м параметром текущий элемент как предшествующий
  newItem->setText (currentColumn, "" + QString("%1").arg(++count));
  newItem->setExpanded(true);
 }
 currentItem = NULL;
 showAll();
}

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

Кнопка "Удалить", по сути, только вызовет написанный выше метод:

void Widget::on_pushButton_2_clicked() { //кнопка Удалить
 if (currentItem) {
  DeleteItem (currentItem);
  currentItem = NULL;
 }
 showAll();
}

Если никакой элемент не выделен, то удалять она тоже ничего не будет.

Служебная функция showAll позаботится об отображении вычисленного количества узлов в заголовке окна виджета.

void Widget::showAll(void) {
 int cnt = treeCount (ui->treeWidget);
 QString str(tr("Всего: ")+QString("%1").arg(cnt));
 setWindowTitle(str);
}

Ну и наш единственный слот "дерева" просто будет запоминать в дополнительных свойствах класса, по какому именно элементу мы щёлкнули:

void Widget::on_treeWidget_itemClicked(QTreeWidgetItem *item, int column) {
 currentItem = item;
 currentColumn = column;
}

Это всё, проект можно запускать и выйдет вот что:

окно виджета QTree, на рисунке выделен элемент
окно виджета QTree, на рисунке выделен элемент

Почему наше решение вышло довольно неуклюжим? А потому что правильно было бы использовать MVC. Но об этом в другой раз, возможно, на таком же простом "списочном" или "древовидном" примере :)

 Скачать архив .zip с папкой этого проекта QT5 (3 Кб)

06.04.2016, 23:39 [30372 просмотра]


теги: список c++ qt программирование учебное textprocessing

К этой статье пока нет комментариев, Ваш будет первым