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

介绍

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

树形结构展示

实现

1.自定义节点

cpp 复制代码
// 自定义节点类
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;
    }
};

2.拖拽功能

重写拖拽相关函数

cpp 复制代码
   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);

部分核心代码

cpp 复制代码
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 << "未知动作!!!!!!!!!!!!!!!!!!!!!!!!";
                    }
                }
            }

3.复制粘贴

cpp 复制代码
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();
}

4.撤销重做

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

cpp 复制代码
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的左侧,当滚动或者新增数据,来更新一下视图数据,当展开或者合并节点,同步更新数据,来实现所有节点的独立行号。

cpp 复制代码
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);

6. QTreeview的搜索功能

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

cpp 复制代码
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的富文本

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

cpp 复制代码
//自定义代理类用来绘制文字富文本
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();
}
相关推荐
追风赶月、12 分钟前
【Linux】线程概念与线程控制
linux·运维·服务器
工业3D_大熊17 分钟前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化
CP-DD25 分钟前
Docker 容器化开发 应用
运维·docker·容器
暮色_年华31 分钟前
Modern Effective C++ Item 11:优先考虑使用deleted函数而非使用未定义的私有声明
c++
流星白龙33 分钟前
【C++习题】10.反转字符串中的单词 lll
开发语言·c++
Smile丶凉轩1 小时前
微服务即时通讯系统的实现(服务端)----(1)
c++·git·微服务·github
萝卜兽编程1 小时前
优先级队列
c++·算法
Bruce小鬼1 小时前
QT文件基本操作
开发语言·qt
懷淰メ2 小时前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
努力的悟空2 小时前
国土变更调查拓扑错误自动化修复工具的研究
运维·自动化