介绍

在键鼠自动化2.0中使用Qtc++实现了全自定义树形结构,实现任务的拖拽,复制粘贴,撤销重做,以及包括树形结构增加序号展示,以及增加搜索功能

树形结构展示

实现

1.自定义节点

// 自定义节点类class TreeNode : public QObject {public:TreeNode(QObject *parent = nullptr): QObject(parent) {}public:bool isInChild = false; //是否接受子节点QString nodeText; //用于判断的节点名称QString nodeItemTest; //显示的名称QVariant taskData; //数据存储QList<TreeNode*> children; //子节点TreeNode* parent = nullptr; //父节点MyStandardItem* item; //itemint number = -1; //临时使用// 重载==运算符以判断nodeText是否相等bool operator==(const TreeNode& other) const {return nodeText == other.nodeText;}};

2.拖拽功能

重写拖拽相关函数

 void startDrag(Qt::DropActions supportedActions);void dragLeaveEvent(QDragLeaveEvent* event);void dragEnterEvent(QDragEnterEvent *event);void dragMoveEvent(QDragMoveEvent *event);void dropEvent(QDropEvent *event);void paintEvent(QPaintEvent* event);

部分核心代码

if (sourceNode->parent == nullptr && targetNode->parent == nullptr) { //父与父//当前是父节点与父节点直接拖拽int sourceRow = sourceNode->item->row();qDebug() << MyDebug << "1111111111111111" << targetRow << sourceRow;if (targetRow != sourceRow) {//在目标源下插入一行if (sourceNode->children.isEmpty()) {TreeNode* node;if (isAppendParent) {node = this->appendChileItem(targetNode, sourceNode);}else {node = this->insertTopItem(sourceNode, targetRow);}cmdAdd->appNodeList(node);this->selectionModel()->select(node->item->index(), QItemSelectionModel::SelectCurrent);//删除来源itemthis->removeTopItem(sourceNode);}else if (!sourceNode->children.isEmpty()) {//如果来源里面有子节点就需要递归插入,先查入头节点TreeNode* newParentNode;if (isAppendParent) {newParentNode = this->appendChileItem(targetNode, sourceNode);}else {newParentNode = this->insertTopItem(sourceNode, targetRow);}this->selectionModel()->select(newParentNode->item->index(), QItemSelectionModel::SelectCurrent);//递归插入for (int i = 0 ; i < sourceNode->children.size(); ++i) {this->RecursionInsert(newParentNode, sourceNode->children.at(i));}cmdAdd->appNodeList(newParentNode);//删除来源itemthis->removeTopItem(sourceNode);}else {qDebug() << MyDebug << "未知动作!!!!!!!!!!!!!!!!!!!!!!!!";}}}

3.复制粘贴

void MyTreeView::copyItemRow(){QModelIndexList selectedIndexes = this->selectedIndexes();if (selectedIndexes.size() <= 0) return;// 使用自定义的比较函数进行排序(从大到小)std::sort(selectedIndexes.begin(), selectedIndexes.end(), compareModelIndex);m_CopyListData.clear();for (int i = 0; i < selectedIndexes.size(); ++i) {MyStandardItem* sourceItem = dynamic_cast<MyStandardItem*>(model->itemFromIndex(selectedIndexes.at(i)));TreeNode* sourceNode = findNodeByText(sourceItem->m_NodeText, m_TreeListData);TreeNode* nodeData = new TreeNode;nodeData->isInChild = sourceNode->isInChild;nodeData->nodeText = sourceNode->nodeText;nodeData->nodeItemTest = sourceNode->nodeItemTest;nodeData->parent = sourceNode->parent;nodeData->taskData = sourceNode->taskData;if (!sourceNode->children.isEmpty()) {QList<TreeNode*> childNodeList;RecursionCopyData(sourceNode, childNodeList);nodeData->children = childNodeList;}m_CopyListData << nodeData;}}void MyTreeView::pasteItemRow(){QModelIndexList selectedIndexes = this->selectedIndexes();MyStandardItem* targetItem;if (selectedIndexes.size() <= 0) {targetItem = dynamic_cast<MyStandardItem*>(model->item(model->rowCount() - 1));}else {std::sort(selectedIndexes.begin(), selectedIndexes.end(), compareModelIndex);targetItem = dynamic_cast<MyStandardItem*>(model->itemFromIndex(selectedIndexes.first()));}if (!targetItem) return;TreeNode* targetNode = findNodeByText(targetItem->m_NodeText, m_TreeListData);QList<TreeNode*> undoNodeList;for (int i = 0; i < m_CopyListData.size(); ++i) {TreeNode* node = m_CopyListData.at(i);//判断目标行是父节点还是子节点if (targetNode->parent == nullptr) {TreeNode* newParentNode = insertTopItem(node, targetItem->row() + 1);//如果存在子节点递归插入if (!node->children.isEmpty()) {for (int number = 0 ; number < node->children.size(); ++number) {RecursionInsert(newParentNode, node->children.at(number), false);}}undoNodeList << newParentNode;}else {// qDebug() << MyDebug << "222222222222222" <nodeItemTest <row() <nodeItemTest;TreeNode* nodeTemp = insertChileItem(targetNode->parent, node, targetItem->row() + 1);if (!node->children.isEmpty()) {for (int number = 0 ; number < node->children.size(); ++number) {RecursionInsert(nodeTemp, node->children.at(number), false);}}undoNodeList << nodeTemp;}}AddRowCommand* cmd = new AddRowCommand(this, undoNodeList);m_Undo.append(cmd);m_Redo.clear();emit signalUpdateTableView();}

4.撤销重做

实现基类command,虚函数撤销与重做,依次实现add和del以及多行拖拽类存储。

class Command {public:virtual ~Command() = default;virtual void undo() = 0;virtual void redo() = 0;};class AddRowCommand : public Command{public:AddRowCommand(MyTreeView* view, QList<TreeNode*> nodeList = QList<TreeNode*>());void appNodeList(TreeNode* nodeData);void undo();void redo();private:MyTreeView* m_View;QList<int> m_RowList;QList<bool> m_IsParentList;QList<QString> m_ParentNodeText;QList<TreeNode*> m_NodeList;};//该类注意事项//1.要注意先增加类对象再删除node,否则删除后再增加找不到子节点和自身对象等等问题class DelRowCommand : public Command{public:DelRowCommand(MyTreeView* view, QList<TreeNode*> nodeList = QList<TreeNode*>());void appNodeList(TreeNode* nodeData);void undo();void redo();private:MyTreeView* m_View;QList<int> m_RowList; //item的行存储,因为item会丢失所以保存行QList<bool> m_IsParentList; //保存item是否是父节点QList<QString> m_ParentNodeText; //父节点nodetext保存QList<TreeNode*> m_NodeList;};class DragRowCommand : public Command{public:DragRowCommand(MyTreeView* view, QList<Command*> cmdList, bool bigToSmall);void undo();void redo();private:MyTreeView* m_View;QList<Command*> m_CmdList;bool m_BigToSmall;};

5.行号

如何实现QTreeView的行号功能?
这里采用了QTabelView功能,布局中放下了tableview和treeview,行号在treeview的左侧,当滚动或者新增数据,来更新一下视图数据,当展开或者合并节点,同步更新数据,来实现所有节点的独立行号。

public slots://读取滚动条数据同步void onReadScrollValue(int value);//遍历节点子节点下的所有数量int CountTotalChildren(TreeNode* node);//递归更新子节点数据void RecursionUpdateChild(TreeNode* node);//展开void onEntered(const QModelIndex &index);//合并void onCollapsed(const QModelIndex &index);//获取是删除还是delvoid onReadIsAddAndDel(int isAdd, bool isHide);//更新视图数据void onUpdateView();protected:void wheelEvent(QWheelEvent* event);void mousePressEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);void mouseDoubleClickEvent(QMouseEvent *event);

6. QTreeview的搜索功能

核心代码节点递归搜索,递归搜索节点返回所有包含的数据,通过存到列表中,加一个当前index来实现上下的切换,

QList<TreeNode*> MyTreeView::findNodeItemText(const QString &findStr){QList<TreeNode*> TreeNodeList;for (int i = 0; i < m_TreeListData.size(); ++i) {TreeNode* node = m_TreeListData.at(i);recursiveFindNodeByTextItem(findStr, node, TreeNodeList);}return TreeNodeList; // 未找到匹配的节点}void MyTreeView::recursiveFindNodeByTextItem(const QString &targetText, TreeNode *currentNode, QList<TreeNode *> &list){QString nodeStr = currentNode->nodeItemTest;nodeStr.remove(Tools::getInstance()->m_HtmlTitleBegin);nodeStr.remove(Tools::getInstance()->m_HtmlTitleEnd);nodeStr.remove(Tools::getInstance()->m_HtmlTextBegin);nodeStr.remove(Tools::getInstance()->m_HtmlTextEnd);nodeStr.remove(Tools::getInstance()->m_HtmlHiglightBegin);nodeStr.remove(Tools::getInstance()->m_HtmlHiglightEnd);if (nodeStr.contains(targetText)) {list.append(currentNode);}for (TreeNode* child : currentNode->children) {recursiveFindNodeByTextItem(targetText, child, list);}}

7.其他功能

1.关于如何实现的treeview的富文本
重新绘制的富文本,当然会比正常文本的资源消耗更高,速度较慢,实际测试几十万行并不会卡,但是比较慢一点,日常能接受。

//自定义代理类用来绘制文字富文本class RichTextDelegate : public QStyledItemDelegate{public:RichTextDelegate(QObject* parent = nullptr): QStyledItemDelegate(parent){}void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;};void RichTextDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const{QString text = index.data(Qt::DisplayRole).toString();QStyleOptionViewItem opt = option;initStyleOption(&opt, index);painter->save();// 手动绘制背景,用于选中和进入的样式if (opt.state & QStyle::State_Selected) {// 选中状态下的背景颜色为204, 232, 255QBrush backgroundBrush(QColor(185, 232, 255));painter->fillRect(opt.rect, backgroundBrush);}else if (opt.state & QStyle::State_MouseOver) {// 进入状态下的背景颜色为225, 243, 255QBrush backgroundBrush(QColor(235, 243, 255));painter->fillRect(opt.rect, backgroundBrush);}// 手动绘制文本QTextDocument doc;doc.setHtml(text);opt.text = "";painter->translate(option.rect.topLeft());doc.drawContents(painter);painter->restore();}