QTreewidget获取鼠标点击位置的item函数itemAt(QPoint pt)

在这里插入图片描述

需求:QTreeWidget响应拖放事件,把节点拖放到其它画布上。
实现:从QTreeWidget派生出子类,实现拖放相关的的虚函数
class QTreeWidgetDrag : public QTreeWidget
{
public:
QTreeWidgetDrag();
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
};

在鼠标按下的函数中,后去点击的item:
void QTreeWidgetDrag::mousePressEvent(QMouseEvent *event)
{
*QTreeWidgetItem pCurrentItem = this->itemAt(event->pos());
//获取节点名称
QString strText = pCurrentItem->text(0);
QByteArray byte = strText.toUtf8();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
//把名称赋值到数据流中,传给要释放到的控件,需要的其它数据都可以往里面塞
dataStream << byte;
//
QMimeDat

#include"mainwindow.h" #include "ui_mainwindow.h" #include <QFileDialog> #include <QDir> #include <QMessageBox> #include <QGraphicsScene> #include <QGraphicsPixmapItem> #include <QSettings> #include <QTextStream> #include<QDebug> #include <QWheelEvent> #include <QMouseEvent> #include <QScrollBar> #include <QGraphicsLineItem> //#include <opencv2/opencv.hpp> #include <QString> #include <QDebug> #include <QDesktopServices> #include <QUrl> #include <QFileSystemModel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); // 初始化参数 toothParams.Init();//矫正参数 laserParams.Init();//激光参数 currentPixmapItem = nullptr; currentLineItem = nullptr; currentScale = 1.0; isDragging = false; // 设置树形视图 //ui->treeWidget->setHeaderLabel("文件夹结构"); //qDebug() << "Original:" << "文件" << "UTF-8:" ; // 设置图形视图的属性 //ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag); ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); ui->graphicsView->setResizeAnchor(QGraphicsView::AnchorUnderMouse);//设置视图组件本身大小改变时的锚点 ui->graphicsView->setRenderHint(QPainter::Antialiasing); ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); // 连接信号槽 connect(ui->treeWidget,&QTreeWidget::itemClicked,this,&MainWindow::on_treeWidget_itemClicked); } MainWindow::~MainWindow() { delete ui; } // void listCameras() { // for (int i = 0;; ++i) { // cv::VideoCapture cap(i); // if (!cap.isOpened()) { // break; // } // qDebug() << "Found camera:" << i; // cap.release(); // } // } void MainWindow::on_browseButton_clicked() { QString folderPath = QFileDialog::getExistingDirectory(this, "选择标定文件夹"); if (!folderPath.isEmpty()) { currentFolderPath = folderPath; ui->folderPathEdit->setText(folderPath); updateTreeView(folderPath); } } void MainWindow::on_calibrateButton_clicked() { /* ---------- 1. 基础检查 ---------- */ if (currentFolderPath.isEmpty()) { QMessageBox::warning(this, QString::fromUtf8("警告"), QString::fromUtf8("请先选择标定文件夹")); return; } if (ui->cameraNameEdit->text().trimmed().isEmpty()) { QMessageBox::warning(this, QString::fromUtf8("警告"), QString::fromUtf8("请输入相机名称")); return; } updateParametersFromUI(); /* ---------- 2. 参数合法性检查 ---------- */ if (laserParams.CameraWidth <= 0 || laserParams.CameraHeight <= 0 || laserParams.CameraFocal <= 0) { QMessageBox::warning(this, QString::fromUtf8("警告"), QString::fromUtf8("相机宽度、高度或焦距不能为 0")); return; } calibrationPoints.clear(); int validDirCount = 0; /* ---------- 3. 主循环 ---------- */ for (int disIndex = 60; disIndex < 400; disIndex += 10) { QString currDir = QDir(currentFolderPath).filePath(QString("%1_0").arg(disIndex)); QDir dir(currDir); if (!dir.exists()) { qDebug() << "跳过不存在的目录:" << currDir; continue; } QFileInfoList imgs = dir.entryInfoList(QStringList() << "*.bmp" << "*.jpg" << "*.png", QDir::Files); if (imgs.isEmpty()) { qDebug() << "目录无图像:" << currDir; continue; } ++validDirCount; QByteArray dirBA = currDir.toLocal8Bit(); char dirChar[512] = {0}; strncpy_s(dirChar, sizeof(dirChar) - 1, dirBA.constData(), _TRUNCATE); qDebug() << "DLL 输入路径:" << dirChar << "图像数量:" << imgs.size(); std::vector<ToothCalibPoint> vLineCaliPoint; bool ok = GetToothPositionModeOne(dirChar, toothParams, vLineCaliPoint); qDebug() << "GetToothPositionModeOne 返回:" << ok << "点数:" << vLineCaliPoint.size(); if (!ok || vLineCaliPoint.empty()) continue; double left = 0, right = 0; for (const auto &p : vLineCaliPoint) { if (p.PointMode == 1) left = p.CameraW; if (p.PointMode == 2) right = p.CameraW; } if (left > 0 && right > 0) toothParams.CenterPos = (left + right) / 2.0; QVector<ToothCalibPoint> qv; qv.reserve(int(vLineCaliPoint.size())); for (const auto &p : vLineCaliPoint) qv.append(p); calibrationPoints.append(qv); } /* ---------- 4. 检查有效目录 ---------- */ if (validDirCount == 0 || calibrationPoints.isEmpty()) { QMessageBox::warning(this, QString::fromUtf8("警告"), QString::fromUtf8("未找到任何有效标定目录或标定点,请检查数据")); return; } /* ---------- 5. 保存 tooth.txt ---------- */ QString saveTxt = QDir(currentFolderPath).filePath("tooth.txt"); QFile file(saveTxt); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(this, QString::fromUtf8("错误"), QString::fromUtf8("无法创建标定文件")); return; } QTextStream out(&file); #if QT_VERSION < QT_VERSION_CHECK(6,0,0) out.setCodec("UTF-8"); #else out.setEncoding(QStringConverter::Utf8); #endif int total = 0; for (const auto &vec : calibrationPoints) for (const auto &p : vec) { out << QString::number(p.CameraW, 'f', 3) << ',' << QString::number(p.CameraH, 'f', 3) << ',' << QString::number(p.WorldX, 'f', 3) << ',' << QString::number(p.WorldZ, 'f', 3) << ',' << p.FileName << ";\n"; ++total; } file.close(); qDebug() << "已保存" << total << "点到" << saveTxt; /* ---------- 6. 激光线标定 ---------- */ LaserLineModelResult res; res.Init(); // 设置输出目录为当前选择的文件夹下的"生成的标注结果"子文件夹 QString outputDir = QDir(currentFolderPath).filePath("生成的标注结果"); QDir().mkpath(outputDir); // 确保输出目录存在 QString cameraName = ui->cameraNameEdit->text().trimmed(); if (cameraName.isEmpty()) { cameraName = "default_camera"; } // 设置输出文件路径 QString dllPath = QDir(outputDir).filePath(cameraName + "-deep3.dll"); QString pcImagePath = QDir(outputDir).filePath(cameraName + "-pc.jpg"); QString rcImagePath = QDir(outputDir).filePath(cameraName + "-rc.jpg"); QString xImagePath = QDir(outputDir).filePath(cameraName + "-X.png"); QString zImagePath = QDir(outputDir).filePath(cameraName + "-Z.png"); qDebug()<<dllPath; QByteArray txtBA = saveTxt.toUtf8(); QByteArray nameBA = cameraName.toUtf8(); QByteArray dllDirBA = outputDir.toUtf8(); strncpy_s(laserParams.CalibPointFileName, sizeof(laserParams.CalibPointFileName) - 1, txtBA.constData(), _TRUNCATE); strncpy_s(laserParams.CameraName, sizeof(laserParams.CameraName) - 1, nameBA.constData(), _TRUNCATE); strncpy_s(res.CalibResultDir, sizeof(res.CalibResultDir) - 1, dllDirBA.constData(), _TRUNCATE); laserParams.MeasurePrecision = ui->precisionSpinBox->value(); // 调用标定函数 bool calibrationSuccess = GetLaserLineCalibUseLcModeThree(laserParams, res); if (calibrationSuccess) { /* ---------- 7. 重命名和移动生成的文件 ---------- */ QString defaultDllPath = QDir(outputDir).filePath("deep3.dll"); QString defaultPcPath = QDir(outputDir).filePath("pc.jpg"); QString defaultRcPath = QDir(outputDir).filePath("rc.jpg"); QString defaultXPath = QDir(outputDir).filePath("X.png"); QString defaultZPath = QDir(outputDir).filePath("Z.png"); // 重命名文件 if (QFile::exists(defaultDllPath)) { QFile::rename(defaultDllPath, dllPath); } if (QFile::exists(defaultPcPath)) { QFile::rename(defaultPcPath, pcImagePath); } if (QFile::exists(defaultRcPath)) { QFile::rename(defaultRcPath, rcImagePath); } if (QFile::exists(defaultXPath)) { QFile::rename(defaultXPath, xImagePath); } if (QFile::exists(defaultZPath)) { QFile::rename(defaultZPath, zImagePath); } /* ---------- 8. 验证文件生成 ---------- */ bool allFilesGenerated = true; QStringList missingFiles; if (!QFile::exists(dllPath)) { allFilesGenerated = false; missingFiles << "DLL文件"; } if (!QFile::exists(pcImagePath)) { allFilesGenerated = false; missingFiles << "pc.jpg"; } if (!QFile::exists(rcImagePath)) { allFilesGenerated = false; missingFiles << "rc.jpg"; } if (!QFile::exists(xImagePath)) { allFilesGenerated = false; missingFiles << "X.png"; } if (!QFile::exists(zImagePath)) { allFilesGenerated = false; missingFiles << "Z.png"; } if (allFilesGenerated) { QMessageBox::information(this, QStringLiteral("完成"), QString::fromUtf8("标定完成!文件已生成在:%1").arg(outputDir)); // 可选:打开生成的文件夹 QDesktopServices::openUrl(QUrl::fromLocalFile(outputDir)); } else { QMessageBox::warning(this, QStringLiteral("部分完成"), QString::fromUtf8("标定完成,但缺少以下文件:%1\n请检查DLL输出路径。") .arg(missingFiles.join(", "))); } } else { QMessageBox::critical(this, QStringLiteral("错误"), QString::fromUtf8("标定失败!请检查输入参数和数据。")); } } void MainWindow::updateTreeView(const QString &folderPath) { ui->treeWidget->clear(); QDir dir(folderPath); if (!dir.exists()) return; // 添加文件夹和子文件夹 QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->treeWidget); rootItem->setText(0, dir.dirName()); rootItem->setData(0, Qt::UserRole, folderPath); // 遍历子文件夹 QFileInfoList folders = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); foreach (const QFileInfo &folderInfo, folders) { QTreeWidgetItem *folderItem = new QTreeWidgetItem(rootItem); folderItem->setText(0, folderInfo.fileName()); folderItem->setData(0, Qt::UserRole, folderInfo.absoluteFilePath()); // 添加图片文件 QDir subDir(folderInfo.absoluteFilePath()); QFileInfoList images = subDir.entryInfoList(QStringList() << "*.bmp" << "*.jpg" << "*.png", QDir::Files); foreach (const QFileInfo &imageInfo, images) { QTreeWidgetItem *imageItem = new QTreeWidgetItem(folderItem); imageItem->setText(0, imageInfo.fileName()); imageItem->setData(0, Qt::UserRole, imageInfo.absoluteFilePath()); } } ui->treeWidget->expandAll(); // 将文件夹和图片全部展示出 } // void MainWindow::on_calibrateButton_clicked() // { // if (currentFolderPath.isEmpty()) // { // QMessageBox::warning(this, QString::fromUtf8("警告"), QString::fromUtf8("请先选择文件夹")); // return; // } // // 更新参数 // updateParametersFromUI(); // // 执行标定过程 // calibrationPoints.clear(); // for (int disIndex = 60; disIndex < 400; disIndex += 10) { // std::vector<ToothCalibPoint> vLineCaliPoint; // // 使用一致的路径分隔符 // //QString currSingleFileDir = currentFolderPath + "/" + QString::number(disIndex) + "_0"; // std::string currSingleFileDir = currentFolderPath.toStdString() + "\\" + std::to_string(disIndex) + "_0"; // //qDebug() << "Current Single File Directory:" << currSingleFileDir; // // 检查目录是否存在 // /*QDir dir(currSingleFileDir); // if (!dir.exists()) { // qDebug() << "Directory does not exist:" << currSingleFileDir; // continue; // }*/ // // 检查目录中是否有图像文件 // /*QStringList images = dir.entryList(QStringList() << "*.bmp" << "*.jpg" << "*.png", QDir::Files); // if (images.isEmpty()) { // qDebug() << "No images found in directory:" << currSingleFileDir; // continue; // }*/ // //qDebug() << "Found" << images.size() << "images in directory"; // char currSingleFileDir_char[512]; // 假设 512 足够 // strcpy_s(currSingleFileDir_char, sizeof(currSingleFileDir_char), currSingleFileDir.c_str()); // // 转换为本地编码(确保与外部库兼容) // //QByteArray ba = currSingleFileDir.toLocal8Bit(); // //char currSingleFileDir_char[512]; // //strncpy_s(currSingleFileDir_char, sizeof(currSingleFileDir_char), ba.constData(), _TRUNCATE); // qDebug() << "Calling GetToothPositionModeOne with path:" << currSingleFileDir_char; // // 调用标定函数 // bool success = GetToothPositionModeOne(currSingleFileDir_char, toothParams, vLineCaliPoint); // qDebug() << "GetToothPositionModeOne result:" << success; // qDebug() << "vLineCaliPoint size:" << vLineCaliPoint.size(); // if (!vLineCaliPoint.empty()) { // double leftPos = 0; // double rightPos = 0; // for (auto& p : vLineCaliPoint) { // qDebug() << "Point mode:" << p.PointMode << "CameraW:" << p.CameraW; // if (p.PointMode == 1) leftPos = p.CameraW; // if (p.PointMode == 2) rightPos = p.CameraW; // } // if (leftPos > 0 && rightPos > 0) { // toothParams.CenterPos = (leftPos + rightPos) / 2.0; // qDebug() << "Calculated center position:" << toothParams.CenterPos; // } // QVector<ToothCalibPoint> qVector; // for (const auto& point : vLineCaliPoint) { // qVector.append(point); // } // calibrationPoints.append(qVector); // } else { // qDebug() << "No calibration points found for distance:" << disIndex; // } // } // // 保存标定文件 // QString saveTxtName = currentFolderPath + "/tooth.txt"; // QFile file(saveTxtName); // if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { // QTextStream out(&file); // // 设置编码为 UTF-8 // #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // out.setCodec("UTF-8"); // #else // out.setEncoding(QStringConverter::Utf8); // #endif // int pointCount = 0; // for (const auto& points : calibrationPoints) { // for (const auto& pt : points) { // out << QString::number(pt.CameraW, 'f', 3) << "," // << QString::number(pt.CameraH, 'f', 3) << "," // << QString::number(pt.WorldX, 'f', 3) << "," // << QString::number(pt.WorldZ, 'f', 3) << "," // << pt.FileName << ";\n"; // pointCount++; // } // } // file.close(); // qDebug() << "Saved" << pointCount << "calibration points to" << saveTxtName; // if (pointCount == 0) { // QMessageBox::warning(this, QString::fromUtf8("警告"), QString::fromUtf8("未找到任何标定点,请检查输入数据和参数设置")); // return; // } // } else { // qDebug() << "Failed to open file for writing:" << saveTxtName; // QMessageBox::warning(this, QString::fromUtf8("错误"), QString::fromUtf8("无法创建标定文件")); // return; // } // // 执行激光线标定 // LaserLineModelResult currResultOut; // currResultOut.Init(); // QByteArray ba1 = saveTxtName.toUtf8(); // strncpy_s(laserParams.CalibPointFileName, ba1.constData(), sizeof(laserParams.CalibPointFileName)); // QByteArray ba2 = ui->cameraNameEdit->text().toUtf8(); // strncpy_s(laserParams.CameraName, ba2.constData(), sizeof(laserParams.CameraName)); // QString cameraSaveDir = QApplication::applicationDirPath() + "/Calibrate/" + // ui->cameraNameEdit->text() + "_dll"; // QDir().mkpath(cameraSaveDir); // QByteArray ba3 = cameraSaveDir.toUtf8(); // strncpy_s(currResultOut.CalibResultDir, ba3.constData(), sizeof(currResultOut.CalibResultDir)); // currResultOut.MeasurePrecision = ui->precisionSpinBox->value(); // GetLaserLineCalibUseLcModeThree(laserParams, currResultOut); // QMessageBox::information(this, QStringLiteral("完成"), QString::fromLocal8Bit("标定完成!")); // } // void MainWindow::on_calibrateButton_clicked() // { // /* ---------- 1. 基础检查 ---------- */ // if (currentFolderPath.isEmpty()) // { // QMessageBox::warning(this, QString::fromUtf8("警告"), // QString::fromUtf8("请先选择标定文件夹")); // return; // } // if (ui->cameraNameEdit->text().trimmed().isEmpty()) // { // QMessageBox::warning(this, QString::fromUtf8("警告"), // QString::fromUtf8("请输入相机名称")); // return; // } // updateParametersFromUI(); // 把 UI 值读到 toothParams / laserParams // /* ---------- 2. 参数合法性检查 ---------- */ // if (laserParams.CameraWidth <= 0 || laserParams.CameraHeight <= 0 || // laserParams.CameraFocal <= 0) // { // QMessageBox::warning(this, QString::fromUtf8("警告"), // QString::fromUtf8("相机宽度、高度或焦距不能为 0")); // return; // } // calibrationPoints.clear(); // 清空旧数据 // int validDirCount = 0; // 统计有效目录数 // /* ---------- 3. 主循环 ---------- */ // for (int disIndex = 60; disIndex < 400; disIndex += 10) // { // QString currDir = QDir(currentFolderPath).filePath(QString("%1_0").arg(disIndex)); // // QString currDir = currentFolderPath.toStdString() + "\\" + std::to_string(disIndex); // QDir dir(currDir); // /* 3.1 目录是否存在 */ // if (!dir.exists()) // { // qDebug() << "跳过不存在的目录:" << currDir; // continue; // } // /* 3.2 是否有图像 */ // QFileInfoList imgs = dir.entryInfoList(QStringList() << "*.bmp" << "*.jpg" << "*.png", // QDir::Files); // if (imgs.isEmpty()) // { // qDebug() << "目录无图像:" << currDir; // continue; // } // ++validDirCount; // /* 3.3 准备 char* 路径 */ // QByteArray dirBA = currDir.toLocal8Bit(); // 本地编码,兼容 DLL // char dirChar[512] = {0}; // strncpy_s(dirChar, sizeof(dirChar) - 1, // dirBA.constData(), _TRUNCATE); // qDebug() << "DLL 输入路径:" << dirChar // << "图像数量:" << imgs.size(); // /* 3.4 调用 DLL */ // std::vector<ToothCalibPoint> vLineCaliPoint; // bool ok = GetToothPositionModeOne(dirChar, toothParams, vLineCaliPoint); // qDebug() << "GetToothPositionModeOne 返回:" << ok // << "点数:" << vLineCaliPoint.size(); // if (!ok || vLineCaliPoint.empty()) // continue; // /* 3.5 计算中心 & 转 Qt 容器 */ // double left = 0, right = 0; // for (const auto &p : vLineCaliPoint) // { // if (p.PointMode == 1) left = p.CameraW; // if (p.PointMode == 2) right = p.CameraW; // } // if (left > 0 && right > 0) // toothParams.CenterPos = (left + right) / 2.0; // QVector<ToothCalibPoint> qv; // qv.reserve(int(vLineCaliPoint.size())); // for (const auto &p : vLineCaliPoint) qv.append(p); // calibrationPoints.append(qv); // } // /* ---------- 4. 一个有效目录都没跑 ---------- */ // if (validDirCount == 0 || calibrationPoints.isEmpty()) // { // QMessageBox::warning(this, QString::fromUtf8("警告"), // QString::fromUtf8("未找到任何有效标定目录或标定点,请检查数据")); // return; // } // /* ---------- 5. 保存 tooth.txt ---------- */ // //QString saveTxt = QApplication::applicationDirPath() + "/resources/tooth.txt"; // QString saveTxt = QDir(currentFolderPath).filePath("tooth.txt"); // QFile file(saveTxt); // if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) // { // QMessageBox::warning(this, QString::fromUtf8("错误"), // QString::fromUtf8("无法创建标定文件")); // return; // } // QTextStream out(&file); // #if QT_VERSION < QT_VERSION_CHECK(6,0,0) // out.setCodec("UTF-8"); // #else // out.setEncoding(QStringConverter::Utf8); // #endif // int total = 0; // for (const auto &vec : calibrationPoints) // for (const auto &p : vec) // { // out << QString::number(p.CameraW, 'f', 3) << ',' // << QString::number(p.CameraH, 'f', 3) << ',' // << QString::number(p.WorldX, 'f', 3) << ',' // << QString::number(p.WorldZ, 'f', 3) << ',' // << p.FileName << ";\n"; // ++total; // } // file.close(); // qDebug() << "已保存" << total << "点到" << saveTxt; // /* ---------- 6. 激光线标定 ---------- */ // LaserLineModelResult res; // res.Init(); // /* 6.1 填充激光参数 & 输出路径 */ // QByteArray txtBA = saveTxt.toUtf8(); // QByteArray nameBA = ui->cameraNameEdit->text().toUtf8(); // QByteArray dirBA = (QApplication::applicationDirPath() // + "/Calibrate/" // + ui->cameraNameEdit->text() + "_dll").toUtf8(); // strncpy_s(laserParams.CalibPointFileName, // sizeof(laserParams.CalibPointFileName) - 1, // txtBA.constData(), _TRUNCATE); // strncpy_s(laserParams.CameraName, // sizeof(laserParams.CameraName) - 1, // nameBA.constData(), _TRUNCATE); // strncpy_s(res.CalibResultDir, // sizeof(res.CalibResultDir) - 1, // dirBA.constData(), _TRUNCATE); // laserParams.MeasurePrecision = ui->precisionSpinBox->value(); // QDir().mkpath(QString::fromUtf8(dirBA)); // 确保输出目录存在 // GetLaserLineCalibUseLcModeThree(laserParams, res); // QMessageBox::information(this, QStringLiteral("完成"), // QString::fromUtf8("标定完成!")); // } //当树形控件中的某个项目被点击时触发,用于加载或显示被选中的内容 void MainWindow::on_treeWidget_itemClicked(QTreeWidgetItem *item, int column) { Q_UNUSED(column); QString filePath = item->data(0, Qt::UserRole).toString(); if (QFileInfo(filePath).isFile()) { // 查找对应的x坐标 QString fileName = QFileInfo(filePath).fileName(); QString distanceStr = QFileInfo(filePath).dir().dirName(); bool flag; double distance = distanceStr.split("_").first().toDouble(&flag); if (flag) { double xPosition = 0; // 在标定数据中查找对应的x坐标 for (const auto& points : calibrationPoints) { for (const auto& pt : points) { if (qAbs(pt.WorldZ - distance) < 0.1) { xPosition = pt.CameraW + pt.WorldX; break; } } } displayImage(filePath, xPosition); } } } void MainWindow::displayImage(const QString &imagePath, double xPosition) { // 清除现有场景 if (ui->graphicsView->scene()) { delete ui->graphicsView->scene(); } QGraphicsScene *scene = new QGraphicsScene(this); QPixmap pixmap(imagePath); if (!pixmap.isNull()) { // 添加图像到场景 currentPixmapItem = scene->addPixmap(pixmap); scene->setSceneRect(pixmap.rect()); // 添加竖线 if (xPosition > 0) { QPen redPen(Qt::red); redPen.setWidth(2); currentLineItem = scene->addLine(xPosition, 0, xPosition, pixmap.height(), redPen); } else { currentLineItem = nullptr; } ui->graphicsView->setScene(scene); // 初始适应视图 ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); currentScale = 1.0; // 启用缩放和平移 ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag); ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); ui->graphicsView->setResizeAnchor(QGraphicsView::AnchorUnderMouse); // 显示图像信息 QString info = QString("图像尺寸: %1 x %2\n文件路径: %3") .arg(pixmap.width()) .arg(pixmap.height()) .arg(imagePath); QGraphicsTextItem *textItem = scene->addText(info); textItem->setPos(10, 10); textItem->setDefaultTextColor(Qt::red); textItem->setZValue(1); // 确保文本显示在最上层 } } // 添加鼠标滚轮事件处理以实现缩放 void MainWindow::wheelEvent(QWheelEvent *event) { if (ui->graphicsView->underMouse()) { double scaleFactor = 1.15; if (event->angleDelta().y() > 0) { // 放大 ui->graphicsView->scale(scaleFactor, scaleFactor); currentScale *= scaleFactor; } else { // 缩小 ui->graphicsView->scale(1.0 / scaleFactor, 1.0 / scaleFactor); currentScale /= scaleFactor; } event->accept(); } else { QMainWindow::wheelEvent(event); } } // 添加鼠标按下事件处理以实现自定义拖拽 void MainWindow::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && ui->graphicsView->underMouse()) { lastMousePos = event->pos(); setCursor(Qt::ClosedHandCursor); event->accept(); } else { QMainWindow::mousePressEvent(event); } } // 添加鼠标移动事件处理以实现自定义拖拽 void MainWindow::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton && ui->graphicsView->underMouse()) { QPoint delta = event->pos() - lastMousePos; lastMousePos = event->pos(); // 移动视图 QScrollBar *hBar = ui->graphicsView->horizontalScrollBar(); QScrollBar *vBar = ui->graphicsView->verticalScrollBar(); hBar->setValue(hBar->value() - delta.x()); vBar->setValue(vBar->value() - delta.y()); event->accept(); } else { QMainWindow::mouseMoveEvent(event); } } // 添加鼠标释放事件处理 void MainWindow::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { setCursor(Qt::ArrowCursor); event->accept(); } else { QMainWindow::mouseReleaseEvent(event); } } // 添加重置视图的槽函数 void MainWindow::onResetView() { if (ui->graphicsView->scene()) { ui->graphicsView->fitInView(ui->graphicsView->scene()->sceneRect(), Qt::KeepAspectRatio); currentScale = 1.0; } } // 添加绘制红线的函数 void MainWindow::drawRedLine(double xPosition) { if (ui->graphicsView->scene() && currentPixmapItem) { // 移除现有的红线 if (currentLineItem) { ui->graphicsView->scene()->removeItem(currentLineItem); delete currentLineItem; } // 创建新红线 QPen redPen(Qt::red); redPen.setWidth(2); QPixmap pixmap = currentPixmapItem->pixmap(); currentLineItem = ui->graphicsView->scene()->addLine( xPosition, 0, xPosition, pixmap.height(), redPen); } } void MainWindow::updateParametersFromUI() { toothParams.CenterDis = ui->centerDisSpinBox->value(); toothParams.SideDis = ui->sideDisSpinBox->value(); toothParams.ToothNum = ui->toothNumSpinBox->value(); toothParams.DeepThresh = ui->deepThreshSpinBox->value(); toothParams.DeepStep = ui->deepStepSpinBox->value(); toothParams.CenterPos = ui->centerPosSpinBox->value(); laserParams.CameraWidth = ui->cameraWidthSpinBox->value(); laserParams.CameraHeight = ui->cameraHeightSpinBox->value(); laserParams.CameraOffsetYCalib = ui->offsetYCalibSpinBox->value(); laserParams.CameraOffsetYMap = ui->offsetYMapSpinBox->value(); laserParams.CameraMapHeight = ui->mapHeightSpinBox->value(); laserParams.RealXOffset = ui->realXOffsetSpinBox->value(); laserParams.RealZOffset = ui->realZOffsetSpinBox->value(); laserParams.CameraPiexSize = ui->pixelSizeSpinBox->value(); laserParams.CameraFocal = ui->focalSpinBox->value(); } void MainWindow::updateUIfromParameters() { ui->centerDisSpinBox->setValue(toothParams.CenterDis); ui->sideDisSpinBox->setValue(toothParams.SideDis); ui->toothNumSpinBox->setValue(toothParams.ToothNum); ui->deepThreshSpinBox->setValue(toothParams.DeepThresh); ui->deepStepSpinBox->setValue(toothParams.DeepStep); ui->centerPosSpinBox->setValue(toothParams.CenterPos); ui->cameraWidthSpinBox->setValue(laserParams.CameraWidth); ui->cameraHeightSpinBox->setValue(laserParams.CameraHeight); ui->offsetYCalibSpinBox->setValue(laserParams.CameraOffsetYCalib); ui->offsetYMapSpinBox->setValue(laserParams.CameraOffsetYMap); ui->mapHeightSpinBox->setValue(laserParams.CameraMapHeight); ui->realXOffsetSpinBox->setValue(laserParams.RealXOffset); ui->realZOffsetSpinBox->setValue(laserParams.RealZOffset); ui->pixelSizeSpinBox->setValue(laserParams.CameraPiexSize); ui->focalSpinBox->setValue(laserParams.CameraFocal); } void MainWindow::on_saveParamsButton_clicked() { QString fileName = QFileDialog::getSaveFileName(this, "保存参数", "", "配置文件 (*.ini)"); if (!fileName.isEmpty()) { QSettings settings(fileName, QSettings::IniFormat); settings.beginGroup("ToothParameters"); settings.setValue("CenterDis", toothParams.CenterDis); settings.setValue("SideDis", toothParams.SideDis); settings.setValue("ToothNum", toothParams.ToothNum); settings.setValue("DeepThresh", toothParams.DeepThresh); settings.setValue("DeepStep", toothParams.DeepStep); settings.setValue("CenterPos", toothParams.CenterPos); settings.endGroup(); settings.beginGroup("LaserParameters"); settings.setValue("CameraWidth", laserParams.CameraWidth); settings.setValue("CameraHeight", laserParams.CameraHeight); settings.setValue("CameraOffsetYCalib", laserParams.CameraOffsetYCalib); settings.setValue("CameraOffsetYMap", laserParams.CameraOffsetYMap); settings.setValue("CameraMapHeight", laserParams.CameraMapHeight); settings.setValue("RealXOffset", laserParams.RealXOffset); settings.setValue("RealZOffset", laserParams.RealZOffset); settings.setValue("CameraPiexSize", laserParams.CameraPiexSize); settings.setValue("CameraFocal", laserParams.CameraFocal); settings.setValue("CameraName", QString(laserParams.CameraName)); settings.endGroup(); } } void MainWindow::on_loadParamsButton_clicked() { QString fileName = QFileDialog::getOpenFileName(this, "加载参数", "", "配置文件 (*.txt)"); if (!fileName.isEmpty()) { QSettings settings(fileName, QSettings::IniFormat); settings.beginGroup("ToothParameters"); toothParams.CenterDis = settings.value("CenterDis", 0).toDouble(); toothParams.SideDis = settings.value("SideDis", 0).toDouble(); toothParams.ToothNum = settings.value("ToothNum", 50).toInt(); toothParams.DeepThresh = settings.value("DeepThresh", 30).toInt(); toothParams.DeepStep = settings.value("DeepStep", 16).toInt(); toothParams.CenterPos = settings.value("CenterPos", 0).toInt(); settings.endGroup(); settings.beginGroup("LaserParameters"); laserParams.CameraWidth = settings.value("CameraWidth", 0).toInt(); laserParams.CameraHeight = settings.value("CameraHeight", 0).toInt(); laserParams.CameraOffsetYCalib = settings.value("CameraOffsetYCalib", 0).toInt(); laserParams.CameraOffsetYMap = settings.value("CameraOffsetYMap", 0).toInt(); laserParams.CameraMapHeight = settings.value("CameraMapHeight", 0).toInt(); laserParams.RealXOffset = settings.value("RealXOffset", 0).toDouble(); laserParams.RealZOffset = settings.value("RealZOffset", 0).toDouble(); laserParams.CameraPiexSize = settings.value("CameraPiexSize", 0).toDouble(); laserParams.CameraFocal = settings.value("CameraFocal", 0).toDouble(); QString cameraName = settings.value("CameraName", "").toString(); strncpy_s(laserParams.CameraName, cameraName.toUtf8().data(), sizeof(laserParams.CameraName)); settings.endGroup(); updateUIfromParameters(); } } 结合我的代码来实现
09-12
<think> 我们正在讨论Qt中的QTreeWidget,特别是如何获取当前可视范围内的项目列表。 根据引用[2]中提到的内容,QTreeWidgetitemAt()函数,可以根据坐标位置返回节点(QTreeWidgetItem)。另外,我们还可以使用visibleRegion()获取可视区域,然后遍历所有项目,检查哪些项目位于这个区域内。 但是,QTreeWidget本身并没有直接提供返回可视范围内所有项目的函数。因此,我们需要自己实现: 步骤: 1. 获取QTreeWidget的可视区域(viewport)的矩形范围。 2. 获取QTreeWidget的顶部不可见项目(通过invisibleRootItem()可以获取根节点,然后遍历子节点?)但实际上,我们更关心的是显示出来的项目。 3. 我们可以使用QTreeWidget的viewport()的矩形,然后通过itemAt()来检查每个可能的位置?但这样效率不高。 另一种高效的方法是: 使用QTreeWidget的indexAt()和visualItemRect()函数。我们可以遍历所有项目(从顶部项目开始,递归遍历子项目),但这样做可能会遍历整个树,如果树很大,效率可能不高。 更高效的方法: 1. 获取可视区域的顶部和底部坐标。 2. 使用itemAt(0, 0)获取顶部项目?注意,坐标是相对于viewport的。 3. 然后使用itemBelow()函数依次获取下面的项目,直到项目的矩形底部超出可视区域的底部。 但注意:itemBelow()函数返回的是下一个显示在下面的项目(不分层级,即可能是同一层级的下一个,也可能是子项目,也可能是展开后的子项目等)。所以,我们可以通过itemAbove和itemBelow来遍历当前显示的所有项目。 具体步骤: 1. 首先,获取当前视口的矩形:viewport()->rect(),得到QRect。 2. 然后,获取视口顶部和底部的坐标:top = viewport->rect().top(); bottom = viewport->rect().bottom(); 3. 使用itemAt(0, top)获取最顶部的项目(注意,这里top可能是0,但是因为可能有垂直滚动,所以最顶部的项目不一定在坐标0处?实际上,坐标是相对于视口的,所以视口左上角为(0,0))。 实际上,我们取视口中间的点(水平方向中间,垂直方向顶部+1)以避免边界问题?但也可以直接用顶部坐标。 4. 然后,从最顶部的项目开始,使用itemBelow()函数依次获取下面的项目,直到项目的矩形(使用visualItemRect(item)获取)的底部坐标超过视口的底部坐标。 但是,注意:visualItemRect(item)返回的是在视口中的坐标(相对于视口)。因此,我们可以这样判断: 对于每个项目,我们获取其矩形,如果矩形的顶部已经大于视口的底部,则停止(因为项目是按顺序从上到下排列的)。 同时,我们也要注意,可能上面的项目只显示了一部分,所以即使顶部在视口内,我们就认为它在可视范围内。 更简单的方法:判断项目矩形是否与视口矩形相交(或包含在视口内)。 然而,QTreeWidget并没有直接提供遍历所有可视项目的方法。我们可以结合以下步骤: a. 获取第一个可视项目:从视口顶部开始(0,0)点,使用itemAt(0,0)获取。 b. 如果没有获取到,则视口没有项目。 c. 然后使用itemBelow()获取下一个项目,直到项目为空或者项目矩形的顶部大于视口底部。 注意:itemAt(0,0)可能获取不到,因为0,0位置可能没有项目?那么我们可以尝试从视口顶部中间位置(width()/2, 0)来获取。 但更通用的方法是使用QTreeWidget的indexAt(QPoint(0,0))获取模型索引,然后转换成QTreeWidgetItem。 实现函数: 我们可以这样写一个函数: ```cpp QList<QTreeWidgetItem*> getVisibleItems(QTreeWidget *treeWidget) { QList<QTreeWidgetItem*> items; // 获取视口矩形 QRect viewportRect = treeWidget->viewport()->rect(); // 获取顶部位置(坐标0,0)的项目 QTreeWidgetItem *item = treeWidget->itemAt(viewportRect.topLeft()); // 如果顶部位置没有项目,则尝试中心位置?或者直接返回空 // 但通常,如果树有项目,顶部位置应该有项目(因为垂直滚动条会滚动到顶部项目) if (!item) { // 尝试在视口顶部中间位置获取 item = treeWidget->itemAt(QPoint(viewportRect.width()/2, 0)); if (!item) { return items; // 空列表 } } // 开始从上到下遍历 while (item) { // 获取当前项目的矩形 QRect rect = treeWidget->visualItemRect(item); // 如果当前项目的顶部已经超过视口的底部,则跳出 if (rect.top() > viewportRect.bottom()) { break; } // 如果当前项目的底部在视口的顶部之上(即整个项目都在视口上方),则跳过(实际上这种情况不会出现,因为我们从上往下遍历,而且视口顶部开始) // 但是我们判断项目是否在视口内:项目矩形与视口矩形相交 if (rect.intersects(viewportRect) || viewportRect.contains(rect)) { items.append(item); } // 获取下一个项目(下方相邻项目) item = treeWidget->itemBelow(item); } return items; } ``` 注意:这个方法可能会漏掉一些项目,比如整个项目都在视口上方但还没有被遍历到的项目?不会,因为我们是从最顶部的项目开始向下遍历的。另外,如果项目在视口下方,则会被跳过(直到超出视口底部就停止)。 但是,有可能树结构展开后,项目很多,而我们只遍历了可视区域内的项目,效率较高。 另一种方法是使用QTreeWidget的indexes()函数(从模型索引获取)结合QTreeView的visibleRegion(),但QTreeWidget继承自QTreeView,所以我们可以使用QTreeView的方法: 使用treeWidget->indexes(treeWidget->viewport()->rect()) 但是,这个函数返回的是在指定矩形内的所有项目的模型索引。注意:这个矩形是视口坐标。 但是,这个函数可能会返回很多索引,包括每一列?因为每个项目可能有多列。 所以我们可以这样做: ```cpp QList<QTreeWidgetItem*> getVisibleItems(QTreeWidget *treeWidget) { QList<QTreeWidgetItem*> items; // 获取视口矩形 QRect viewportRect = treeWidget->viewport()->rect(); // 获取在视口矩形内的所有索引 QModelIndexList indexes = treeWidget->indexes(viewportRect); // 遍历这些索引,取出每个索引对应的项目(注意:只取第一列,因为每个项目第一列代表项目本身,其他列是子项) QSet<QTreeWidgetItem*> uniqueItems; // 使用集合避免重复(同一个项目不同列会多次出现) for (const QModelIndex &index : indexes) { // 只取第一列的索引(因为每个项目的第一列索引对应的item就是该项目) if (index.column() == 0) { QTreeWidgetItem *item = treeWidget->itemFromIndex(index); if (item) { uniqueItems.insert(item); } } } items = uniqueItems.values(); // 可能需要排序?按照从上到下的顺序 // 我们可以使用itemAbove和itemBelow来排序,但这里我们使用另一种方法:根据项目的y坐标排序 std::sort(items.begin(), items.end(), [treeWidget](QTreeWidgetItem *a, QTreeWidgetItem *b) { return treeWidget->visualItemRect(a).top() < treeWidget->visualItemRect(b).top(); }); return items; } ``` 这种方法可能会更准确,因为它获取了所有在视口矩形内的项目(包括只显示一部分的项目),而且避免了使用itemBelow遍历可能漏掉的问题(比如,有些项目可能不在连续的行上?但实际上树控件是按行显示的,所以应该是连续的)。 但是,indexes(QRect)函数可能会返回很多索引,如果项目很多,效率可能不高?因为它会遍历所有项目?实际上,它是根据绘制区域来获取的,所以应该只返回在矩形内的项目,效率应该可以接受。 在Qt文档中,QTreeView::indexes(const QRect &rect) const函数返回rect区域内所有项目的模型索引。所以这种方法也是可行的。 综上所述,两种方法都可以。第一种方法效率更高(只遍历连续的行),第二种方法更准确(获取所有在矩形内的项目,包括非连续的?但树控件是连续的,所以连续行)。不过,第一种方法在树控件折叠展开时可能会有问题?不会,因为itemBelow是按照显示顺序的。 考虑到性能,我们选择第一种方法(从上到下遍历)即可,因为它只需要遍历可视区域内的项目数量,而第二种方法需要遍历整个控件(?不一定,因为Qt内部实现可能优化了,只返回在矩形内的索引,但索引数量可能很多)。 但是,在大型树中,第一种方法更优。 因此,我们推荐使用第一种方法。 注意:在获取itemAt时,可能因为树控件有头视图(header)而偏移,但是itemAt的坐标是相对于视口的,而且头视图不在视口内,所以我们不用考虑。 另外,我们还需要考虑水平滚动?因为项目可能很长,但我们在判断时只关心垂直方向。因为可视范围主要指的是垂直方向,水平方向超出我们可以通过水平滚动条查看,但项目仍然在垂直方向上是可视的。所以我们只判断垂直方向。 修改第一种方法:我们判断项目矩形与视口的垂直重叠: ```cpp QList<QTreeWidgetItem*> getVisibleItems(QTreeWidget *treeWidget) { QList<QTreeWidgetItem*> items; QRect viewportRect = treeWidget->viewport()->rect(); // 获取视口顶部的一个项目 QTreeWidgetItem *item = treeWidget->itemAt(QPoint(0,0)); if (!item) { // 如果(0,0)没有,尝试中间位置 item = treeWidget->itemAt(QPoint(viewportRect.width()/2, 0)); if (!item) { return items; } } // 从当前项目开始向下遍历,直到超出视口 while (item) { QRect itemRect = treeWidget->visualItemRect(item); // 判断项目矩形是否与视口在垂直方向有重叠 if (itemRect.top() <= viewportRect.bottom() && itemRect.bottom() >= viewportRect.top()) { items.append(item); } else if (itemRect.top() > viewportRect.bottom()) { // 已经超出视口底部,停止 break; } item = treeWidget->itemBelow(item); } return items; } ``` 这里我们判断项目矩形和视口矩形在垂直方向有重叠(即项目至少有一部分在视口内)。 这样,我们就得到了可视范围内的项目列表。 注意:这个方法可能会包含一些不在视口内的项目?比如,项目在视口上方,但还没有超出视口底部?不会,因为我们从上往下遍历,遇到第一个项目就在视口顶部(可能是部分可见),然后往下直到项目顶部超过视口底部(此时后面的项目都在视口下方,不可见)。 因此,这个函数应该可以工作。 最后,我们需要注意的是,QTreeWidget在更新期间可能会有延迟,所以在获取可视项目之前,可能需要更新视图(比如,在滚动条滚动后立即调用,可能视图还没更新?)。所以最好在paint事件之后调用,或者使用其他机制确保视图是最新的。 总结:我们提供了获取QTreeWidget可视范围内项目列表的方法。 另外,引用[2]中提到了itemAbove和itemBelow函数,我们可以利用它们进行遍历。 所以,以上代码就是解决方案。 注意:这里我们只获取了项目本身,如果项目有子项目并且展开,那么子项目也会被视为单独的项目,并被itemBelow遍历到。 如果项目未展开,则子项目不会被遍历到。 因此,这个函数返回的是当前显示的所有项目(在可视区域内)的列表。 引用[2]中提到了invisibleRootItem,但我们这里没有用到,因为从顶部开始遍历,我们获取的是顶级项目或展开后的项目。 如果树控件中没有任何项目,则返回空列表。 我们可以在Qt项目中使用这个函数。 相关问题: 1. 如何判断一个QTreeWidgetItem是否在可视范围内? 2. 如何滚动QTreeWidget使某个项目处于可视区域? 3. 如何获取QTreeWidget中所有展开的项目? 但是,根据用户的问题,我们只生成了获取可视范围内项目列表的方法。 现在,回答用户的问题: 获取QTreeWidget可视范围内的item列表,可以通过遍历当前显示的项目(从视口顶部开始,使用itemBelow逐个向下,直到超出视口范围)来实现。 我们提供了一个函数getVisibleItems,代码如上。 注意:需要包含头文件<QTreeWidget>和<QRect>等。 示例代码: ```cpp #include <QTreeWidget> #include <QRect> QList<QTreeWidgetItem*> getVisibleItems(QTreeWidget *treeWidget) { QList<QTreeWidgetItem*> items; if (!treeWidget) return items; QRect viewportRect = treeWidget->viewport()->rect(); QTreeWidgetItem *item = treeWidget->itemAt(QPoint(0,0)); if (!item) { item = treeWidget->itemAt(QPoint(viewportRect.width()/2, 0)); if (!item) { return items; } } while (item) { QRect itemRect = treeWidget->visualItemRect(item); // 检查项目是否在视口的垂直范围内 if (itemRect.bottom() >= viewportRect.top() && itemRect.top() <= viewportRect.bottom()) { items.append(item); } else if (itemRect.top() > viewportRect.bottom()) { break; } item = treeWidget->itemBelow(item); } return items; } ``` 使用:在需要的地方调用这个函数,传入treeWidget指针即可。 注意:这个函数可能不会获取到完全被其他控件遮挡的项目,但一般情况下,只要项目在QTreeWidget的视口内且未被遮挡,就会被获取到。 另外,如果树控件有样式表设置内边距,可能需要调整坐标?但itemAt和visualItemRect已经考虑了这些,所以不需要。 希望这个回答能够解决您的问题。 §§相关问题§§ 1. 如何判断一个QTreeWidgetItem是否在可视范围内? 2. 如何滚动QTreeWidget确保指定项目可见? 3. 如何获取QTreeWidget中所有被选中的项目? </think> ### 获取QTreeWidget可视范围内Item列表的方法 在Qt中,获取`QTreeWidget`当前可视范围内的Item列表需要通过以下几个步骤实现: #### 核心方法代码 ```cpp QList<QTreeWidgetItem*> getVisibleItems(QTreeWidget* treeWidget) { QList<QTreeWidgetItem*> visibleItems; // 获取可视区域矩形 QRect viewportRect = treeWidget->viewport()->rect(); // 获取顶部起始项目 QTreeWidgetItem* currentItem = treeWidget->itemAt(viewportRect.topLeft()); if (!currentItem) return visibleItems; // 无项目时返回空列表 // 向下遍历可视项目 while (currentItem) { QRect itemRect = treeWidget->visualItemRect(currentItem); // 检查是否仍在可视区域内 if (itemRect.top() <= viewportRect.bottom()) { visibleItems.append(currentItem); currentItem = treeWidget->itemBelow(currentItem); } else { break; } } return visibleItems; } ``` #### 关键API说明 1. **`itemAt()`** 根据坐标获取对应Item: `QTreeWidgetItem* itemAt(const QPoint& pos) const` ```cpp // 示例:获取视口左上角项目 QTreeWidgetItem* topItem = treeWidget->itemAt(treeWidget->viewport()->rect().topLeft()); ``` 2. **`itemBelow()`**[^2] 获取当前Item下方的相邻项目(支持嵌套结构): `QTreeWidgetItem* itemBelow(const QTreeWidgetItem* item) const` ```cpp // 示例:获取下一个可见项目 QTreeWidgetItem* nextItem = treeWidget->itemBelow(currentItem); ``` 3. **`visualItemRect()`** 获取项目在视口中的显示矩形: `QRect visualItemRect(const QTreeWidgetItem* item) const` ```cpp // 示例:判断项目是否在可视区 QRect rect = treeWidget->visualItemRect(item); bool isVisible = viewportRect.intersects(rect); ``` #### 使用示例 ```cpp // 调用函数获取可视Item列表 QList<QTreeWidgetItem*> visibleItems = getVisibleItems(ui->treeWidget); // 遍历处理可视项目 foreach (QTreeWidgetItem* item, visibleItems) { qDebug() << "Visible item:" << item->text(0); } ``` #### 注意事项 1. **折叠节点处理** 该方法自动跳过未展开的子节点,因`itemBelow()`只遍历当前可见项目[^2] 2. **性能优化** 复杂度为$O(n)$($n$=可视项目数),避免遍历全部数据 3. **坐标系统** 所有坐标均相对于视口(viewport),需通过`viewport()->rect()`获取正确范围 4. **动态更新** 推荐在`scrollContentsBy()`或`resizeEvent()`中调用,实时响应滚动/缩放
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码猿杂谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值