Qt之实现自定义树状图控件

本文分享了一个使用Qt制作的自定义树控件,通过ListWidget和自定义Widget组合成节点,形成类似树状图的效果。该控件适用于创建如QQ好友列表的应用,虽然原始的QQ列表代码丢失,但通过调整此控件仍能实现类似功能。完整代码可在指定QQ群获取。
摘要由CSDN通过智能技术生成

一、简述

这是好久之前做的一个树控件,挺好玩的就拿出来分享一下.
代码的主要思路是是通过一个ListWidget和一个自定义的Widget进行组合成为一个节点,然后多个这样的控件进行组合,类似树控件的一个展示效果。

在这里插入图片描述 在这里插入图片描述

通过这个控件可以做成QQ好友列表的效果,之前有做过一版QQ列表,已经做完了,可惜代码找不到了…
不过只需要修改这两个控件就可以做成QQ好友列表的样子了,如果代码找到了,到时候可以在群里分享一下。
在这里插入图片描述

二、代码之路

CustomTreeWidget.h

#include <QtWidgets/QWidget>
#include <QLabel>
#include <QHBoxLayout>
#include <QPainter>
#include <QListWidget>
#include <QScrollbar>
#include <QMouseEvent>
#include <QScrollArea>
#include <QTimer>

#define ITEM_HEIGHT 40	// item高度;

// 传感器数据结构;
struct SensorDataStruct
{
	QColor alarmColor;
	QString sensorName;
	float sensorNumber;
};

// 节点数据结构;
struct NodeDataStruct
{
	QString strNodeName;
	int strNodeCount;
	QColor numberColor;
	QList<SensorDataStruct> sensorDataList;
};



class SensorItemWidget : public QWidget
{
public:
	SensorItemWidget(QWidget* parent = NULL)
		: QWidget(parent)
	{
		initWidget();
		this->setFixedSize(QSize(250, ITEM_HEIGHT));
		this->setStyleSheet(".QWidget:hover{background:rgba(200, 200, 200, 150);}");
	}

    // 设置传感器信息;
	void setSensorInfo(QColor alarmColor, QString sensorName, float sensorNumber)
	{
		m_colorWidget->setStyleSheet(QString("QWidget{border-radius:6px;background:rgb(%1, %2, %3);}").arg(alarmColor.red()).arg(alarmColor.green()).arg(alarmColor.blue()));

		m_sensorLabel->setText(sensorName);
		m_sensorLabel->setScaledContents(true);
		m_numberLabel->setText(QString::number(sensorNumber));
		m_numberLabel->setScaledContents(true);
	}

	
private:
	void initWidget()
	{
		m_colorWidget = new QWidget;
		m_colorWidget->setFixedSize(QSize(12, 12));

		m_sensorLabel = new QLabel;

		m_numberLabel = new QLabel;

		QHBoxLayout* hLayout = new QHBoxLayout(this);
		hLayout->addWidget(m_colorWidget);
		hLayout->addWidget(m_sensorLabel);
		hLayout->addWidget(m_numberLabel);
		hLayout->addStretch();
		hLayout->setSpacing(15);
		hLayout->setContentsMargins(20, 0, 0, 0);
	}

private:
	QWidget* m_colorWidget;
	QLabel* m_sensorLabel;
	QLabel* m_numberLabel;
};


class SencorListWidget : public QWidget
{
	Q_OBJECT

public:
	SencorListWidget(QWidget* parent = NULL)
		: QWidget(parent)
		, m_isFolded(true)
	{
		initWidget();
		this->setWindowFlags(Qt::FramelessWindowHint);

        this->setStyleSheet("QListWidget{border:none;border-bottom:1px solid gray;}");
	}

	// 设置标题栏信息;
	void setTitleInfo(QString strTitle, int count, QColor numberColor)
	{
		m_titleLabel->setText(strTitle);
		m_numberLabel->setText(QString::number(count));
		m_numberLabel->setStyleSheet(QString("color:white;border-radius:8px;background:rgb(%1, %2, %3);").arg(numberColor.red()).arg(numberColor.green()).arg(numberColor.blue()));
	}

	// 添加传感器子项;
	void addSensorItem(QColor alarmColor, QString sensorName, float sensorNumber)
	{
		QListWidgetItem* item = new QListWidgetItem;
		item->setSizeHint(QSize(250, ITEM_HEIGHT));
		m_listWidget->addItem(item);
		SensorItemWidget* itemWidget = new SensorItemWidget;
		itemWidget->setSensorInfo(alarmColor, sensorName, sensorNumber);
		m_listWidget->setItemWidget(item, itemWidget);
	}

	// 更新传感器某一项状态;
	void updateSensorItem(int rowCount, QColor alarmColor, QString sensorName, float sensorNumber)
	{
		QListWidgetItem* item = m_listWidget->item(rowCount);
		if (item != NULL)
		{
			SensorItemWidget* itemWidget = static_cast<SensorItemWidget*>(m_listWidget->itemWidget(item));
			itemWidget->setSensorInfo(alarmColor, sensorName, sensorNumber);
		}
	}

private:
	void initTitleBackWidget()
	{
		m_titleBackWidget = new QWidget;
		m_titleBackWidget->setFixedSize(QSize(250, 40));
		m_titleBackWidget->installEventFilter(this);
		m_titleBackWidget->setStyleSheet(".QWidget{border-bottom:1px solid gray;}\
                                            .QWidget:hover{background:rgba(200, 200, 200, 200);}");

		m_foldStateLabel = new QLabel;
		m_foldStateLabel->setFixedSize(QSize(20, 20));
		m_foldStateLabel->setPixmap(QIcon(":/Resources/Folded.png").pixmap(m_foldStateLabel->size()));

		m_titleLabel = new QLabel;
		m_titleLabel->setStyleSheet("font-size:16px;font-weight:bold;");

		m_numberLabel = new QLabel;
		m_numberLabel->setFixedSize(QSize(16, 16));
		m_numberLabel->setAlignment(Qt::AlignCenter);

		QHBoxLayout* hButtonLayout = new QHBoxLayout;
		hButtonLayout->addWidget(m_numberLabel);
		hButtonLayout->setContentsMargins(0, 0, 0, 10);

        m_foldTagLabel = new QLabel;
        m_foldTagLabel->setFixedSize(QSize(20, 20));
        m_foldTagLabel->setPixmap(QIcon(":/Resources/EnableFold.png").pixmap(m_foldTagLabel->size()));

		QHBoxLayout* hTitleLayout = new QHBoxLayout(m_titleBackWidget);
		hTitleLayout->addWidget(m_foldStateLabel);
		hTitleLayout->addWidget(m_titleLabel);
		hTitleLayout->addLayout(hButtonLayout);
        hTitleLayout->addStretch();
        hTitleLayout->addWidget(m_foldTagLabel);
		hTitleLayout->setSpacing(5);
		hTitleLayout->setContentsMargins(5, 0, 25, 0);
	}

	void initWidget()
	{
		initTitleBackWidget();

		m_listWidget = new QListWidget;
		m_listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		m_listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
		m_listWidget->setVisible(false);

		QVBoxLayout* vMainLayout = new QVBoxLayout(this);
		vMainLayout->addWidget(m_titleBackWidget);
		vMainLayout->addWidget(m_listWidget);
		vMainLayout->addStretch();
		vMainLayout->setSpacing(0);
		vMainLayout->setMargin(0);
	}

	bool eventFilter(QObject *watched, QEvent *event)
	{
		if (watched == m_titleBackWidget)
		{
			if (event->type() == QEvent::MouseButtonRelease)
			{
				QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
				if (mouseEvent->button() == Qt::LeftButton)
				{
                    // 节点点击进行展开收缩;
					if (m_isFolded)
					{
						m_foldStateLabel->setPixmap(QIcon(":/Resources/UnFolded.png").pixmap(m_foldStateLabel->size()));
						m_listWidget->setVisible(true);
						m_listWidget->setFixedHeight(m_listWidget->count() * ITEM_HEIGHT + 10);
						this->setFixedHeight(m_titleBackWidget->height() + m_listWidget->height());
						emit signalNodeFoldChanged(m_titleBackWidget->height() + m_listWidget->height());
					}
					else
					{
						m_foldStateLabel->setPixmap(QIcon(":/Resources/Folded.png").pixmap(m_foldStateLabel->size()));
						m_listWidget->setVisible(false);
						this->setFixedHeight(m_titleBackWidget->height());
						emit signalNodeFoldChanged(m_titleBackWidget->height());
					}
					m_isFolded = !m_isFolded;
				}
			}
		}

		return __super::eventFilter(watched, event);
	}

signals:
	void signalNodeFoldChanged(int itemHeight);

private:
	QLabel* m_foldStateLabel;
	QLabel* m_titleLabel;
	QLabel* m_numberLabel;
    QLabel* m_foldTagLabel;

	QWidget* m_titleBackWidget;
	QListWidget* m_listWidget;

	bool m_isFolded;
};


class CustomTreeWidget : public QWidget
{
	Q_OBJECT

public:
	CustomTreeWidget(QWidget *parent = Q_NULLPTR);

private:
	void initWidget();

private slots:
	void onNodeFoldChanged();

	void onUpdateData();

private:

	QScrollArea* m_scrollArea;

	QList<NodeDataStruct> m_nodeDataList;

	QList<SencorListWidget*> m_nodeWidgetList;

	QWidget* m_sensorBackWidget;


	// 刷新数据时钟;
	QTimer m_refreshTimer;
	int m_refreshIndex; 
};


CustomTreeWidget.cpp

#include "CustomTreeWidget.h"

CustomTreeWidget::CustomTreeWidget(QWidget *parent)
	: QWidget(parent)
	, m_refreshIndex(0)
{
	initWidget();

	this->setFixedSize(QSize(250, 600));

	m_refreshTimer.setInterval(500);
	connect(&m_refreshTimer, &QTimer::timeout, this, &CustomTreeWidget::onUpdateData);
	m_refreshTimer.start();

    this->setStyleSheet("*{font-family:Microsoft YaHei;}\
           QScrollBar::vertical {\
              background:rgb(226,222,221);\
              border:none;\
              width: 5px;\
              margin:0px;\
           }\
           QScrollBar::handle:vertical {\
              background: rgb(192,192,192);\
              border-radius:1px;\
              min-height: 20px;\
              width:5px;\
          }\
          QScrollBar::add-line:vertical {\
              height:0px;\
          }\
          QScrollBar::sub-line:vertical {\
              height:0px;\
          }\
          QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {\
             background:transparent;\
          }");
}

void CustomTreeWidget::initWidget()
{
	m_scrollArea = new QScrollArea;
	m_scrollArea->setFixedWidth(250);
	m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	m_scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
	m_scrollArea->setStyleSheet(".QScrollArea{background:white;}");

	for (int i = 0; i < 4; i++)
	{
		NodeDataStruct nodeData;
		nodeData.numberColor = Qt::red;
		nodeData.strNodeCount = 6;
		nodeData.strNodeName = QString("Node-%1").arg(i);
		for (int j = 0; j < 6; j ++)
		{
			SensorDataStruct sensorData;
			sensorData.alarmColor = Qt::blue;
			sensorData.sensorName = QString("Sensor-%1").arg(j);
			sensorData.sensorNumber = j;
			nodeData.sensorDataList.append(sensorData);
		}
		m_nodeDataList.append(nodeData);
	}

	m_sensorBackWidget = new QWidget;
	m_sensorBackWidget->setFixedWidth(250);
	m_sensorBackWidget->setStyleSheet(".QWidget{background:white;}");
	QVBoxLayout* vBackWidget = new QVBoxLayout(m_sensorBackWidget);
	vBackWidget->setSpacing(0);
	vBackWidget->setMargin(0);
	// 根据已经造好的数据布局界面;
	for (int i = 0; i < m_nodeDataList.size(); i++)
	{
		NodeDataStruct nodeData = m_nodeDataList[i];
		SencorListWidget* sencorListWidget = new SencorListWidget;
		connect(sencorListWidget, &SencorListWidget::signalNodeFoldChanged, this, &CustomTreeWidget::onNodeFoldChanged);
		// 先设置节点标题信息;
		sencorListWidget->setTitleInfo(nodeData.strNodeName, nodeData.strNodeCount, nodeData.numberColor);
		// 为每个节点添加数据;
		for (int j = 0; j < nodeData.sensorDataList.count(); j++)
		{
			SensorDataStruct sensorData = nodeData.sensorDataList[j];
			sencorListWidget->addSensorItem(sensorData.alarmColor, sensorData.sensorName, sensorData.sensorNumber);
		}

		vBackWidget->addWidget(sencorListWidget);
		m_nodeWidgetList.append(sencorListWidget);
	}
	
	m_scrollArea->setWidget(m_sensorBackWidget);

	QHBoxLayout* hMainLayout = new QHBoxLayout(this);
	hMainLayout->addWidget(m_scrollArea);
	hMainLayout->setMargin(0);
}

void CustomTreeWidget::onNodeFoldChanged()
{
	int height = 0;
	for (int i = 0; i < m_nodeWidgetList.count(); i++)
	{
		height += m_nodeWidgetList[i]->height();
	}
	m_sensorBackWidget->setFixedHeight(height);
}

// 刷新数据;
void CustomTreeWidget::onUpdateData()
{
	if (m_refreshIndex >= m_nodeDataList.count())
	{
		m_refreshIndex = 0;
	}
	
	// 这里500ms更新一个节点的数据;
	SencorListWidget* sencorListWidget = m_nodeWidgetList[m_refreshIndex];
	NodeDataStruct nodeData = m_nodeDataList[m_refreshIndex];
	nodeData.numberColor = QColor(rand() % 256, rand() % 256, rand() % 256);
	sencorListWidget->setTitleInfo(nodeData.strNodeName, nodeData.strNodeCount, nodeData.numberColor);

	for (int j = 0; j < 6; j++)
	{
		SensorDataStruct sensorData = nodeData.sensorDataList[j];
		sensorData.alarmColor = nodeData.numberColor = QColor(rand() % 256, rand() % 256, rand() % 256);
		sencorListWidget->updateSensorItem(j, sensorData.alarmColor, sensorData.sensorName, sensorData.sensorNumber);
	}	

	m_refreshIndex++;
}


详细工程代码可以加群进行下载哦,群号:311750285,更多好玩,有趣的在等着你哦!!!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值