Возникла необходимость построить дерево структурных подразделений в приложении Qt. Решение задачи решил реализовать с помощью виджета QTreeWidget, так как дерево не очень большое и редко (практически никогда) не изменяется. Вообще то создать дерево не представляет особого труда, но ввиду того того что к узлам дерева необходимо привязывать пользовательские объекты, приходится учитывать кое какие моменты. Ниже опишу как решается такого рода проблема имея Qt и СУБД FireBird 2.5.
Структура базы данных
Чтобы читатель имел понятие о том с какими данными приходится работать, кратко опишу структуру моей БД.
Имеется 3 таблицы следующего содержания:
Поле | Описание |
---|---|
DEP_ID | Ид. подразделения |
DEP_NAME | Полное название |
DESCRIPTION | Описание |
DEP_KSORT | Сокращенное название |
DEP_SKNAME | Порядок сортировки |
Поле | Описание |
---|---|
SERV_ID | Ид. службы |
DEP_ID | Ид. подразделения |
SERV_NAME | Полное название |
DESCRIPTION | Описание |
SERV_SKNAME | Сокращенное название |
Поле | Описание |
---|---|
SECT_ID | Ид. сектора |
SERV_ID | Ид. службы |
DEP_ID | Ид. подразделения |
SECT_NAME | Полное название |
DESCRIPTION | Описание |
SECT_SKNAME | Сокращенное описание |
В принципе с таблицами должно быть все понятно. Подразделение содержит службы, а служба содержит сектора.
Сборка драйвера QIBASE для Qt
Прежде чем приступать к работе с базой, необходимо собрать драйвер QIBASE. Как собрать можно почитать здесь. От себя только добавлю, что в моем случае (система windows 64 разряда) команды сборки были такими:
c:\Qt\qt-4.8.4\src\plugins\sqldrivers\ibase>qmake "INCLUDEPATH+=c:/Firebird_2_5/include" "LIBS+=c:/Firebird_2_5/WOW64/lib/fbclient_ms.lib" ibase.pro mingw32-make -f Makefile.Debug mingw32-make -f Makefile.Release
После успешной сборки я поместил два файла ...\debug\qsqlibased4.dll
и ...\release\qsqlibase4.dll
в папку c:\Qt\qt-4.8.4\plugins\sqldrivers\
.
Извлечение набора данных для дерева
Чтобы упростить себе жизнь с формированием дерева, я решил извлекать данные вместе с уровнями узлов в дереве. При этом большая часть логики ложится на сервер БД, а это плюс. Сам запрос в виде хранимой процедуры выглядит таким образом:
CREATE OR ALTER PROCEDURE ENT_TREE RETURNS ( ENT_ID INTEGER, ENT_PARENT_ID INTEGER, ENT_NAME VARCHAR(300), ENT_SMALL_NAME VARCHAR(50), ENT_LEVEL VARCHAR(12) ) AS DECLARE VARIABLE DEP_ID INTEGER; DECLARE VARIABLE SERV_ID INTEGER; BEGIN ENT_ID = NULL; ENT_PARENT_ID = NULL; ENT_NAME = NULL; ENT_SMALL_NAME = NULL; ENT_LEVEL = NULL; FOR SELECT DEP_ID, 0, DEP_NAME, DEP_SKNAME, 0 FROM SEC_DEPARTMENTS DEP WHERE DEP.DEP_KSORT < 9 ORDER BY DEP.DEP_KSORT ASC INTO :ENT_ID, :ENT_PARENT_ID, :ENT_NAME, :ENT_SMALL_NAME, :ENT_LEVEL DO BEGIN DEP_ID = ENT_ID; SUSPEND; FOR SELECT SERV_ID, DEP_ID, SERV_NAME, SERV_SKNAME, 1 FROM SEC_SERVICES SERV WHERE SERV.DEP_ID = :DEP_ID ORDER BY SERV.SERV_NAME COLLATE WIN1251_UA INTO :ENT_ID, :ENT_PARENT_ID, :ENT_NAME, :ENT_SMALL_NAME, :ENT_LEVEL DO BEGIN SERV_ID = ENT_ID; SUSPEND; FOR SELECT SECT_ID, SERV_ID, SECT_NAME, SECT_SKNAME, 2 FROM SEC_SECTORS SECT WHERE (SECT.DEP_ID = :DEP_ID) AND (SECT.SERV_ID = :SERV_ID) ORDER BY SECT.SECT_NAME COLLATE WIN1251_UA INTO :ENT_ID, :ENT_PARENT_ID, :ENT_NAME, :ENT_SMALL_NAME, :ENT_LEVEL DO BEGIN SUSPEND; END END END END
В FireBird 2.5 есть возможность выполнять хранимые процедуры как простые запросы к базе, используя EXECUTE BLOCK
. Этим я и решил воспользоватся.
Формирование дерева
Выше упоминалось, что в моем случае нужно было привязывать пользовательские объекты к узлам дерева. Для хранения этих данных я создал простой класс капсулу TreeData
примерно такого вида:
#ifndef TREEDATA_H #define TREEDATA_H #include <qvariant.h> class TreeData { public: TreeData(); TreeData(unsigned int level, unsigned int entId); TreeData(TreeData*); ~TreeData(); unsigned int level(); unsigned int entId(); void setLevel(unsigned int level); void setEntId(unsigned int entId); private: unsigned int _level; unsigned int _entId; }; Q_DECLARE_METATYPE(TreeData*) #endif // TREEDATA_H
Реализация геттеров и сеттеров так же как и конструкторов тривиальна и не нуждается в описании.
Теперь сам код для формирования дерева
#include <treedata.h> //... void MainWindow::setTreeDepartments() { ui->treeDepartments->setColumnCount(1); QSqlQuery query("EXECUTE BLOCK " "RETURNS ( " "ENT_ID INTEGER, " "ENT_PARENT_ID INTEGER, " "ENT_NAME VARCHAR(300), " "ENT_SMALL_NAME VARCHAR(50), " "ENT_LEVEL VARCHAR(12)) " "AS " "DECLARE VARIABLE DEP_ID INTEGER; " "DECLARE VARIABLE SERV_ID INTEGER; " "BEGIN " "ENT_ID = NULL; " "ENT_PARENT_ID = NULL; " "ENT_NAME = NULL; " "ENT_SMALL_NAME = NULL; " "ENT_LEVEL = NULL; " " " "FOR SELECT DEP_ID, 0, DEP_NAME, DEP_SKNAME, 0 " "FROM SEC_DEPARTMENTS DEP " "WHERE DEP.DEP_KSORT > 9 " "ORDER BY DEP.DEP_KSORT ASC " "INTO :ENT_ID, :ENT_PARENT_ID, :ENT_NAME, :ENT_SMALL_NAME, :ENT_LEVEL " "DO BEGIN " "DEP_ID = ENT_ID; " "SUSPEND; " " " "FOR SELECT SERV_ID, DEP_ID, SERV_NAME, SERV_SKNAME, 1 " "FROM SEC_SERVICES SERV " "WHERE SERV.DEP_ID = :DEP_ID " "ORDER BY SERV.SERV_NAME COLLATE WIN1251_UA " "INTO :ENT_ID, :ENT_PARENT_ID, :ENT_NAME, :ENT_SMALL_NAME, :ENT_LEVEL " "DO BEGIN " "SERV_ID = ENT_ID; " "SUSPEND; " " " "FOR SELECT SECT_ID, SERV_ID, SECT_NAME, SECT_SKNAME, 2 " "FROM SEC_SECTORS SECT " "WHERE (SECT.DEP_ID = :DEP_ID) AND (SECT.SERV_ID = :SERV_ID) " "ORDER BY SECT.SECT_NAME COLLATE WIN1251_UA " "INTO :ENT_ID, :ENT_PARENT_ID, :ENT_NAME, :ENT_SMALL_NAME, :ENT_LEVEL " "DO BEGIN " "SUSPEND; " "END " "END " "END " "END "); QTreeWidgetItem *level0, *level1, *level2; while ( query.next() ){ TreeData *td = new TreeData(query.value(4).toUInt(), query.value(0).toUInt()); switch (query.value(4).toInt()) { case 0:{ level0 = new QTreeWidgetItem; level0->setText(0, query.value(2).toString()); level0->setData(0, Qt::UserRole, qVariantFromValue(td)); ui->treeDepartments->addTopLevelItem(level0); break; } case 1:{ level1 = new QTreeWidgetItem; level1->setText(0, query.value(2).toString()); level1->setData(0, Qt::UserRole, qVariantFromValue(td)); level0->addChild(level1); break; } case 2:{ level2 = new QTreeWidgetItem; level2->setText(0, query.value(2).toString()); level2->setData(0, Qt::UserRole, qVariantFromValue(td)); level1->addChild(level2); break; } default: break; } } }
Ниже описан слот для кнопки, по нажатию на которую я извлекаю данные и вывожу их в QMessageBox
:
void MainWindow::btnTestClick() { QList<QTreeWidgetItem*> selected = ui->treeDepartments->selectedItems(); QVariant tdVariant = selected.at(0)->data(0, Qt::UserRole); TreeData *td = tdVariant.value<TreeData*>(); QString res = QString("Level: %1; EntId: %2").arg(td->level()).arg(td->entId()); QMessageBox::information(this, "Information", res); }
Удачи!
Комментариев нет:
Отправить комментарий