大部分的软件都有多个页面,这时候就需要一个导航栏控件,通过在导航栏中选择某一栏,同时显示对应的页面。
本文代码效果如下:

本文的导航栏控件基于大佬 feiyangqingyun 的导航栏控件博客Qt/C++编写自定义控件46-树状导航栏_qt之实现自定义树状图控件-CSDN博客做了美化,修复了一些会导致崩溃的bug。
本文代码:https://download.csdn.net/download/Sakuya__/89420773?spm=1001.2014.3001.5501
https://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
- #include
- #include
- #include
-
- 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
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; - std::vector
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
- #include
- #include
- #include
-
- 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
::iterator it = treeNode.begin(); it != treeNode.end();) { - for (std::list
::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
(listNode[index.row()].treeNode); - }
- return QVariant();
- }
-
- void NavModel::refreshList()
- {
- listNode.clear();
-
- for (std::vector
::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
::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
- #include
- #include
- #include
-
- 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";
- }