前言
qt中定义了很多简单实用的数据展示视图控件,比如表格和树形表等。由于表格的结构和数据库存储类似,所以qt提供一个类来实现表格视图和数据库的直接连接。但是对于树形结构,在数据库中的表达和视图的表达有较大差异,qt尚未提供相关的功能。
本文的背景是要做一个模板的管理功能。由于模板的组织方式是层级结构(树形结构),所以要用到树形控件。又要求具有持久化保存模板的功能,将来还可能扩展的远程的模板统一管理,所以使用数据库进行模板的保存。
所以本文要介绍的是笔者最近做的一个基于QTreeWidget的自定义树形控件,可以实现右击菜单,每次更改树结构后实时修改数据库,初始化时从数据库读取树形结构并显示。
具体效果如下:
但由于内容比较多,笔者打算分为两篇进行介绍,首先在这一篇中,介绍qt控件的继承以及在界面中把控件提升(promete)为自定义的控件。在下一篇中,介绍如何把对树的操作写入数据库以及如何在初始化过程中从数据库读取树结构。
具体实现
控件类的定义
首先,自定义的控件要继承自QTreeWidget,然后需要添加一些增加根节点,删除节点,增加目录,增加模板,重命名节点等想要实现功能的槽函数,这些槽函数会在后面右击菜单中调用。右击菜单在qt中是一个action,需要定义几个QAction变量。initTreeWidget()函数在程序启动时从数据库读取树形结构,但是本文只介绍控件的自定义相关内容,这个函数中会生成一个固定的树。
class QMyTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit QMyTreeWidget(QWidget *parent = 0);
private slots:
void on_treeWidget_customContextMenuRequested(const QPoint &pos);
void addRootNode();
void deleteAllNodes();
void addCategory();
void addTemplate();
void deleteNode();
void reNameNode();
private:
void initTreeWidget();
QTreeWidgetItem* curItem; //当前被右击的item
//空白地方点击
QAction *addRootNodeAction;
QAction *deleteAllNodesAction;
//节点上点击
QAction *addCategoryAction;
QAction *addTemplateAction;
QAction *deleteNodeAction;
QAction *reNameNodeAction;
};
action和槽函数连接
addRootNodeAction = new QAction("&addRootNode", this);
deleteAllNodesAction = new QAction("&deleteAllNodes", this);
addCategoryAction = new QAction("&addCategory", this);
addTemplateAction = new QAction("&addTemplate", this);
deleteNodeAction = new QAction("&deleteNode", this);
reNameNodeAction = new QAction("&reNameNode", this);
connect(addRootNodeAction,SIGNAL(triggered()),this,SLOT(addRootNode()));
connect(deleteAllNodesAction,SIGNAL(triggered()),this,SLOT(deleteAllNodes()));
connect(addCategoryAction,SIGNAL(triggered()),this,SLOT(addCategory()));
connect(addTemplateAction,SIGNAL(triggered()),this,SLOT(addTemplate()));
connect(deleteNodeAction,SIGNAL(triggered()),this,SLOT(deleteNode()));
connect(reNameNodeAction,SIGNAL(triggered()),this,SLOT(reNameNode()));
右击菜单实现
首先,需要把控件的contextMenuPolicy选项设为CustomContextMenu
然后把右击菜单动作和槽函数连接起来:
connect(this,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(on_treeWidget_customContextMenuRequested(QPoint)));
在槽函数中实现不同位置点击弹出不同的菜单
void QMyTreeWidget::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
curItem=this->itemAt(pos); //获取当前被点击的节点
//在空白位置点击,弹出菜单:添加根节点,删除所有模板。
if(curItem == NULL)
{
QMenu *popMenu =new QMenu(this);//定义一个右键弹出菜单
popMenu->addAction(addRootNodeAction);
popMenu->addAction(deleteAllNodesAction);
popMenu->exec(QCursor::pos());
}
else{
QVariant var = curItem->data(0,Qt::UserRole);
qDebug() << var.toString() << endl;
//读取表,对isCategory字段进行判断
if(1) //选中目录
{
qDebug() << "category" << endl;
QMenu *popMenu =new QMenu(this);//定义一个右键弹出菜单
popMenu->addAction(addCategoryAction);
popMenu->addAction(addTemplateAction);
popMenu->addAction(deleteNodeAction);
popMenu->addAction(reNameNodeAction);
popMenu->exec(QCursor::pos());
}
else //选中模板
{
qDebug() << "template" << endl;
QMenu *popMenu =new QMenu(this);//定义一个右键弹出菜单
popMenu->addAction(deleteNodeAction);
popMenu->addAction(reNameNodeAction);
popMenu->exec(QCursor::pos());
}
}
}
槽函数实现过程
重命名节点:
void QMyTreeWidget::reNameNode()
{
qDebug() << "reNameNode" << endl;
QInputDialog dia(this);
dia.setWindowTitle("Input Dialog");
dia.setLabelText("Please input text:");
dia.setInputMode(QInputDialog::TextInput);//可选参数:DoubleInput TextInput
if(dia.exec() == QInputDialog::Accepted)
{
qDebug() << dia.textValue();
curItem->setText(0,dia.textValue());
//更新数据库
//curTtem->data dia.textValue()
}
}
删除节点:
void QMyTreeWidget::deleteNode()
{
qDebug() << "deleteNode" << endl;
//curItem->data() 当前UUID 删除该索引
QTreeWidgetItem* parent = curItem->parent();
if(parent == NULL)
{
//得到索引
int index = 0;
int count = this->topLevelItemCount();
for(int i = 0;i<count;i++)
{
//QTreeWidget
//ui->treeWidget->TopLevelItem()
QTreeWidgetItem* temp = this->topLevelItem(i);
if(curItem->data(0,Qt::UserRole).toString() == temp->data(0,Qt::UserRole).toString())
{
index = i;
}
}
this->takeTopLevelItem(index);
}
else
parent->removeChild(curItem);
//删除数据库一行
//curTtem->data
}
增加一个叶节点,该节点下不能再添加节点
void QMyTreeWidget::addTemplate()
{
qDebug() << "addTemplate" << endl;
//新节点ID 根 模板名称 颜色值 是否目录
//随机UUID 当前UUID title colors 0
//curItem->data() 当前UUID
QTreeWidgetItem *temp=new QTreeWidgetItem(curItem);
QInputDialog dia(this);
dia.setWindowTitle("Input Dialog");
dia.setLabelText("Please input text:");
dia.setInputMode(QInputDialog::TextInput);//可选参数:DoubleInput TextInput
if(dia.exec() == QInputDialog::Accepted)
{
qDebug() << dia.textValue();
temp->setText(0,dia.textValue());
QVariant var;
QString str = QUuid::createUuid().toString();
var.setValue(str);
temp->setData(0,Qt::UserRole,var);
}
}
添加一个目录,该节点可以有子节点
void QMyTreeWidget::addCategory()
{
qDebug() << "addCategory" << endl;
//在TEMPLATE表中新加一行
//新节点ID 根 模板名称 颜色值 是否目录
//随机UUID 当前UUID title colors 1
//curItem->data() 当前UUID
QTreeWidgetItem *temp=new QTreeWidgetItem(curItem);
QInputDialog dia(this);
dia.setWindowTitle("Input Dialog");
dia.setLabelText("Please input text:");
dia.setInputMode(QInputDialog::TextInput);//可选参数:DoubleInput TextInput
if(dia.exec() == QInputDialog::Accepted)
{
qDebug() << dia.textValue();
temp->setText(0,dia.textValue());
QVariant var;
QString str = QUuid::createUuid().toString();
var.setValue(str);
temp->setData(0,Qt::UserRole,var);
//数据库添加一行
//UUID: str Title:dia.textValue()
}
}
删除所有节点:
void QMyTreeWidget::deleteAllNodes()
{
qDebug() << "deleteAllNodes" << endl;
this->clear();
//清空数据库表
}
添加根节点,在整个树为空时,在任意位置点击可以选择
void QMyTreeWidget::addRootNode()
{
qDebug() << "addRootNode" << endl;
//在TEMPLATE表中新加一行
//新节点ID 根 模板名称 颜色值 是否目录
//随机UUID -1 title colors 1
QTreeWidgetItem *temp=new QTreeWidgetItem(this);
QInputDialog dia(this);
dia.setWindowTitle("Input Dialog");
dia.setLabelText("Please input text:");
dia.setInputMode(QInputDialog::TextInput);//可选参数:DoubleInput TextInput
if(dia.exec() == QInputDialog::Accepted)
{
qDebug() << dia.textValue();
temp->setText(0,dia.textValue());
QVariant var;
QString str = QUuid::createUuid().toString();
var.setValue(str);
temp->setData(0,Qt::UserRole,var);
}
}
初始化一个固定的树
void QMyTreeWidget::initTreeWidget()
{
//初始化时从数据库读取当前树结构,需要使用递归的方法
//initTreeWidget() 的实现
this->clear();
//ui->treeWidget->clear();
this->setHeaderHidden(true);
//ui->treeWidget->setHeaderHidden(true);
//第一组
QTreeWidgetItem *group1=new QTreeWidgetItem(this);
group1->setText(0,"group1");
QVariant var;
QString str = QUuid::createUuid().toString();
var.setValue(str);
group1->setData(0,Qt::UserRole,var);
QTreeWidgetItem *item11=new QTreeWidgetItem(group1);
item11->setText(0,"item11");
QTreeWidgetItem *newnew=new QTreeWidgetItem(item11);
newnew->setText(0,"newnew");
QTreeWidgetItem *item12=new QTreeWidgetItem(group1);
item12->setText(0,"item12");
QTreeWidgetItem *item13=new QTreeWidgetItem(group1);
item13->setText(0,"item13");
//第二组
QTreeWidgetItem *group2=new QTreeWidgetItem(this);
group2->setText(0,"group2");
QTreeWidgetItem *item21=new QTreeWidgetItem(group2);
item21->setText(0,"item21");
QTreeWidgetItem *item22=new QTreeWidgetItem(group2);
item22->setText(0,"item22");
QTreeWidgetItem *item23=new QTreeWidgetItem(group2);
item23->setText(0,"item23");
item23->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
item23->setCheckState(0,Qt::Unchecked);
}
至此,控件的自定义已经完成。这个demo可以实现右键操作,并且控件结构随之变化。但是在程序重新启动后,上次更改过的内容全部丢失。要解决问题,需要用数据库保存树结构。这将在下一篇中进行介绍。预知后事如何,请听下回分解。