QT 实现自定义树状导航栏_qt树状导航栏-程序员宅基地

技术标签: qt  QT学习之路  mvc  

一、介绍

|版本声明:山河君,未经博主允许,禁止转载

1.使用技术

Qt包含一组使用模型/视图结构的类,可以用来管理数据并呈现给用户。这种体系结构引入的分离使开发人员更灵活地定制项目,并且提供了一个标准模型的接口,以允许广泛范围的数据源被使用到到现有的视图中。
模型 - 视图 - 控制器(MVC)是一种设计模式,由三类对象组成:

模型:应用程序对象。

视图:屏幕演示。

控制器:定义了用户界面响应用户输入的方式
在这里插入图片描述
Qt把视图和控制器组合在一起,从而形成模型/视图结构。模型直接与数据进行通信,并为视图和委托提供访问数据的接口。

2.效果

展开前:
在这里插入图片描述
展开后:

在这里插入图片描述

传送门(项目源码)

二、实现(重要代码都已加注释)

1.CNavModel模型类

头文件:

#ifndef CNAVMODEL_H
#define CNAVMODEL_H

#include <QAbstractListModel>
#include <QList>
#include <QVector>

class CNavModel : public QAbstractListModel
{
    
    Q_OBJECT
public:
    struct TreeNode
    {
    
        QString qsLableName;
        int nLevel;
        bool collapse;
        int nIndex;
        QList<TreeNode* > listChildren;
    };

    struct ListNode
    {
    
        QString qsLabelName;
        TreeNode* pTreeNode;
    };

public:
    explicit CNavModel(QObject *parent = nullptr);
    ~CNavModel();
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

public:
    void ReadConfig(QString qsPath);    //读取导航栏节点配置文件
    void Refresh(); //刷新模型,我这里没有用到

public slots:
    void Collapse(const QModelIndex& index);    //节点展开、收缩槽函数

private:
    void RefreshList();     //刷新当前界面显示的节点

private:
    QVector<TreeNode*>  m_vecTreeNode;      //用于存储对应关系
    QVector<ListNode>   m_vecListNode;      //存储所有的节点
};

#endif // CNAVMODEL_H

实现:

#include "CNavModel.h"
#include <QFile>
#include <QDomDocument>
#include <QDebug>

CNavModel::CNavModel(QObject *parent)
    : QAbstractListModel(parent)
{
    
}

CNavModel::~CNavModel()
{
    

    QVector<TreeNode*>  m_vecTreeNode;      
    QVector<ListNode>   m_vecListNode;      
    for(QVector<TreeNode*>::iterator it = m_vecTreeNode.begin(); it != m_vecTreeNode.end(); it++)
    {
    
        if((*it)->listChildren.size())
        {
    
            for (QList<TreeNode*>::iterator itChild = (*it)->listChildren.begin(); itChild != (*it)->listChildren.end(); it++)
                delete (*itChild);
        }
        delete (*it);
    }

    m_vecListNode.clear();
    m_vecTreeNode.clear();
}

int CNavModel::rowCount(const QModelIndex &parent) const	//返回行数
{
    
    return m_vecListNode.size();
}

QVariant CNavModel::data(const QModelIndex &index, int role) const
{
    
    if ( !index.isValid() )
        return QVariant();

    if ( index.row() >= m_vecListNode.size() || index.row() < 0 )
        return QVariant();

    if ( role == Qt::DisplayRole )      //显示文字
        return m_vecListNode[index.row()].qsLabelName;
    else if ( role == Qt::UserRole )    //用户定义起始位置
        return QVariant::fromValue((void*)m_vecListNode[index.row()].pTreeNode);

    return QVariant();
}

void CNavModel::ReadConfig(QString qsPath)
{
    
    QFile xmlFile(qsPath);
    if(!xmlFile.open(QFile::ReadOnly | QFile::Text))
        return;

    QDomDocument docXml;
    QString error;
    if(!docXml.setContent(&xmlFile, false, &error))
    {
    
        xmlFile.close();
        return;
    }

    QDomElement xmlRoot = docXml.documentElement(); //返回根节点
    QDomNode domNode = xmlRoot.firstChild(); //获取子节点,一级节点
    while (!domNode.isNull())
    {
    
        if(domNode.isElement())
        {
    
            QDomElement domElement = domNode.toElement();   //一级节点
            TreeNode* pTreeNode = new TreeNode;

            pTreeNode->qsLableName = domElement.attribute("lable");//获取一级节点的lable
            pTreeNode->nLevel = 1;  //标志一级节点
            pTreeNode->collapse =  domElement.attribute("collapse").toInt(); //标志是否展开
            pTreeNode->nIndex = domElement.attribute("index").toInt();  //获取标志

            QDomNodeList list = domElement.childNodes();    //获取二级节点
            for(int i = 0; i < list.count(); i++)
            {
    
                QDomElement secNodeInfo = list.at(i).toElement();
                TreeNode* pSecNode = new TreeNode;
                pSecNode->qsLableName = secNodeInfo.attribute("lable");
                pSecNode->nLevel = 2;
                pSecNode->nIndex = secNodeInfo.attribute("index").toInt();
                pTreeNode->collapse = false;
                pTreeNode->listChildren.push_back(pSecNode);
            }
            m_vecTreeNode.push_back(pTreeNode);
        }
        domNode = domNode.nextSibling();    //下一一级节点
    }

    xmlFile.close();
    RefreshList();  //刷新界面标题栏展示数据
    beginInsertRows(QModelIndex(), 0, m_vecListNode.size());    //插入所有节点
    endInsertRows(); //结束插入
}

void CNavModel::RefreshList()
{
    
    m_vecListNode.clear();
    for(QVector<TreeNode*>::iterator it = m_vecTreeNode.begin(); it != m_vecTreeNode.end(); it++)
    {
    
        //一级节点
        ListNode node;
        node.qsLabelName = (*it)->qsLableName;
        node.pTreeNode = *it;
        m_vecListNode.push_back(node);

        if(!(*it)->collapse) //如果一级节点未展开,则插入下一一级节点
            continue;

        for(QList<TreeNode*>::iterator child = (*it)->listChildren.begin(); child != (*it)->listChildren.end(); child++)
        {
    
            ListNode node;
            node.qsLabelName = (*child)->qsLableName;
            node.pTreeNode = *child;
            m_vecListNode.push_back(node);
        }
    }
}

void CNavModel::Collapse(const QModelIndex& index)
{
    
    TreeNode* pTreeNode = m_vecListNode[index.row()].pTreeNode; //获取当前点击节点
    if(pTreeNode->listChildren.size() == 0) //如果该节点没有子节点 则返回
        return;

    pTreeNode->collapse = !pTreeNode->collapse; //刷新是否展开标志

    if(!pTreeNode->collapse)    //如果是不展开,即为展开变成合并,移除合并的
    {
    
        beginRemoveRows(QModelIndex(), index.row() + 1, pTreeNode->listChildren.size()); //默认起始节点为最初节点
        endRemoveRows();
    }
    else {
    
        beginInsertRows(QModelIndex(), index.row() + 1, pTreeNode->listChildren.size());
        endInsertRows();
    }
    RefreshList(); //更新界面显示节点数据
}

void CNavModel::Refresh()
{
    
    RefreshList();
    beginResetModel();
    endResetModel();
}

2.视图类源码

这里采用的是QListView视图进行列表显示,用户可以进行自行更改视图样式
头文件

#ifndef CNAVVIEW_H
#define CNAVVIEW_H

#include <QObject>
#include <QListView>

class CNavView : public QListView
{
    
    Q_OBJECT

public:
    CNavView(QWidget *parent);
    ~CNavView();
};

#endif // CNAVVIEW_H

实现:这里只修改了背景色等

#include "CNavView.h"
#include <QColor>

CNavView::CNavView(QWidget *parent) : QListView (parent)
{
    
    setStyleSheet(
        QString(
        "QListView{background-color:%1;"
        "border:0px solid %2;"
        "border-right-width:1px;}")
        .arg("239, 241, 250")
        .arg("214, 216, 224"));
}

CNavView::~CNavView()
{
    

}

3.委托(重要)

引入委托的目的:项目数据显示和自定义编辑。
头文件:

#ifndef CNAVDELEGATE_H
#define CNAVDELEGATE_H

#include <QObject>
#include <QStyledItemDelegate>

class CNavDelegate : public QStyledItemDelegate
{
    
    Q_OBJECT
public:
    CNavDelegate(QObject* parent);
    ~CNavDelegate();

public:
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;

    void SetPending(bool pending) {
     m_pending = pending; }

private:
    QString GetImagePath(int nIndex) const;

private:
    bool m_pending;
};

#endif // CNAVDELEGATE_H

实现:

#include "CNavDelegate.h"
#include "CNavModel.h"
#include <QPainter>
#include <QColor>

CNavDelegate::CNavDelegate(QObject *parent)
    :QStyledItemDelegate(parent)
    , m_pending(false)
{
    

}

CNavDelegate::~CNavDelegate()
{
    

}

QSize CNavDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    
    CNavModel::TreeNode* pTreeNode = (CNavModel::TreeNode*)index.data(Qt::UserRole).value<void*>();
    if(pTreeNode->nLevel == 1)
        return QSize(50, 45);
    else
        return QSize(50, 28);
}

void CNavDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    
    CNavModel::TreeNode* pTreeNode = (CNavModel::TreeNode*)index.data(Qt::UserRole).value<void*>();
    painter->setRenderHint(QPainter::Antialiasing); //防走样

    //根据绘制时提供的信息进行背景色绘制
    if ( option.state & QStyle::State_Selected )
    {
    
        painter->fillRect(option.rect, QColor(133, 153, 216));
    }
    else if ( option.state & QStyle::State_MouseOver )
    {
    
        painter->fillRect(option.rect, QColor(209, 216, 240));
    }
    else
    {
    
        if ( pTreeNode->nLevel == 1 )
            painter->fillRect(option.rect, QColor(247, 249, 255));
        else
            painter->fillRect(option.rect, QColor(239, 241, 250));
    }

    //添加图片
    if(pTreeNode->listChildren.size() != 0)
    {
    
        QString qsImagePath;
        if(!pTreeNode->collapse)
        {
    
            if ( option.state & QStyle::State_Selected )
                qsImagePath = ":/image/unexpand_selected.png";
            else
                qsImagePath = ":/image/unexpand_normal.png";
        }
        else {
    
            if ( option.state & QStyle::State_Selected )
                qsImagePath = ":/image/expand_selected.png";
            else
                qsImagePath = ":/image/expand_normal.png";
        }

        //设置图片大小
        QPixmap img(qsImagePath);
        QRect targetRect = option.rect;
        targetRect.setWidth(16);
        targetRect.setHeight(16);

        //设置图片坐标
        QPoint c = option.rect.center();
        c.setX(8);
        targetRect.moveCenter(c);

        //将图片放到对应位置
        painter->drawPixmap(targetRect, qsImagePath, img.rect());
    }
    else {
    
        QString qsPath = GetImagePath(pTreeNode->nIndex);
        if(qsPath.size())
        {
    
            QPixmap img(qsPath);
            QRect targetRect = option.rect;
            targetRect.setWidth(16);
            targetRect.setHeight(16);

            //设置图片坐标
            QPoint c = option.rect.center();
            c.setX(12);
            targetRect.moveCenter(c);

            //将图片放到对应位置
            painter->drawPixmap(targetRect, qsPath, img.rect());
        }
    }

    //添加文字
    QPen textPen( option.state & QStyle::State_Selected ? QColor(255, 255, 255) : QColor(58, 58, 58));
    painter->setPen(textPen);

    int margin = 25;

    if ( pTreeNode->nLevel == 2 )
        margin = 45;

    QRect rect = option.rect;
    rect.setWidth(rect.width() - margin);
    rect.setX(rect.x() + margin);

    QFont normalFont("Microsoft Yahei", 9);
    painter->setFont(normalFont);
    painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString() );

    //在每一行下方划线
    QPen linePen(QColor(214, 216, 224));
    linePen.setWidth(1);
    painter->setPen(linePen);

    if ( pTreeNode->nLevel == 1
        || (pTreeNode->nLevel == 2 ) )
    {
    
        painter->drawLine(
            QPointF(option.rect.x(), option.rect.y()+option.rect.height()-1),
            QPointF(option.rect.x() + option.rect.width(), option.rect.y()+option.rect.height()-1));
    }
}

QString CNavDelegate::GetImagePath(int nIndex) const
{
    
    switch (nIndex)
    {
    
    case 1:
        return QString();
    case 13:
        return QString(":/image/Purchase.png");
    case 16:
        return QString(":/image/Tasklist.png");
    case 17:
        return QString(":/image/Trendchart.png");
    default:
        return QString();
    }
}

4.使用:

void CMainFrm::SetNavigationBar()
{
    
    CNavModel* pNavModel = new CNavModel(this);
    CNavDelegate* pDelegate = new CNavDelegate(this);
    pNavModel->ReadConfig(QCoreApplication::applicationDirPath() += "/config/navigation.xml");
    ui->listView->setModel(pNavModel);
    ui->listView->setItemDelegate(pDelegate);
    connect(ui->listView, SIGNAL(doubleClicked(const QModelIndex &)), pNavModel, SLOT(Collapse(const QModelIndex &)));
}

项目源码:

传送门
如果对您有所帮助,还请帮忙点个赞

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42956179/article/details/110817651

智能推荐

【Django缓存实现】前端缓存和后端缓存_django内置缓存-程序员宅基地

文章浏览阅读2.6k次。缓存(cache),原始意义是指访问速度比一般随机存取存储器(RAM)快的一种高速存储器,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一。缓存的优点汇总,加快页面打开速度,减少网络带宽消耗,降低服务器压力。具体工作原理可参考:缓存_百度百科 (baidu.com)。通俗的说,这里涉及到计算机的各种存储,内存、磁盘、cpu等都算是计算机的存储器。_django内置缓存

Java实现最小费用最大流问题_代码解决最小费用最大流问题-程序员宅基地

文章浏览阅读1.2w次,点赞20次,收藏8次。1 问题描述在最大流有多组解时,给每条边在附上一个单位费用的量,问在满足最大流时的最小费用是多少?2 解决方案下面代码所使用的测试数据如下图:package com.liuzhen.practice;import java.util.ArrayList;import java.util.Scanner;public class Main { public static ..._代码解决最小费用最大流问题

Java实现 LeetCode 810 黑板异或游戏 (分析)_java 黑板上写着一个非负整数数组 nums[i] 。alice 和 bob 轮流从黑板上擦掉一个-程序员宅基地

文章浏览阅读9.1k次。810. 黑板异或游戏一个黑板上写着一个非负整数数组 nums[i] 。小红和小明轮流从黑板上擦掉一个数字,小红先手。如果擦除一个数字后,剩余的所有数字按位异或运算得出的结果等于 0 的话,当前玩家游戏失败。 (另外,如果只剩一个数字,按位异或运算得到它本身;如果无数字剩余,按位异或运算结果为 0。)换种说法就是,轮到某个玩家时,如果当前黑板上所有数字按位异或运算结果等于 0,这个玩家获胜。假设两个玩家每步都使用最优解,当且仅当小红获胜时返回 true。示例:输入: nums = [1, 1, 2_java 黑板上写着一个非负整数数组 nums[i] 。alice 和 bob 轮流从黑板上擦掉一个

(模板题)sdut 3362 数据结构实验之图论六:村村通公路(prim求最小生成树)_现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使-程序员宅基地

文章浏览阅读530次。数据结构实验之图论六:村村通公路Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^题目描述当前农村公路建设正如火如荼的展开,某乡镇政府决定实现村村通公路,工程师现有各个村落之间的原始道路统计数据表,表中列出了各村之间可以建设公路的若干条道路的成本,你的任务是根据给出的数据表,求使得每个村都有公路_现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使

基于结巴分词的文本余弦相似度计算_.net jieba分词里的相似度算法-程序员宅基地

文章浏览阅读555次。多维空间余弦公式:举一个具体的例子,假如:句子 X 和句子 Y 对应向量分别是x1,x2,...,x64000 和y1,y2,...,y64000,那么它们夹角的余弦等于一二三冲鸭:使用余弦相似度算法计算文本相似度55 赞同 · 10 评论文章这篇文章个人觉得不太好理解,在研读之后,自己总结了一个通俗易懂的算法。import jiebaimport math s1 = '这只皮靴号码大了。那只号码合适's1_cut = [i for i in jieba.cut(s1, c_.net jieba分词里的相似度算法

html5 django游戏,html5、django上传文件-程序员宅基地

文章浏览阅读80次。使用简单的表单上传文件到django框架form表单需要指定 enctype="multipart/form-data"前端代码:uploadFile后端代码:def upload(request):##request.FILES 为类字典类型,健为前段name指定的值,字典值为UploadFile对象,前端form中需要指定enctype="multipart/form-data"##uploa..._django+h5 上传文件

随便推点

java new Filereader_Java FileReader类-程序员宅基地

文章浏览阅读695次。首页>基础教程>IO流>Reader类Java FileReader类FileReader用于以字符为单位读取文本文件,能够以字符流的形式读取文件内容。除了读取的单位不同之外,FileReader与FileInputStream并无太大差异,也就是说,FileReader用于读取文本。根据不同的编码方案,一个字符可能会相当于一个或者多个字节。构造函数FileReade..._java new filereader

Rewwit的用法及优点_前端代理rew-程序员宅基地

文章浏览阅读141次。提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言前言前言现在Nginx已经成为很多公司作为前端反向代理(proxy pass)服务器的首选,在实际工作中往往会遇到很多跳转(重写URL)的需求。比如:更换域名后需要保持旧的域名能跳转到新的域名上、某网页发生改变需要跳转到新的页面、网站防盗链等等需求。如果在后端使用的 Apache服务器,虽然也能做跳转,规则库也很强大,但是用Nginx跳转效率会更高一.Rewite跳转场景1、调整用户浏览的URL,看起来更规范,合乎_前端代理rew

第四届蓝桥杯JavaA组省赛真题_第四届蓝桥杯省赛a组-程序员宅基地

文章浏览阅读1.1w次,点赞19次,收藏3次。解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论题目1、世纪末的星期题目描述曾有邪教称1999年12月31日是世界末日。当然该谣言已经不攻自破。还有人称今后的某个世纪末的12月31日,如果是星期一则会....有趣的是,任何一个世纪末的年份的12月31日都不可能是星期一!! 于是,“谣言制造商”又修改为星期日......1999年的12月31日是星期五,请问:未来哪一..._第四届蓝桥杯省赛a组

网卡的认识_怎么理解 高速平面网卡 csdn-程序员宅基地

文章浏览阅读531次。---------网卡是构成网络的一部分-------------- 网卡通常有两种:一种是插在计算机主板的插槽中,另一种是集成在我们的主板上。他的主要功能是, 是处理计算机的数据转换为能够通过介质传输的信号。 广义上讲我们得网卡有两部分组_怎么理解 高速平面网卡 csdn

html5游戏安全性 抓包,使用被动混合内容的方式来跨越浏览器会阻断HTTPS上的非安全请求(HTTP)请求的安全策略抓包详解...-程序员宅基地

文章浏览阅读319次。/*通过传入loginId在token中附加loginId参数,方便后续读取指定缓存中的指定用户信息*/GET /multitalk/takePhone.php?loginId=4edc153568311361687793 HTTP/1.1Host: txl.cytxl.com.cnConnection: keep-alivePragma: no-cacheCache-Control: no-ca..._html5网页游戏数据抓包

记一次MySQL 5.7.10的binlog文件轮转(rotate)引起卡顿(block)出现慢日志的处理过程_mysql5.7切binlog导致服务性能抖动-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏2次。描述:程序反映当一个游戏服开服一段时间后,其使用的MySQL每隔一段时间都会出现几十条慢日志,慢日志集中在1到2秒内连续刷出,卡顿平均在60秒左右。一天当中每个时间段都有概率出现,即使是半夜3,4点没什么玩家的时候。定位(trouble troubleshooting):根据描述,基本排除是程序代码的问题,因为游戏高峰期打世界BOSS的时候(每天中午12点玩家最活跃的时候)都不会..._mysql5.7切binlog导致服务性能抖动

推荐文章

热门文章

相关标签