1. 程式人生 > 其它 >QT 實現自定義樹狀導航欄

QT 實現自定義樹狀導航欄

技術標籤:QT學習之路mvcqt

一、介紹

未經允許,禁止轉載!

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 &)));
}

專案原始碼:

傳送門
各位觀眾老爺,如果覺得有點幫助請點個贊,給咱漲漲積分