QT 實現自定義樹狀導航欄
阿新 • • 發佈:2020-12-08
一、介紹
未經允許,禁止轉載!
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 &)));
}
專案原始碼:
傳送門
各位觀眾老爺,如果覺得有點幫助請點個贊,給咱漲漲積分