大部分的软件都有多个页面,这时候就需要一个导航栏控件,通过在导航栏中选择某一栏,同时显示对应的页面。
本文代码效果如下:
本文的导航栏控件基于大佬 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_NAMESPACEclass 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";
}