Qt QListView自定义树状导航控件

作者 : admin 本文共16218个字,预计阅读时间需要41分钟 发布时间: 2024-06-17 共1人阅读

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

        本文代码效果如下:

Qt QListView自定义树状导航控件插图

        本文的导航栏控件基于大佬 feiyangqingyun 的导航栏控件博客Qt/C++编写自定义控件46-树状导航栏_qt之实现自定义树状图控件-CSDN博客做了美化,修复了一些会导致崩溃的bug。

        本文代码:https://download.csdn.net/download/Sakuya__/89420773?spm=1001.2014.3001.5501Qt QListView自定义树状导航控件插图(1)https://download.csdn.net/download/Sakuya__/89420773?spm=1001.2014.3001.5501        也可以在这里下载大佬的代码学习:NavListView: Qt 自定义的树形导航控件Qt QListView自定义树状导航控件插图(1)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() 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() 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)   ///colorBgNormal   = colorBgNormal;
this->colorBgSelected = colorBgSelected;
this->colorBgHover    = colorBgHover;
}
void NavListView::setColorText(QColor colorTextNormal,  ///< 正常字体颜色
QColor colorTextSelected,///< 选中字体颜色
QColor colorTextHover)   ///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类

Qt QListView自定义树状导航控件插图(2)

        其中listViewNavigation控件添加了如下的样式表:

NavDelegate
{
background-color:"#016BFF";
}
QListView#listViewNavigation
{
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
background-color:"#016BFF";
}

 

本站无任何商业行为
个人在线分享 » Qt QListView自定义树状导航控件
E-->