大部分的软件都有多个页面,这时候就需要一个导航栏控件,通过在导航栏中选择某一栏,同时显示对应的页面。
本文代码效果如下:
本文的导航栏控件基于大佬 feiyangqingyun 的导航栏控件博客Qt/C++编写自定义控件46-树状导航栏_qt之实现自定义树状图控件-CSDN博客做了美化,修复了一些会导致崩溃的bug。
本文代码:https://download.csdn.net/download/Sakuya__/89420773?spm=1001.2014.3001.5501https://download.csdn.net/download/Sakuya__/89420773?spm=1001.2014.3001.5501 也可以在这里下载大佬的代码学习:NavListView: Qt 自定义的树形导航控件https://gitee.com/qt-open-source-collection/NavListView
代码之路
NavListView.h
#ifndef NAVLISTVIEW_H
#define NAVLISTVIEW_H
#include <QStyledItemDelegate>
#include <QAbstractListModel>
#include <QListView>
#include <vector>
class NavListView;
class NavDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
NavDelegate(QObject *parent);
~NavDelegate();
protected:
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const ;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
private:
NavListView *nav;
};
class NavModel : public QAbstractListModel
{
Q_OBJECT
public:
NavModel(QObject *parent);
~NavModel();
public:
struct TreeNode {
QString iconName;
QString label;
int level;
bool collapse;
bool theFirst;
bool theLast;
QString info;
std::list<TreeNode *> children;
};
struct ListNode {
QString label;
TreeNode *treeNode;
};
protected:
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
private:
std::vector<TreeNode *> treeNode;
std::vector<ListNode> listNode;
public slots:
void readData(QString path);
void setData(QStringList listItem);
void collapse(const QModelIndex &index);
private:
void refreshList();
};
class NavListView : public QListView
{
Q_OBJECT
public:
enum IcoStyle {IcoStyle_Cross = 0, IcoStyle_Triangle = 1};
NavListView(QWidget *parent);
~NavListView();
bool getInfoVisible() const {
return infoVisible;
}
bool getLineVisible() const {
return lineVisible;
}
bool getIcoColorBg() const {
return icoColorBg;
}
IcoStyle getIcoStyle() const {
return style;
}
QColor getColorLine() const {
return colorLine;
}
/// ====== 获取背景颜色函数
QColor getColorBgNormal() const{
return colorBgNormal;
}
QColor getColorBgSelected() const{
return colorBgSelected;
}
QColor getColorBgHover() const{
return colorBgHover;
}
QColor getColorBgNormalLeval2() const{
return colorBgNormalLeval2;
}
QColor getColorBgSelectedLeval2() const{
return colorBgSelectedLeval2;
}
QColor getColorBgHoverLeval2() const{
return colorBgHoverLeval2;
}
/// ====== 获取文字颜色函数
QColor getColorTextNormal() const {
return colorTextNormal;
}
QColor getColorTextSelected() const {
return colorTextSelected;
}
QColor getColorTextHover() const {
return colorTextHover;
}
QColor getColorTextNormalLeval2() const {
return colorTextNormalLeval2;
}
QColor getColorTextSelectedLeval2() const {
return colorTextSelectedLeval2;
}
QColor getColorTextHoverLeval2() const {
return colorTextHoverLeval2;
}
public slots:
// 读取xml文件数据
void readData(QString xmlPath);
// 设置数据集合
void setData(QStringList listItem);
// 设置当前选中行
void setCurrentRow(int row);
// 设置是否显示提示信息
void setInfoVisible(bool infoVisible);
// 设置是否显示间隔线条
void setLineVisible(bool lineVisible);
// 设置伸缩图片是否采用背景色
void setIcoColorBg(bool icoColorBg);
// 设置伸缩图片样式
void setIcoStyle(IcoStyle style);
/// ====== 设置各种前景色背景色选中色
void setColorLine(QColor colorLine);
void setColorBg(QColor colorBgNormal, QColor colorBgSelected, QColor colorBgHover);
void setColorText(QColor colorTextNormal, QColor colorTextSelected, QColor colorTextHover);
void setColorBgLeval2(QColor colorBgNormal, QColor colorBgSelected, QColor colorBgHover);
void setColorTextLeval2(QColor colorTextNormal, QColor colorTextSelected, QColor colorTextHover);
private:
NavModel *model;
NavDelegate *delegate;
bool infoVisible; // 是否显示提示信息
bool lineVisible; // 是否显示分割线条
bool icoColorBg; // 伸缩图片是否使用颜色
IcoStyle style; // 图标样式
QColor colorLine; // 线条颜色
/// ====== leval为1时的效果
QColor colorBgNormal; // 正常背景色
QColor colorBgSelected; // 选中背景色
QColor colorBgHover; // 悬停背景色
QColor colorTextNormal; // 正常文字颜色
QColor colorTextSelected; // 选中文字颜色
QColor colorTextHover; // 悬停文字颜色
/// ====== leval为2时的效果
QColor colorBgNormalLeval2; // 正常背景颜色
QColor colorBgSelectedLeval2; //
QColor colorBgHoverLeval2; //
QColor colorTextNormalLeval2; // 正常文字颜色
QColor colorTextSelectedLeval2; //
QColor colorTextHoverLeval2; //
};
#endif // NAVLISTVIEW_H
NavListView.cpp
#include "NavListView.h"
#include <QPainter>
#include <QFile>
#include <qdom.h>
#include <QDebug>
NavDelegate::NavDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
nav = (NavListView *)parent;
}
NavDelegate::~NavDelegate()
{
}
QSize NavDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
NavModel::TreeNode *node = (NavModel::TreeNode *)index.data(Qt::UserRole).toULongLong();
if (node->level == 1)
{
return QSize(192, 71);
}
else
{
return QSize(182, 48);
}
}
void NavDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->setRenderHint(QPainter::Antialiasing);
NavModel::TreeNode *node = (NavModel::TreeNode *)index.data(Qt::UserRole).toULongLong();
QColor colorBg;
QColor colorText;
QFont fontText;
int iconSize = 0, leftMargin = 0, topMargin = 0;
if(1 == node->level)
{
if (option.state & QStyle::State_Selected)
{
colorBg = nav->getColorBgSelected();
colorText = nav->getColorTextSelected();
}
else if (option.state & QStyle::State_MouseOver)
{
colorBg = nav->getColorBgHover();
colorText = nav->getColorTextHover();
}
else
{
colorBg = nav->getColorBgNormal();
colorText = nav->getColorTextNormal();
}
iconSize = 32;
leftMargin = 32;
topMargin = 20;
fontText.setPixelSize(20);
painter->setBrush(QBrush(nav->getColorBgNormal()));
painter->setPen(Qt::transparent);
painter->drawRoundedRect(option.rect, 8, 20, Qt::RelativeSize);
}
else if(2 == node->level)
{
if (option.state & QStyle::State_Selected)
{
colorBg = nav->getColorBgSelectedLeval2();
colorText = nav->getColorTextSelectedLeval2();
}
else if (option.state & QStyle::State_MouseOver)
{
colorBg = nav->getColorBgHoverLeval2();
colorText = nav->getColorTextHoverLeval2();
}
else
{
colorBg = nav->getColorBgNormalLeval2();
colorText = nav->getColorTextNormalLeval2();
}
iconSize = 24;
leftMargin = 25;
topMargin = 13;
fontText.setPixelSize(18);
QRect rectLevel2 = option.rect;
rectLevel2.setX(option.rect.x() + 12);
if (node->theFirst)
{
rectLevel2.setHeight(option.rect.height() + 4);
rectLevel2.setWidth(option.rect.width() + 8);
painter->setBrush(QBrush(nav->getColorBgNormalLeval2()));
painter->setPen(Qt::transparent);
painter->drawRoundedRect(rectLevel2, 8, 20, Qt::RelativeSize);
}
else if (node->theLast)
{
rectLevel2.setY(option.rect.y() - 4);
rectLevel2.setWidth(option.rect.width() + 8);
painter->setBrush(QBrush(nav->getColorBgNormalLeval2()));
painter->setPen(Qt::transparent);
painter->drawRoundedRect(rectLevel2, 8, 20, Qt::RelativeSize);
}
else
{
painter->fillRect(rectLevel2, nav->getColorBgNormalLeval2());
}
}
/// ====== 菜单选项背景颜色
if (1 == node->level && option.state & QStyle::State_Selected)
{
QRect rectMenu = option.rect;
rectMenu.setWidth(option.rect.width() - 20);
rectMenu.setHeight(option.rect.height()- 10);
rectMenu.setX(option.rect.x() + 10);
rectMenu.setY(option.rect.y() + 10);
painter->setBrush(QBrush(colorBg));
painter->setPen(Qt::transparent);
painter->drawRoundedRect(rectMenu, 8, 20, Qt::RelativeSize);
}
/// ====== 绘制图标
QPixmap pixMap;
pixMap.load(node->iconName);
QRect rectIcon = option.rect;
rectIcon.setX(option.rect.x()+leftMargin);
rectIcon.setY(option.rect.y()+topMargin);
rectIcon.setWidth(iconSize);
rectIcon.setHeight(iconSize);
painter->drawPixmap(rectIcon, pixMap);
/// ====== 绘制条目文字
if(option.state & QStyle::State_Selected)
{
painter->setOpacity(1);
}
else
{
painter->setOpacity(0.5);
}
painter->setPen(QPen(colorText));
int margin = 72;
if (node->level == 2)
{
margin = 84;
}
QRect rect = option.rect;
rect.setX(rect.x() + margin);
painter->setFont(fontText);
painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString());
painter->setOpacity(1);
/// ====== 绘制分割线
QRect rectLine = option.rect;
rectLine.setX(option.rect.x()+16);
rectLine.setY(option.rect.y()-1);
rectLine.setWidth(168);
rectLine.setHeight(1);
QPixmap pixMapLine;
pixMapLine.load(":/Images/Line.png");
painter->drawPixmap(rectLine, pixMapLine);
}
NavModel::NavModel(QObject *parent) : QAbstractListModel(parent)
{
}
NavModel::~NavModel()
{
for (std::vector<TreeNode *>::iterator it = treeNode.begin(); it != treeNode.end();) {
for (std::list<TreeNode *>::iterator child = (*it)->children.begin(); child != (*it)->children.end();) {
delete(*child);
child = (*it)->children.erase(child);
}
delete(*it);
it = treeNode.erase(it);
}
}
void NavModel::readData(QString path)
{
QFile xml(path);
if (!xml.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
QDomDocument doc;
if (!doc.setContent(&xml, false))
{
return;
}
treeNode.clear();
listNode.clear();
QDomNode root = doc.documentElement().firstChildElement("layout");
QDomNodeList children = root.childNodes();
for (int i = 0; i != children.count(); ++i)
{
QDomElement nodeInfo = children.at(i).toElement();
TreeNode *node = new TreeNode;
node->label = nodeInfo.attribute("label");
node->collapse = nodeInfo.attribute("collapse").toInt();
node->info = nodeInfo.attribute("info");
node->level = 1;
QDomNodeList secondLevel = nodeInfo.childNodes();
for (int j = 0; j != secondLevel.count(); ++j)
{
QDomElement secNodeInfo = secondLevel.at(j).toElement();
TreeNode *secNode = new TreeNode;
secNode->label = secNodeInfo.attribute("label");
secNode->info = secNodeInfo.attribute("info");
secNode->collapse = false;
secNode->level = 2;
secNode->theLast = (j == secondLevel.count() - 1 && i != children.count() - 1);
node->children.push_back(secNode);
}
treeNode.push_back(node);
}
refreshList();
beginResetModel();
endResetModel();
}
void NavModel::setData(QStringList listItem)
{
int count = listItem.count();
if (count == 0) {
return;
}
treeNode.clear();
listNode.clear();
// listItem格式: 标题|父节点标题(父节点为空)|是否展开|提示信息
for (int i = 0; i < count; i++)
{
QString item = listItem.at(i);
QStringList list = item.split("|");
if (list.count() < 4)
{
continue;
}
// 首先先将父节点即父节点标题为空的元素加载完毕
QString title = list.at(0);
QString fatherTitle = list.at(1);
QString collapse = list.at(2);
QString info = list.at(3);
QString iconFile = list.at(4);
if (fatherTitle.isEmpty())
{
TreeNode *node = new TreeNode;
node->label = title;
node->collapse = collapse.toInt();
node->info = info;
node->level = 1;
node->iconName = iconFile;
// 先计算该父节点有多少个子节点
int secCount = 0;
for (int j = 0; j < count; j++)
{
QString secItem = listItem.at(j);
QStringList secList = secItem.split("|");
if (secList.count() < 4)
{
continue;
}
QString secFatherTitle = secList.at(1);
if (secFatherTitle == title)
{
secCount++;
}
}
// 查找该父节点是否有对应子节点,有则加载
int currentCount = 0;
for (int j = 0; j < count; j++)
{
QString secItem = listItem.at(j);
QStringList secList = secItem.split("|");
if (secList.count() < 4)
{
continue;
}
QString secTitle = secList.at(0);
QString secFatherTitle = secList.at(1);
QString secInfo = secList.at(3);
QString secIconName = secList.at(4);
if (secFatherTitle == title)
{
currentCount++;
TreeNode *secNode = new TreeNode;
secNode->label = secTitle;
secNode->info = secInfo;
secNode->collapse = false;
secNode->level = 2;
secNode->theFirst = (currentCount == 1);
secNode->theLast = (currentCount == secCount);
secNode->iconName = secIconName;
node->children.push_back(secNode);
}
}
treeNode.push_back(node);
}
}
refreshList();
beginResetModel();
endResetModel();
}
int NavModel::rowCount(const QModelIndex &parent) const
{
return listNode.size();
}
QVariant NavModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= listNode.size() || index.row() < 0) {
return QVariant();
}
if (role == Qt::DisplayRole) {
return listNode[index.row()].label;
} else if (role == Qt::UserRole) {
return reinterpret_cast<quint64>(listNode[index.row()].treeNode);
}
return QVariant();
}
void NavModel::refreshList()
{
listNode.clear();
for (std::vector<TreeNode *>::iterator it = treeNode.begin(); it != treeNode.end(); ++it) {
ListNode node;
node.label = (*it)->label;
node.treeNode = *it;
listNode.push_back(node);
if ((*it)->collapse) {
continue;
}
for (std::list<TreeNode *>::iterator child = (*it)->children.begin(); child != (*it)->children.end(); ++child) {
ListNode node;
node.label = (*child)->label;
node.treeNode = *child;
node.treeNode->theLast = false;
listNode.push_back(node);
}
if (!listNode.empty()) {
listNode.back().treeNode->theLast = true;
}
}
}
void NavModel::collapse(const QModelIndex &index)
{
TreeNode *node = listNode[index.row()].treeNode;
if (node->children.size() == 0) {
return;
}
node->collapse = !node->collapse;
if (!node->collapse) {
beginInsertRows(QModelIndex(), index.row() + 1, index.row() + node->children.size());
endInsertRows();
} else {
beginRemoveRows(QModelIndex(), index.row() + 1, index.row() + node->children.size());
endRemoveRows();
}
// 刷新放在删除行之后,放在删除行之前可能导致rowCount返回数据错误
refreshList();
}
NavListView::NavListView(QWidget *parent) : QListView(parent)
{
infoVisible = true;
lineVisible = true;
icoColorBg = false;
style = NavListView::IcoStyle_Cross;
colorLine = QColor(214, 216, 224);
colorBgNormal = QColor(239, 241, 250);
colorBgSelected = QColor(133, 153, 216);
colorBgHover = QColor(209, 216, 240);
colorTextNormal = QColor(58, 58, 58);
colorTextSelected = QColor(255, 255, 255);
colorTextHover = QColor(59, 59, 59);
this->setMouseTracking(true);
model = new NavModel(this);
delegate = new NavDelegate(this);
connect(this, SIGNAL(clicked(QModelIndex)), model, SLOT(collapse(QModelIndex)));
}
NavListView::~NavListView()
{
delete model;
delete delegate;
}
void NavListView::readData(QString xmlPath)
{
model->readData(xmlPath);
this->setModel(model);
this->setItemDelegate(delegate);
}
void NavListView::setData(QStringList listItem)
{
model->setData(listItem);
this->setModel(model);
this->setItemDelegate(delegate);
}
void NavListView::setCurrentRow(int row)
{
QModelIndex index = model->index(row, 0);
setCurrentIndex(index);
}
void NavListView::setInfoVisible(bool infoVisible)
{
this->infoVisible = infoVisible;
}
void NavListView::setLineVisible(bool lineVisible)
{
this->lineVisible = lineVisible;
}
void NavListView::setIcoColorBg(bool icoColorBg)
{
this->icoColorBg = icoColorBg;
}
void NavListView::setIcoStyle(NavListView::IcoStyle style)
{
this->style = style;
}
void NavListView::setColorLine(QColor colorLine)
{
this->colorLine = colorLine;
}
void NavListView::setColorBg(QColor colorBgNormal, ///< 正常背景颜色
QColor colorBgSelected,///< 选中背景颜色
QColor colorBgHover) ///< 鼠标悬停背景颜色
{
this->colorBgNormal = colorBgNormal;
this->colorBgSelected = colorBgSelected;
this->colorBgHover = colorBgHover;
}
void NavListView::setColorText(QColor colorTextNormal, ///< 正常字体颜色
QColor colorTextSelected,///< 选中字体颜色
QColor colorTextHover) ///< 鼠标悬停字体颜色
{
this->colorTextNormal = colorTextNormal;
this->colorTextSelected = colorTextSelected;
this->colorTextHover = colorTextHover;
}
void NavListView::setColorBgLeval2(QColor _colorBgNormalLeval2,
QColor _colorBgSelectedLeval2,
QColor _colorBgHoverLeval2)
{
this->colorBgNormalLeval2 = _colorBgNormalLeval2;
this->colorBgSelectedLeval2 = _colorBgSelectedLeval2;
this->colorBgHoverLeval2 = _colorBgHoverLeval2;
}
void NavListView::setColorTextLeval2(QColor _colorTextNormalLeval2,
QColor _colorTextSelectedLeval2,
QColor _colorTextHoverLeval2)
{
this->colorTextNormalLeval2 = _colorTextNormalLeval2;
this->colorTextSelectedLeval2 = _colorTextSelectedLeval2;
this->colorTextHoverLeval2 = _colorTextHoverLeval2;
}
NavigationList.h
#ifndef NAVIGATIONLIST_H
#define NAVIGATIONLIST_H
#include <QWidget>
#include <QPainter>
#include <QStyleOption>
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace Ui { class NavigationList; }
QT_END_NAMESPACE
class NavigationList : public QWidget
{
Q_OBJECT
public:
explicit NavigationList(QWidget *parent = nullptr);
~NavigationList();
void initTreeView();
protected:
void paintEvent(QPaintEvent* _event) override;
public slots:
void slotListViewPressed(const QModelIndex &);
signals:
void signalPageSwitch(QString page); // 页面切换信号
private:
Ui::NavigationList *ui;
bool m_isHideAdditional = true;
};
#endif // NAVIGATIONLIST_H
NavigationList.cpp
#include "NavigationList.h"
#include "ui_NavigationList.h"
NavigationList::NavigationList(QWidget *parent) :
QWidget(parent),
ui(new Ui::NavigationList)
{
ui->setupUi(this);
initTreeView();
connect(ui->listViewNavigation, &NavListView::pressed, this, &NavigationList::slotListViewPressed);
ui->listViewNavigation->setCurrentRow(0);
}
NavigationList::~NavigationList()
{
delete ui;
}
void NavigationList::paintEvent(QPaintEvent* _event)
{
Q_UNUSED(_event)
QStyleOption n_styleOption;
n_styleOption.init(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &n_styleOption, &painter, this);
}
void NavigationList::initTreeView()
{
ui->listViewNavigation->setIcoColorBg(false);
ui->listViewNavigation->setColorLine(QColor("#FFFFFF"));
ui->listViewNavigation->setColorBg(QColor("#016BFF"),
QColor("#2A83FF"),
QColor("#2A83FF"));
ui->listViewNavigation->setColorText(QColor("#FFFFFF"),
QColor("#FFFFFF"),
QColor(0, 0, 0));
ui->listViewNavigation->setColorBgLeval2(QColor("#EBF1FF"),QColor("#EBF1FF"),QColor("#EBF1FF"));
ui->listViewNavigation->setColorTextLeval2(QColor("#000000"),
QColor("#000000"),
QColor("#6D6D6D"));
// 设置数据方式
QStringList listItem;
listItem.append(QString::fromLocal8Bit("Tab1||0||:/Images/1.png|"));
listItem.append(QString::fromLocal8Bit("Tab2||0||:/Images/2.png|"));
listItem.append(QString::fromLocal8Bit("Tab3||1||:/Images/3.png|"));
listItem.append(QString::fromLocal8Bit("Tab4|Tab3|||:/Images/4.png|"));
listItem.append(QString::fromLocal8Bit("Tab5|Tab3|||:/Images/5.png|"));
listItem.append(QString::fromLocal8Bit("Tab6|Tab3|||:/Images/6.png|"));
listItem.append(QString::fromLocal8Bit("Tab7|Tab3|||:/Images/7.png|"));
listItem.append(QString::fromLocal8Bit("Tab8||0||:/Images/8.png|"));
listItem.append(QString::fromLocal8Bit("Tab9||0||:/Images/9.png|"));
ui->listViewNavigation->setData(listItem);
}
void NavigationList::slotListViewPressed(const QModelIndex &)
{
// 获取到点击的某一行,再根据点击显示对应的界面
QModelIndex index = ui->listViewNavigation->currentIndex();
QString text = index.data().toString();
emit signalPageSwitch(text);
}
NavigationList.ui
只有一个QListView控件,被提升成了上面的NavListView类
其中listViewNavigation控件添加了如下的样式表:
NavDelegate
{
background-color:"#016BFF";
}
QListView#listViewNavigation
{
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
background-color:"#016BFF";
}