• 键鼠自动化2.0树形结构讲解


    介绍

    在键鼠自动化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; //item
    
        int number = -1; //临时使用
    
        // 重载==运算符以判断nodeText是否相等
        bool operator==(const TreeNode& other) const {
            return nodeText == other.nodeText;
        }
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    部分核心代码

    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);
                            //删除来源item
                            this->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);
                            //删除来源item
                            this->removeTopItem(sourceNode);
                        }
                        else {
                            qDebug() << MyDebug << "未知动作!!!!!!!!!!!!!!!!!!!!!!!!";
                        }
                    }
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    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" << targetNode->nodeItemTest << targetItem->row() << node->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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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);
        //获取是删除还是del
        void onReadIsAddAndDel(int isAdd, bool isHide);
        //更新视图数据
        void onUpdateView();
    protected:
        void wheelEvent(QWheelEvent* event);
        void mousePressEvent(QMouseEvent *event);
        void mouseReleaseEvent(QMouseEvent *event);
        void mouseDoubleClickEvent(QMouseEvent *event);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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, 255
            QBrush backgroundBrush(QColor(185, 232, 255));
            painter->fillRect(opt.rect, backgroundBrush);
        }
        else if (opt.state & QStyle::State_MouseOver) {
            // 进入状态下的背景颜色为225, 243, 255
            QBrush 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  • 相关阅读:
    jsonschema脚本测试
    Optional详解
    Spring WebFlux 实践
    07--Zabbix监控告警
    【Apollo】Apollo的入门介绍
    8月最新修正版风车IM即时聊天通讯源码+搭建教程
    HTML学习笔记
    Echarts 柱状图逐条加载动画
    Charles下载与安装教程(超详细)
    分享一下大转盘抽奖小程序怎么做
  • 原文地址:https://blog.csdn.net/LuXiaoXin1999/article/details/134488453