Возникла необходимость построить дерево структурных подразделений в приложении 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);
}
Удачи!
Комментариев нет:
Отправить комментарий