1.概要
qt c++ 做一个图片流量功能的窗口,图片从一个文件夹内读取,将文件夹的呢图片文件生成一个列表。窗口有两个按钮是上一页和下一页,用于图片的切换;图片的下面有一个刻度的标识,从0~2560,刻度值显示100的整倍数的刻度值;这个文件夹的图片都是2560宽的,高度是100或者200;窗口的宽度是1280,所以图形的显示区域要有一个滚动条,一个刻度值,等于一个 像素,这个刻度的目的就是为了显示图片的像素位置,不要对图片进行缩放
2.内容
以下是实现不缩放图片的精确像素查看器完整代码:
绘图类
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include <QApplication>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
#include <QDebug>
class PixelRuler : public QGraphicsItem {
public:
PixelRuler(qreal width) : m_width(width) {}
QRectF boundingRect() const override {
return QRectF(0, 0, m_width, 40); // 增加底部空间
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override {
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);
painter->fillRect(boundingRect(), Qt::white);
painter->setPen(Qt::black);
// 绘制主刻度线(每100像素,延长至25像素)
for (int i = 0; i <= m_width; i += 100) {
painter->drawLine(i, 10, i, 25); // 延长主刻度线
// 绘制主刻度值
painter->setFont(QFont("Arial", 8));
QString text = QString::number(i);
QRectF textRect(i - 15, 28, 30, 20); // 文字下移
painter->drawText(textRect, Qt::AlignCenter, text);
}
// 绘制次要刻度线(每10像素,保持15像素高度)
for (int i = 0; i <= m_width; i += 10) {
if (i % 100 == 0) continue;
painter->drawLine(i, 10, i, 15);
}
}
private:
qreal m_width;
};
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(QWidget *parent = nullptr)
: QWidget(parent), currentIndex(0) {
setupUI();
loadImages(QDir::homePath() + "/Pictures");
}
private:
QGraphicsView *graphicsView;
QGraphicsScene *scene;
QGraphicsPixmapItem *imageItem;
PixelRuler *ruler;
QPushButton *openFolderBtn;
QPushButton *prevBtn;
QPushButton *nextBtn;
QStringList imagePaths;
int currentIndex;
const int VIEW_WIDTH = 1280;
const int RULER_HEIGHT = 30;
const int IMAGE_WIDTH = 2560;
void setupUI() {
// 创建控件
openFolderBtn = new QPushButton("Open Folder", this);
prevBtn = new QPushButton("Previous");
nextBtn = new QPushButton("Next");
// 按钮布局
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(prevBtn);
btnLayout->addWidget(nextBtn);
btnLayout->addWidget(openFolderBtn);
// 图形视图设置
scene = new QGraphicsScene(this);
graphicsView = new QGraphicsView(scene, this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setFixedWidth(VIEW_WIDTH);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(graphicsView);
mainLayout->addLayout(btnLayout);
// 连接信号槽
connect(openFolderBtn, &QPushButton::clicked, this, &ImageViewer::browseFolder);
connect(prevBtn, &QPushButton::clicked, this, &ImageViewer::showPrevious);
connect(nextBtn, &QPushButton::clicked, this, &ImageViewer::showNext);
// 初始场景设置
imageItem = new QGraphicsPixmapItem;
scene->addItem(imageItem);
ruler = new PixelRuler(IMAGE_WIDTH);
scene->addItem(ruler);
}
void loadImages(const QString &directory) {
QDir dir(directory);
QStringList filters = {"*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"};
QStringList allFiles = dir.entryList(filters, QDir::Files | QDir::Readable);
imagePaths.clear();
for (const QString &file : allFiles) {
QString filePath = dir.absoluteFilePath(file);
QPixmap pixmap(filePath);
if (pixmap.width() == IMAGE_WIDTH &&
(pixmap.height() == 60 || pixmap.height() == 100)) {
imagePaths.append(filePath);
}
}
if (imagePaths.isEmpty()) {
QMessageBox::warning(this, "Error",
"No valid images found (2560x100/200 required)");
return;
}
std::sort(imagePaths.begin(), imagePaths.end());
currentIndex = 0;
updateDisplay();
}
void updateDisplay() {
if (imagePaths.isEmpty()) {
imageItem->setPixmap(QPixmap());
return;
}
QPixmap pixmap(imagePaths[currentIndex]);
if (pixmap.isNull()) {
qDebug() << "Failed to load image:" << imagePaths[currentIndex];
QMessageBox::warning(this, "Error", "Failed to load image");
return;
}
// 保持原始尺寸
imageItem->setPixmap(pixmap);
imageItem->setPos(0, 0);
ruler->setPos(0, pixmap.height());
// 设置场景大小
scene->setSceneRect(0, 0, IMAGE_WIDTH,
pixmap.height() + RULER_HEIGHT);
// 设置视图变换矩阵(禁用自动缩放)
graphicsView->setTransform(QTransform());
graphicsView->scale(1.0, 1.0);
graphicsView->setSceneRect(scene->sceneRect());
// 更新滚动条范围
//graphicsView->horizontalScrollBar()->setRange(0, IMAGE_WIDTH - VIEW_WIDTH);
//graphicsView->horizontalScrollBar()->setPageStep(VIEW_WIDTH);
// 更新按钮状态
prevBtn->setEnabled(currentIndex > 0);
nextBtn->setEnabled(currentIndex < imagePaths.size() - 1);
}
private slots:
void browseFolder() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Folder");
if (!dir.isEmpty()) {
loadImages(dir);
}
}
void showPrevious() {
if (currentIndex > 0) {
--currentIndex;
updateDisplay();
}
}
void showNext() {
if (currentIndex < imagePaths.size() - 1) {
++currentIndex;
updateDisplay();
}
}
};
#endif // IMAGEVIEWER_H
主函数
#include "ImageViewer.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ImageViewer viewer;
viewer.setWindowTitle("Pixel-Perfect Image Viewer");
viewer.resize(1280, 600);
viewer.show();
return app.exec();
}
运行效果
关键特性说明:
- 精确像素显示:
- 图片保持原始2560宽度,不进行任何缩放
- 滚动条范围精确对应图片宽度(2560 - 1280)
- 刻度标识与图片像素1:1对应
- 智能图片加载:
- 自动过滤不符合尺寸的图片(仅加载2560宽度,100/200高度的图片)
- 支持常见图片格式(jpg/jpeg/png/bmp/gif)
- 自动按文件名排序
- 界面功能:
- 固定1280像素宽的查看区域
- 始终可见的水平滚动条
- 精确的像素刻度标识(每10像素一个次要刻度,每100像素一个主刻度)
- 导航按钮状态智能管理
- 用户体验优化:
- 平滑的滚动体验
- 清晰的刻度标识
- 即时的图片切换响应
- 保持滚动位置记忆(切换图片时滚动条位置不变)
使用说明:
- 编译运行程序
- 点击"Open Folder"选择包含2560宽度图片的文件夹
- 使用"Previous"和"Next"按钮切换图片
- 拖动滚动条查看图片不同部分
- 观察底部刻度与图片像素位置的精确对应关系
注意事项:
- 仅支持宽度为2560像素,高度为100或200像素的图片
- 窗口高度会自动调整以适应不同高度的图片
- 滚动条范围根据图片宽度自动计算
- 刻度标识始终显示在图片底部,位置固定不变
2.功能升级-添加图片的缩放功能
需求;图片更加滑轮缩放
代码
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include <QApplication>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
#include <QDebug>
#include <QGraphicsView>
#include <QScrollBar>
#include <QWheelEvent>
//QGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
class WheelZoomView : public QGraphicsView {
public:
explicit WheelZoomView(QGraphicsScene *scene,QWidget *parent = nullptr)
: QGraphicsView(scene,parent), m_scaleFactor(1.0) {
// 设置渲染选项
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
setDragMode(ScrollHandDrag); // 启用拖拽平移
}
protected:
void wheelEvent(QWheelEvent *event) override {
// 计算缩放因子(每次15%)
const qreal factor = event->angleDelta().y() > 0 ? 1.15 : 1.0 / 1.15;
// 获取鼠标在场景中的位置
const QPointF scenePos = mapToScene(event->position().toPoint());
// 执行以鼠标位置为中心的缩放
performZoom(factor, scenePos);
event->accept();
}
private:
void performZoom(qreal factor, const QPointF ¢er) {
// 限制缩放范围(0.1x 到 10x)
m_scaleFactor *= factor;
m_scaleFactor = qBound(0.1, m_scaleFactor, 10.0);
// 计算缩放变换矩阵
QTransform transform;
transform.translate(center.x(), center.y());
transform.scale(factor, factor);
transform.translate(-center.x(), -center.y());
// 应用变换
setTransform(transform * this->transform());
}
QGraphicsScene *m_scene;
qreal m_scaleFactor;
};
class PixelRuler : public QGraphicsItem {
public:
PixelRuler(qreal width) : m_width(width) {}
QRectF boundingRect() const override {
return QRectF(0, 0, m_width, 40); // 增加底部空间
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override {
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);
painter->fillRect(boundingRect(), Qt::white);
painter->setPen(Qt::black);
// 绘制主刻度线(每100像素,延长至25像素)
for (int i = 0; i <= m_width; i += 100) {
painter->drawLine(i, 10, i, 25); // 延长主刻度线
// 绘制主刻度值
painter->setFont(QFont("Arial", 8));
QString text = QString::number(i);
QRectF textRect(i - 15, 28, 30, 20); // 文字下移
painter->drawText(textRect, Qt::AlignCenter, text);
}
// 绘制次要刻度线(每10像素,保持15像素高度)
for (int i = 0; i <= m_width; i += 10) {
if (i % 100 == 0) continue;
painter->drawLine(i, 10, i, 15);
}
}
private:
qreal m_width;
};
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(QWidget *parent = nullptr)
: QWidget(parent), currentIndex(0) {
setupUI();
loadImages(QDir::homePath() + "/Pictures");
}
private:
QGraphicsView *graphicsView;
QGraphicsScene *scene;
QGraphicsPixmapItem *imageItem;
PixelRuler *ruler;
QPushButton *openFolderBtn;
QPushButton *prevBtn;
QPushButton *nextBtn;
QStringList imagePaths;
int currentIndex;
const int VIEW_WIDTH = 1280;
const int RULER_HEIGHT = 30;
const int IMAGE_WIDTH = 2560;
void setupUI() {
// 创建控件
openFolderBtn = new QPushButton("Open Folder", this);
prevBtn = new QPushButton("Previous");
nextBtn = new QPushButton("Next");
// 按钮布局
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(prevBtn);
btnLayout->addWidget(nextBtn);
btnLayout->addWidget(openFolderBtn);
// 图形视图设置
scene = new QGraphicsScene(this);
graphicsView = new WheelZoomView(scene, this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setFixedWidth(VIEW_WIDTH);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(graphicsView);
mainLayout->addLayout(btnLayout);
// 连接信号槽
connect(openFolderBtn, &QPushButton::clicked, this, &ImageViewer::browseFolder);
connect(prevBtn, &QPushButton::clicked, this, &ImageViewer::showPrevious);
connect(nextBtn, &QPushButton::clicked, this, &ImageViewer::showNext);
// 初始场景设置
imageItem = new QGraphicsPixmapItem;
scene->addItem(imageItem);
ruler = new PixelRuler(IMAGE_WIDTH);
scene->addItem(ruler);
}
void loadImages(const QString &directory) {
QDir dir(directory);
QStringList filters = {"*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"};
QStringList allFiles = dir.entryList(filters, QDir::Files | QDir::Readable);
imagePaths.clear();
for (const QString &file : allFiles) {
QString filePath = dir.absoluteFilePath(file);
QPixmap pixmap(filePath);
if (pixmap.width() == IMAGE_WIDTH &&
(pixmap.height() == 60 || pixmap.height() == 100)) {
imagePaths.append(filePath);
}
}
if (imagePaths.isEmpty()) {
QMessageBox::warning(this, "Error",
"No valid images found (2560x100/200 required)");
return;
}
std::sort(imagePaths.begin(), imagePaths.end());
currentIndex = 0;
updateDisplay();
}
void updateDisplay() {
if (imagePaths.isEmpty()) {
imageItem->setPixmap(QPixmap());
return;
}
QPixmap pixmap(imagePaths[currentIndex]);
if (pixmap.isNull()) {
qDebug() << "Failed to load image:" << imagePaths[currentIndex];
QMessageBox::warning(this, "Error", "Failed to load image");
return;
}
// 保持原始尺寸
imageItem->setPixmap(pixmap);
imageItem->setPos(0, 0);
ruler->setPos(0, pixmap.height());
// 设置场景大小
scene->setSceneRect(0, 0, IMAGE_WIDTH,
pixmap.height() + RULER_HEIGHT);
// 设置视图变换矩阵(禁用自动缩放)
graphicsView->setTransform(QTransform());
graphicsView->scale(1.0, 1.0);
graphicsView->setSceneRect(scene->sceneRect());
// 更新滚动条范围
//graphicsView->horizontalScrollBar()->setRange(0, IMAGE_WIDTH - VIEW_WIDTH);
//graphicsView->horizontalScrollBar()->setPageStep(VIEW_WIDTH);
// 更新按钮状态
prevBtn->setEnabled(currentIndex > 0);
nextBtn->setEnabled(currentIndex < imagePaths.size() - 1);
}
private slots:
void browseFolder() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Folder");
if (!dir.isEmpty()) {
loadImages(dir);
}
}
void showPrevious() {
if (currentIndex > 0) {
--currentIndex;
updateDisplay();
}
}
void showNext() {
if (currentIndex < imagePaths.size() - 1) {
++currentIndex;
updateDisplay();
}
}
};
#endif // IMAGEVIEWER_H
主函数
#include "ImageViewer.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ImageViewer viewer;
viewer.setWindowTitle("Pixel-Perfect Image Viewer");
viewer.resize(1280, 600);
viewer.show();
return app.exec();
}
运行结果
缩小
放大
3.功能升级-添加文件夹切换,和位置标注、滚动
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include <QApplication>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
#include <QDebug>
#include <QLabel>
#include <QGraphicsView>
#include <QScrollBar>
#include <QWheelEvent>
//QGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
class WheelZoomView : public QGraphicsView {
public:
explicit WheelZoomView(QGraphicsScene *scene,QWidget *parent = nullptr)
: QGraphicsView(scene,parent), m_scaleFactor(1.0) {
// 设置渲染选项
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
setDragMode(ScrollHandDrag); // 启用拖拽平移
}
protected:
void wheelEvent(QWheelEvent *event) override {
// 计算缩放因子(每次15%)
const qreal factor = event->angleDelta().y() > 0 ? 1.15 : 1.0 / 1.15;
// 获取鼠标在场景中的位置
const QPointF scenePos = mapToScene(event->position().toPoint());
// 执行以鼠标位置为中心的缩放
performZoom(factor, scenePos);
event->accept();
}
private:
void performZoom(qreal factor, const QPointF ¢er) {
// 限制缩放范围(0.1x 到 10x)
m_scaleFactor *= factor;
m_scaleFactor = qBound(0.1, m_scaleFactor, 10.0);
// 计算缩放变换矩阵
QTransform transform;
transform.translate(center.x(), center.y());
transform.scale(factor, factor);
transform.translate(-center.x(), -center.y());
// 应用变换
setTransform(transform * this->transform());
}
QGraphicsScene *m_scene;
qreal m_scaleFactor;
};
class PixelRuler : public QGraphicsItem {
public:
PixelRuler(qreal width) : m_width(width) {}
QRectF boundingRect() const override {
return QRectF(0, 0, m_width, 40); // 增加底部空间
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override {
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);
painter->fillRect(boundingRect(), Qt::white);
painter->setPen(Qt::black);
// 绘制主刻度线(每100像素,延长至25像素)
for (int i = 0; i <= m_width; i += 100) {
if(i==this->start||i==this->end){
painter->setPen(Qt::red);
painter->drawLine(i, 10, i, 25); // 延长主刻度线
painter->setPen(Qt::black);
}else{
painter->drawLine(i, 10, i, 25); // 延长主刻度线
}
// 绘制主刻度值
painter->setFont(QFont("Arial", 8));
QString text = QString::number(i);
QRectF textRect(i - 15, 28, 30, 20); // 文字下移
painter->drawText(textRect, Qt::AlignCenter, text);
}
// 绘制次要刻度线(每10像素,保持15像素高度)
for (int i = 0; i <= m_width; i += 10) {
if (i % 100 == 0) continue;
if(i==this->start||i==this->end){
painter->setPen(Qt::red);
painter->drawLine(i, 10, i, 15);
painter->setPen(Qt::black);
}else{
painter->drawLine(i, 10, i, 15);
}
}
}
public:
int start;
int end;
private:
qreal m_width;
};
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(QWidget *parent = nullptr)
: QWidget(parent), currentIndex(0) {
setupUI();
//loadImages(QDir::homePath() + "/Pictures");
loadMainFolders("D:\\imgs");
loadCurrentFolder();
}
private:
QGraphicsView *graphicsView;
QGraphicsScene *scene;
QGraphicsPixmapItem *imageItem;
PixelRuler *ruler;
QPushButton *openFolderBtn;
QPushButton *prevBtn;
QPushButton *nextBtn;
QPushButton *prevDayBtn;
QPushButton *nextDayBtn;
QStringList imagePaths;
int currentIndex;
const int VIEW_WIDTH = 1280;
const int RULER_HEIGHT = 30;
const int IMAGE_WIDTH = 2560;
// UI组件
QLabel *folderLabel;
QLabel *imageStatusLabel;
// 数据存储
QStringList folderList;
//QStringList imageFiles;
int currentFolderIndex;
//int currentImageIndex;
void setupUI() {
// 创建控件
prevDayBtn = new QPushButton("前一天");
nextDayBtn = new QPushButton("后一天");
folderLabel = new QLabel("1/9 20250425");
// 创建控件
openFolderBtn = new QPushButton("Open Folder", this);
// 按钮布局
QHBoxLayout *btnLayout_top = new QHBoxLayout;
btnLayout_top->addWidget(prevDayBtn);
btnLayout_top->addWidget(folderLabel);
btnLayout_top->addWidget(nextDayBtn);
btnLayout_top->addWidget(openFolderBtn);
prevBtn = new QPushButton("Previous");
nextBtn = new QPushButton("Next");
imageStatusLabel = new QLabel("0/0");
// 按钮布局
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(prevBtn);
btnLayout->addWidget(imageStatusLabel);
btnLayout->addWidget(nextBtn);
// 图形视图设置
scene = new QGraphicsScene(this);
graphicsView = new WheelZoomView(scene, this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setFixedWidth(VIEW_WIDTH);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(btnLayout_top);
mainLayout->addWidget(graphicsView);
mainLayout->addLayout(btnLayout);
// 连接信号槽
connect(openFolderBtn, &QPushButton::clicked, this, &ImageViewer::browseFolder);
connect(prevBtn, &QPushButton::clicked, this, &ImageViewer::showPrevious);
connect(nextBtn, &QPushButton::clicked, this, &ImageViewer::showNext);
// 信号槽连接
connect(prevDayBtn, &QPushButton::clicked, this, &ImageViewer::prevDay);
connect(nextDayBtn, &QPushButton::clicked, this, &ImageViewer::nextDay);
// 初始场景设置
imageItem = new QGraphicsPixmapItem;
scene->addItem(imageItem);
ruler = new PixelRuler(IMAGE_WIDTH);
scene->addItem(ruler);
}
//加载图片文件夹
void loadImages(const QString &directory) {
qDebug()<<"directory:"+directory;
QDir dir(directory);
QStringList filters = {"*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"};
QStringList allFiles = dir.entryList(filters, QDir::Files | QDir::Readable);
imagePaths.clear();
for (const QString &file : allFiles) {
QString filePath = dir.absoluteFilePath(file);
QPixmap pixmap(filePath);
if (pixmap.width() == IMAGE_WIDTH &&
(pixmap.height() == 60 ||pixmap.height() == 100 || pixmap.height() == 200)) {
imagePaths.append(filePath);
}
}
if (imagePaths.isEmpty()) {
QMessageBox::warning(this, "Error",
"No valid images found (2560x100/200 required)");
return;
}
std::sort(imagePaths.begin(), imagePaths.end());
currentIndex = 0;
updateDisplay();
}
//更新刻度尺
void updateRuler(){
QStringList str = imagePaths[currentIndex].split("=")[1].split("_")[0].split("-");
qDebug()<<str;
int a = str[0].toInt();
int b = str[1].toInt();
ruler->start = (a/10)*10;
ruler->end = (b/10+1)*10;
qDebug()<<ruler->end;
if(ruler->end>2560){
ruler->end = 2560;
}
ruler->update();
int stroll = a;
if(stroll==0){
}else{
stroll= stroll/2;
}
this->graphicsView->horizontalScrollBar()->setValue(stroll);
}
//更新图片
void updateDisplay() {
if (imagePaths.isEmpty()) {
imageItem->setPixmap(QPixmap());
return;
}
QPixmap pixmap(imagePaths[currentIndex]);
if (pixmap.isNull()) {
qDebug() << "Failed to load image:" << imagePaths[currentIndex];
QMessageBox::warning(this, "Error", "Failed to load image");
return;
}
// 保持原始尺寸
imageItem->setPixmap(pixmap);
imageItem->setPos(0, 0);
ruler->setPos(0, pixmap.height());
// 设置场景大小
scene->setSceneRect(0, 0, IMAGE_WIDTH,
pixmap.height() + RULER_HEIGHT);
// 设置视图变换矩阵(禁用自动缩放)
graphicsView->setTransform(QTransform());
graphicsView->scale(1.0, 1.0);
graphicsView->setSceneRect(scene->sceneRect());
// 更新滚动条范围
//graphicsView->horizontalScrollBar()->setRange(0, IMAGE_WIDTH - VIEW_WIDTH);
//graphicsView->horizontalScrollBar()->setPageStep(VIEW_WIDTH);
// 更新按钮状态
prevBtn->setEnabled(currentIndex > 0);
nextBtn->setEnabled(currentIndex < imagePaths.size() - 1);
//更新刻度尺
updateRuler();
}
//加载主目录
void loadMainFolders(const QString &mainDir) {
qDebug()<<"loadMainFolders";
QDir dir(mainDir);
QStringList folders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const QString &folder : folders) {
if (QDate::fromString(folder, "yyyyMMdd").isValid()) {
folderList.append(dir.filePath(folder));
}
}
std::sort(folderList.begin(), folderList.end(), [](const QString &a, const QString &b) {
return QFileInfo(a).fileName() < QFileInfo(b).fileName();
});
currentFolderIndex = folderList.size() - 1;
qDebug()<<"loadMainFolders end";
}
//加载当前文件夹
void loadCurrentFolder() {
if (folderList.isEmpty()) return;
//QDir dir(folderList[currentFolderIndex]);
//QStringList filters = {"*.png", "*.jpg", "*.jpeg", "*.bmp"};
//imageFiles = dir.entryList(filters, QDir::Files, QDir::Name);
currentIndex = 0;
loadImages(folderList[currentFolderIndex]);
//if (!imageFiles.isEmpty()) loadImage();
updateFolderInfo();
updateImageStatus();
}
//更换文件夹
void navigateFolder(int step) {
int newIndex = currentFolderIndex + step;
if (newIndex >= 0 && newIndex < folderList.size()) {
currentFolderIndex = newIndex;
loadCurrentFolder();
}
}
//更新文件夹名称
void updateFolderInfo() {
if (folderList.isEmpty()) return;
QString info = QString("%1/%2 %3")
.arg(currentFolderIndex + 1)
.arg(folderList.size())
.arg(QFileInfo(folderList[currentFolderIndex]).fileName());
folderLabel->setText(info);
//ruler->repaint(); // 强制立即重绘
}
//更新文件名称
void updateImageStatus() {
if (imagePaths.isEmpty()) {
imageStatusLabel->setText("0/0");
return;
}
QString fileName = QFileInfo(imagePaths[currentIndex]).fileName();
QString status = QString("%1/%2 %3")
.arg(currentIndex + 1)
.arg(imagePaths.size())
.arg(fileName);
imageStatusLabel->setText(status);
}
private slots:
void prevDay() { navigateFolder(-1); }
void nextDay() { navigateFolder(1); }
void browseFolder() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Folder");
if (!dir.isEmpty()) {
//loadImages(dir);
loadMainFolders(dir);
}
}
void showPrevious() {
if (currentIndex > 0) {
--currentIndex;
updateDisplay();
}
}
void showNext() {
if (currentIndex < imagePaths.size() - 1) {
++currentIndex;
updateDisplay();
}
}
};
#endif // IMAGEVIEWER_H
#include "ImageViewer.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ImageViewer viewer;
viewer.setWindowTitle("Pixel-Perfect Image Viewer");
viewer.resize(1280, 600);
viewer.show();
return app.exec();
}
运行结果
3.关联链接
4.关联知识
1.QGraphicsScene 详解
QGraphicsScene
是 Qt 框架中 图形视图(Graphics View)架构 的核心组件,用于管理大量 2D 图形项(QGraphicsItem
)的交互、渲染和布局。它提供了一个虚拟画布,开发者可以在其中动态添加、删除或修改图形项,并通过多个视图(QGraphicsView
)进行展示。
核心功能
-
项管理
作为容器,管理所有QGraphicsItem
及其子类(如QGraphicsRectItem
、QGraphicsTextItem
等)。 -
场景坐标系
定义全局坐标系,所有项的位置和尺寸均基于此坐标系,支持缩放、旋转等变换。 -
事件传播
自动将鼠标、键盘事件传递给场景中的项,支持复杂的交互逻辑。 -
索引优化
使用 BSP树(Binary Space Partitioning Tree) 对项进行空间索引,加速渲染和碰撞检测。 -
多视图支持
允许一个场景被多个QGraphicsView
同时观察,每个视图可独立设置缩放、视口等属性。
关键概念详解
1. 场景坐标系
- 原点位置:默认在场景左上角,但可通过
setSceneRect()
调整。 - 项的位置与变换:
每个项的位置(pos()
)和变换(setTransform()
)均基于场景坐标系。
示例:item->setPos(100, 50)
将项移动到场景坐标 (100,50)。
2. 项的添加与管理
- 添加项:
QGraphicsRectItem *rect = new QGraphicsRectItem(0, 0, 100, 50); scene->addItem(rect);
- 查找项:
通过items()
或itemAt()
获取特定位置的项。 - 项的父子关系:
支持父子项结构,子项的位置和变换相对于父项。
3. 事件处理
- 事件传播机制:
事件从视图传递到场景,再由场景分发给最顶层的项。若项未处理事件,则传递给父项或场景。 - 自定义事件:
重写QGraphicsItem
的mousePressEvent()
、keyPressEvent()
等方法。
4. 性能优化
- BSP树索引:
自动管理项的渲染顺序和碰撞检测,尤其适合动态场景。可通过setItemIndexMethod()
调整索引策略。 - 项的可视性:
使用setVisible(false)
隐藏项,减少渲染开销。 - 批量操作:
在批量修改项属性时,先调用scene->setSceneRect(QRectF())
禁用索引更新,操作完成后恢复。
5. 多视图支持
- 共享场景:
多个QGraphicsView
可观察同一场景,每个视图独立设置视口、缩放比例等。QGraphicsView *view1 = new QGraphicsView(scene); QGraphicsView *view2 = new QGraphicsView(scene); view1->setViewport(new QOpenGLWidget); // 使用OpenGL加速渲染
高级用法
-
场景序列化
通过QGraphicsScene::itemIndex
遍历所有项,保存其属性和位置到文件,实现场景状态的持久化。 -
撤销/重做操作
结合QUndoStack
,记录项的修改历史,支持复杂编辑功能的撤销。 -
OpenGL加速
启用QGraphicsView::setViewport(QOpenGLWidget*)
,利用硬件加速提升大规模场景的渲染性能。 -
自定义项
继承QGraphicsItem
或QGraphicsObject
,实现复杂绘制逻辑(如自定义形状、动画)。
示例代码
// 创建场景和视图
QGraphicsScene *scene = new QGraphicsScene;
QGraphicsView *view = new QGraphicsView(scene);
view->setRenderHint(QPainter::Antialiasing); // 抗锯齿
// 添加矩形和椭圆
QGraphicsRectItem *rect = scene->addRect(50, 50, 100, 80, QPen(Qt::blue), QBrush(Qt::cyan));
QGraphicsEllipseItem *ellipse = scene->addEllipse(200, 150, 80, 60, QPen(Qt::red), QBrush(Qt::yellow));
// 处理鼠标事件(在自定义项中)
class MyItem : public QGraphicsEllipseItem {
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
qDebug() << "Item clicked at:" << event->pos();
}
};
// 设置场景范围
scene->setSceneRect(0, 0, 400, 300);
常见问题
- 项不显示
- 检查项的位置是否在视图的可见区域内。
- 确认项的
zValue()
未被遮挡。
- 性能低下
- 对静态项调用
setCacheMode(QGraphicsItem::DeviceCoordinateCache)
启用缓存。 - 减少频繁更新项的几何属性(如
setPos()
),合并多次操作为一次。
- 对静态项调用
- 坐标转换
- 使用
mapToScene()
和mapFromScene()
在项和场景坐标系间转换坐标。
- 使用
通过合理利用 QGraphicsScene
的功能,可以高效实现复杂的图形交互应用,如绘图工具、游戏地图编辑器或数据可视化界面。
2.代码解析
// 图形视图设置
scene = new QGraphicsScene(this);
graphicsView = new QGraphicsView(scene, this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setFixedWidth(VIEW_WIDTH);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
以下是您提供的 QGraphicsView
设置代码的逐行详解,帮助您理解每个配置的作用和优化方向:
1. 场景与视图的关联
scene = new QGraphicsScene(this);
graphicsView = new QGraphicsView(scene, this);
- 作用:
创建QGraphicsScene
作为图形项的容器,并将QGraphicsView
与场景绑定,视图成为观察场景的窗口。 - 关键点:
一个场景可被多个视图共享,但每个视图有独立属性(如缩放、视口位置)。
2. 视口更新模式
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
- 作用:
指定视口更新方式。FullViewportUpdate
表示每次更新整个视口区域(默认行为),确保画面一致性,但可能降低性能。 - 替代方案:
BoundingRectViewportUpdate
:仅更新项边界区域,适合频繁局部更新的场景。SmartViewportUpdate
:自动选择更新策略,平衡性能与一致性。
3. 拖拽模式
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
- 作用:
设置用户拖拽行为。ScrollHandDrag
允许用户按住鼠标中键或右键拖动视图(模拟“手形工具”)。 - 其他模式:
NoDrag
:禁用拖拽。RubberBandDrag
:框选多个项(需配合setRubberBandSelectionMode
)。
4. 变换锚点
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
- 作用:
定义缩放或旋转时的锚点位置。AnchorUnderMouse
表示以鼠标位置为基准进行变换,保持用户操作的直观性。 - 替代选项:
AnchorViewCenter
:以视口中心为锚点。NoAnchor
:无固定锚点,可能导致意外偏移。
5. 调整大小锚点
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
- 作用:
指定视口大小变化时的锚点。AnchorViewCenter
确保视口缩放或调整大小时,内容以视口中心为基准,避免内容偏移。
6. 固定宽度与滚动条
graphicsView->setFixedWidth(VIEW_WIDTH);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
- 作用:
- 固定视图宽度,避免布局变化。
- 始终显示水平滚动条,即使内容未超出视口(适合需要精确导航的场景)。
- 优化建议:
- 若内容动态变化,可改用
ScrollBarAlwaysOff
并结合ensureVisible()
自动滚动。
- 若内容动态变化,可改用
潜在优化方向
- 性能提升
- 对静态项启用缓存:
setItemCacheMode(QGraphicsItem::DeviceCoordinateCache)
。 - 使用
QOpenGLWidget
作为视口:graphicsView->setViewport(new QOpenGLWidget)
加速渲染。
- 对静态项启用缓存:
- 交互增强
- 结合
QGraphicsItem::ItemIsMovable
允许用户拖动项。 - 实现
wheelEvent
支持鼠标滚轮缩放。
- 结合
- 布局适配
- 使用
QGraphicsWidget
构建复杂UI,替代手动布局。 - 通过
sceneRect
动态调整场景范围,优化导航体验。
- 使用
完整配置示例(增强版)
// 创建场景与视图
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 2000, 2000); // 设置场景范围
graphicsView = new QGraphicsView(scene, this);
graphicsView->setViewport(new QOpenGLWidget); // 启用OpenGL加速
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setRenderHint(QPainter::Antialiasing); // 抗锯齿
// 优化滚动条策略
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// 自动滚动到场景边缘
graphicsView->setAlignment(Qt::AlignCenter);
通过上述配置,您可以构建一个高性能、交互友好的图形视图应用,适用于地图浏览、图表编辑等场景。
3.QPainter
QPainter
是 Qt 框架中用于 2D 图形绘制的核心类,它提供了一套高度优化的绘图工具,支持在窗口部件、图像(QPixmap
)、打印机等绘图设备上进行矢量图形、文本和图像的绘制。以下是其关键点详解:
核心功能
-
基本图形绘制
支持直线、矩形、椭圆、多边形、贝塞尔曲线等矢量图形的绘制。 -
文本渲染
支持多字体、颜色、对齐方式的文本输出,支持复杂文本布局。 -
图像操作
支持图像(QImage
、QPixmap
)的缩放、旋转、合成等操作。 -
坐标变换
通过平移、旋转、缩放等变换实现复杂绘图逻辑。 -
抗锯齿与合成模式
提供多种抗锯齿算法和合成模式(如透明叠加、颜色混合)。
使用流程
-
创建绘图设备
如QWidget
(在paintEvent
中)、QPixmap
、QImage
等。 -
创建 QPainter 对象
绑定到绘图设备:QPainter painter(this); // 在 QWidget 的 paintEvent 中 // 或 QPainter painter(&pixmap); // 在 QPixmap 上绘制
-
设置绘制属性
配置画笔(QPen
)、画刷(QBrush
)、字体(QFont
)等。 -
执行绘制操作
调用drawLine()
、drawRect()
、drawText()
等方法。 -
释放资源
QPainter
对象超出作用域时自动释放,无需手动销毁。
关键类与配置
1. QPen(画笔)
- 控制线条样式(颜色、宽度、虚线等):
QPen pen(Qt::red); pen.setWidth(2); pen.setStyle(Qt::DashLine); painter.setPen(pen);
2. QBrush(画刷)
- 控制填充样式(纯色、渐变、纹理等):
QBrush brush(Qt::blue); // 渐变填充 QLinearGradient gradient(0, 0, 100, 100); gradient.setColorAt(0, Qt::yellow); gradient.setColorAt(1, Qt::green); painter.setBrush(gradient);
3. 字体与文本
- 设置字体属性并绘制文本:
QFont font("Arial", 12); font.setBold(true); painter.setFont(font); painter.drawText(QRect(10, 10, 200, 30), Qt::AlignCenter, "Hello Qt!");
4. 坐标变换
- 平移、旋转、缩放:
painter.translate(100, 50); // 平移坐标系 painter.rotate(45); // 旋转45度 painter.scale(1.5, 1.5); // 放大1.5倍 painter.drawRect(0, 0, 100, 50); // 在变换后的坐标系中绘制矩形
高级功能
-
路径(QPainterPath)
创建复杂矢量路径,支持布尔运算:QPainterPath path; path.addRect(50, 50, 40, 40); path.addEllipse(50, 50, 80, 80); path.setFillRule(Qt::OddEvenFill); painter.drawPath(path);
-
合成模式(Composition Modes)
控制图形叠加方式:painter.setCompositionMode(QPainter::CompositionMode_SourceOver); // 其他模式如 CompositionMode_Multiply、CompositionMode_Screen 等
-
性能优化
- 对静态内容启用缓存:
setCacheMode(DeviceCoordinateCache)
。 - 避免在
paintEvent
中频繁创建/销毁对象。
- 对静态内容启用缓存:
示例代码(在 QWidget 中绘制)
void MyWidget::paintEvent(QPaintEvent *event) {
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); // 抗锯齿
// 绘制背景渐变
QLinearGradient gradient(0, 0, width(), height());
gradient.setColorAt(0, Qt::white);
gradient.setColorAt(1, Qt::gray);
painter.fillRect(rect(), gradient);
// 绘制红色矩形
painter.setPen(QPen(Qt::red, 2));
painter.setBrush(Qt::NoBrush);
painter.drawRect(50, 50, 100, 80);
// 绘制旋转文本
painter.save();
painter.translate(200, 100);
painter.rotate(30);
painter.drawText(0, 0, "Rotated Text");
painter.restore();
}
常见问题
- 绘图不更新
- 确保在
paintEvent
中调用绘制代码,并通过update()
触发重绘。
- 确保在
- 坐标错位
- 检查是否误用局部坐标系或未正确恢复变换(
painter.save()
/painter.restore()
)。
- 检查是否误用局部坐标系或未正确恢复变换(
- 性能问题
- 对复杂场景使用
QGraphicsView
框架,而非直接操作QPainter
。
- 对复杂场景使用
与 QGraphicsView 的对比
特性 | QPainter | QGraphicsView |
---|---|---|
适用场景 | 简单静态图形、自定义绘制 | 动态交互场景、大量图形项 |
性能 | 直接绘制,适合简单图形 | 通过场景管理,适合复杂交互 |
坐标变换 | 手动管理 | 自动管理项的位置和变换 |
项管理 | 无内置项管理 | 通过 QGraphicsItem 管理 |
选择依据:简单绘图用 QPainter
,复杂交互场景用 QGraphicsView
。
4.QRectF 详解
QRectF
是 Qt 框架中用于表示浮点数精度矩形的类,常用于图形编程、几何计算等需要高精度坐标的场景。以下是其详解:
1. 核心概念
- 坐标类型:使用
qreal
(通常为float
或double
,取决于编译配置)存储坐标,支持小数精度。 - 不可变性:
QRectF
对象不可直接修改,所有操作会返回新对象,原对象保持不变。 - 标准化(Normalized):确保左上角坐标 ≤ 右下角坐标,避免宽高为负数。
2. 创建 QRectF
构造函数
// 通过左上角坐标和宽高创建
QRectF(qreal x, qreal y, qreal width, qreal height);
// 通过左上角和右下角坐标创建
QRectF(const QPointF &topLeft, const QPointF &bottomRight);
// 直接指定坐标值
QRectF(qreal x1, qreal y1, qreal x2, qreal y2);
静态方法
// 合并两个矩形(返回包含两者的最小矩形)
QRectF QRectF::united(const QRectF &r1, const QRectF &r2);
// 计算两个矩形的交集
QRectF QRectF::intersected(const QRectF &r1, const QRectF &r2);
3. 常用成员函数
方法 | 功能 |
---|---|
x() , y() | 返回左上角 x/y 坐标 |
width() , height() | 返回宽高 |
left() , right() | 返回左/右边界坐标 |
top() , bottom() | 返回上/下边界坐标 |
topLeft() , bottomRight() | 返回左上角和右下角坐标的 QPointF |
contains(const QPointF &point) | 判断点是否在矩形内 |
intersects(const QRectF &rect) | 判断是否与另一个矩形相交 |
adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) | 调整矩形边界(返回新对象) |
translated(qreal dx, qreal dy) | 平移矩形(返回新对象) |
toRect() | 转换为整数精度的 QRect(可能丢失精度) |
isNull() | 判断矩形是否无效(宽或高为负数或零) |
4. 与 QRect 的区别
特性 | QRectF | QRect |
---|---|---|
坐标类型 | qreal (浮点) | int (整数) |
精度 | 高精度,适合图形渲染 | 低精度,适合像素操作 |
转换方法 | toRect() 转为 QRect | toRectF() 转为 QRectF |
5. 使用场景
- 图形渲染:在
QPainter
中绘制矢量图形(如抗锯齿路径)。 - 几何计算:需要高精度坐标的碰撞检测、变换操作。
- 图形视图框架:与
QGraphicsItem
等组件配合使用。
6. 示例代码
// 创建矩形:左上角 (10.5, 20.3),宽 80.0,高 60.0
QRectF rect1(10.5, 20.3, 80.0, 60.0);
// 平移矩形
QRectF rect2 = rect1.translated(5.0, -3.0);
// 调整边界(左/上/右/下各扩展 2.0)
QRectF rect3 = rect1.adjusted(-2.0, -2.0, 2.0, 2.0);
// 判断点是否在矩形内
QPointF point(30.0, 40.0);
if (rect1.contains(point)) {
qDebug() << "点在矩形内";
}
// 转换为 QRect(可能丢失精度)
QRect rectInt = rect1.toRect();
7. 注意事项
- 不可变性:频繁修改需创建新对象,可能影响性能,建议复用对象或预计算。
- 标准化检查:使用
normalized()
确保左上角 ≤ 右下角。 - 精度丢失:转换为
QRect
时,坐标会被截断为整数。
通过合理使用 QRectF
,可以在 Qt 中高效处理高精度的几何运算和图形渲染任务。
5.QGraphicsView
QGraphicsView
是 Qt 框架中 图形视图框架(Graphics View Framework) 的核心组件之一,用于高效显示和管理大型可滚动的二维图形场景(QGraphicsScene
)。以下是其详解:
1. 核心概念
- 作用:作为视口(Viewport),显示
QGraphicsScene
的内容,支持缩放、平移、旋转等操作。 - 坐标系:默认以场景坐标(Scene Coordinates)为中心,可通过变换矩阵调整视图坐标系。
- 性能优化:仅渲染可见区域内的图元(
QGraphicsItem
),适合处理复杂或大规模场景。
2. 主要功能
功能 | 说明 |
---|---|
场景浏览 | 通过滚动条查看超出视口范围的场景部分 |
视图变换 | 支持缩放(scale() )、旋转(rotate() )、平移(translate() ) |
坐标映射 | 场景坐标 ↔ 视图坐标 ↔ 部件坐标的转换 |
渲染控制 | 设置抗锯齿、平滑变换等渲染质量参数 |
多视图支持 | 多个 QGraphicsView 可观察同一场景,独立变换 |
3. 常用成员函数
场景管理
// 设置关联的场景
void setScene(QGraphicsScene *scene);
// 获取当前场景
QGraphicsScene *scene() const;
视图变换
// 缩放视图(以视图中心为基准)
void scale(qreal sx, qreal sy);
// 旋转视图(以视图中心为基准)
void rotate(qreal angle);
// 平移视图
void translate(qreal dx, qreal dy);
// 重置所有变换(恢复原始状态)
void resetTransform();
坐标映射
// 将视图坐标转换为场景坐标
QPointF mapToScene(const QPointF &viewPos) const;
// 将场景坐标转换为视图坐标
QPointF mapFromScene(const QPointF &scenePos) const;
// 将部件坐标转换为场景坐标(需考虑视口偏移)
QPointF mapToScene(const QPoint &widgetPos) const;
渲染设置
// 设置渲染质量(如抗锯齿)
void setRenderHints(QPainter::RenderHints hints);
// 设置视口更新模式(如是否保留绘制内容)
void setViewportUpdateMode(ViewportUpdateMode mode);
滚动条控制
// 设置水平和垂直滚动条策略
void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy);
void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy);
4. 信号与槽
- 场景变化信号:当场景内容或结构变化时触发。
void sceneRectChanged(const QRectF &rect);
- 视图变换信号:视图发生缩放、旋转等操作时触发。
void transformChanged();
5. 使用场景
- 大规模图形显示:如地图、电路图、流程图等需要滚动和缩放的内容。
- 交互式应用:结合鼠标和键盘事件实现拖拽、选择、编辑图元。
- 多视图同步:多个
QGraphicsView
观察同一场景,独立操作但数据共享。
6. 示例代码
// 创建场景和视图
QGraphicsScene *scene = new QGraphicsScene;
QGraphicsView *view = new QGraphicsView(scene);
// 添加矩形图元
QGraphicsRectItem *rect = scene->addRect(0, 0, 100, 100);
rect->setBrush(Qt::blue);
// 设置视图属性
view->setRenderHint(QPainter::Antialiasing); // 抗锯齿
view->setDragMode(QGraphicsView::ScrollHandDrag); // 手型工具拖拽
// 显示视图
view->show();
7. 高级特性
- 项选择:通过
setSelectionMode()
实现单选/多选图元。 - 锚点布局:使用
QGraphicsAnchorLayout
实现图元的自动布局。 - 嵌入部件:通过
QGraphicsProxyWidget
将 QWidget 嵌入场景。 - OpenGL 加速:启用
setViewport(new QOpenGLWidget)
提升渲染性能。
8. 注意事项
- 性能优化:避免频繁修改场景,批量操作使用
QGraphicsScene::setSceneRect()
。 - 坐标转换:注意场景、视图、部件坐标系的转换关系。
- 线程安全:图形操作需在主线程执行。
通过合理使用 QGraphicsView
,可以实现复杂、高性能的图形交互界面。
5.过程项目
1.基本功能
qt c++ 做一个图片流量功能的窗口,图片从一个文件夹内读取,将文件夹的呢图片文件生成一个列表。窗口有两个按钮是上一页和下一页,用于图片的切换
以下是使用Qt C++实现图片浏览功能的完整代码示例:
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QFileDialog>
#include <QDir>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPixmap>
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(QWidget *parent = nullptr) : QWidget(parent), currentIndex(0) {
setupUI();
loadImages(QDir::homePath() + "/Pictures"); // 默认加载用户图片目录
}
private:
QLabel *imageLabel;
QPushButton *openFolderBtn;
QPushButton *prevBtn;
QPushButton *nextBtn;
QStringList imagePaths;
int currentIndex;
QString currentDirectory;
void setupUI() {
// 创建控件
imageLabel = new QLabel("No images loaded", this);
imageLabel->setAlignment(Qt::AlignCenter);
imageLabel->setMinimumSize(400, 300);
imageLabel->setScaledContents(true);
openFolderBtn = new QPushButton("Open Folder", this);
prevBtn = new QPushButton("Previous");
nextBtn = new QPushButton("Next");
// 布局设置
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(prevBtn);
btnLayout->addWidget(nextBtn);
btnLayout->addWidget(openFolderBtn);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(imageLabel);
mainLayout->addLayout(btnLayout);
// 连接信号槽
connect(openFolderBtn, &QPushButton::clicked, this, &ImageViewer::browseFolder);
connect(prevBtn, &QPushButton::clicked, this, &ImageViewer::showPrevious);
connect(nextBtn, &QPushButton::clicked, this, &ImageViewer::showNext);
}
void loadImages(const QString &directory) {
QDir dir(directory);
QStringList filters = {"*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"};
imagePaths = dir.entryList(filters, QDir::Files | QDir::Readable);
// 转换为绝对路径并排序
for (int i = 0; i < imagePaths.size(); ++i) {
imagePaths[i] = dir.absoluteFilePath(imagePaths[i]);
}
std::sort(imagePaths.begin(), imagePaths.end());
currentDirectory = directory;
currentIndex = 0;
updateDisplay();
}
void updateDisplay() {
if (imagePaths.isEmpty()) {
imageLabel->setText("No images found");
prevBtn->setEnabled(false);
nextBtn->setEnabled(false);
return;
}
QPixmap pixmap(imagePaths[currentIndex]);
if (pixmap.isNull()) {
imageLabel->setText("Failed to load image");
return;
}
// 保持宽高比缩放图片
QSize labelSize = imageLabel->size();
pixmap = pixmap.scaled(labelSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
imageLabel->setPixmap(pixmap);
// 更新按钮状态
prevBtn->setEnabled(imagePaths.size() > 1);
nextBtn->setEnabled(imagePaths.size() > 1);
}
private slots:
void browseFolder() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Folder");
if (!dir.isEmpty()) {
loadImages(dir);
}
}
void showPrevious() {
if (imagePaths.isEmpty()) return;
currentIndex = (currentIndex - 1 + imagePaths.size()) % imagePaths.size();
updateDisplay();
}
void showNext() {
if (imagePaths.isEmpty()) return;
currentIndex = (currentIndex + 1) % imagePaths.size();
updateDisplay();
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ImageViewer viewer;
viewer.setWindowTitle("Image Viewer");
viewer.resize(800, 600);
viewer.show();
return app.exec();
}
#include "main.moc"
代码说明:
- 界面布局:
- 使用垂直布局管理主窗口
- 顶部为图片显示区域(QLabel)
- 底部为三个按钮:打开文件夹、上一张、下一张
- 核心功能:
- 打开文件夹:通过文件对话框选择图片目录
- 图片加载:支持常见图片格式(jpg/jpeg/png/bmp/gif)
- 自动排序:按文件名排序图片
- 循环浏览:到达首尾时自动循环
- 自动缩放:保持图片比例适应窗口大小
- 特性:
- 智能按钮状态管理(无图片时禁用导航按钮)
- 错误处理(加载失败提示)
- 支持拖放窗口调整大小
- 图片平滑缩放处理
使用说明:
- 编译运行程序
- 点击"Open Folder"选择包含图片的目录
- 使用"Previous"和"Next"按钮浏览图片
- 调整窗口大小时图片会自动缩放
扩展建议:
- 添加幻灯片播放功能
- 支持图片旋转/缩放操作
- 添加缩略图导航栏
- 支持键盘快捷键(←/→键切换)
- 添加图片信息展示(尺寸、格式等)
这个实现使用了Qt的核心模块,无需额外依赖,可以在支持Qt5/Qt6的环境下直接编译运行。
2.QDir::homePath() 指向哪里
QDir::homePath()
是 Qt 中用于获取当前用户家目录路径的跨平台方法,具体指向位置取决于操作系统:
-
Windows
C:\Users\<用户名>
或C:\Documents and Settings\<用户名>
(旧系统) -
Linux/Unix
/home/<用户名>
或通过~
符号解析的路径 -
macOS
/Users/<用户名>
特点:
- 自动适配不同操作系统
- 无需硬编码路径(如
C:\
或/home/
) - 返回路径末尾带路径分隔符(如
/
或\
) - 受系统环境变量影响(如
HOME
环境变量)
使用场景示例:
// 获取用户文档目录(跨平台安全)
QString docPath = QDir::homePath() + "Documents/MyApp/";
// 创建配置文件路径
QString configPath = QDir::homePath() + ".config/myapp.ini";
注意事项:
- 需要读写权限时,应检查目录是否存在并处理权限问题
- 多用户系统下,返回的是当前运行程序用户的家目录
- 部分系统可能通过环境变量覆盖默认路径(如
HOME
)
如果需要获取其他特殊目录(如桌面、下载目录),建议使用 QStandardPaths
类,它提供更丰富的标准路径获取方式。
3.添加刻度
qt c++ 做一个图片流量功能的窗口,图片从一个文件夹内读取,将文件夹的呢图片文件生成一个列表。窗口有两个按钮是上一页和下一页,用于图片的切换;图片的下面有一个刻度的标识,从0~2560,刻度值显示100的整倍数的刻度值;这个文件夹的图片都是2560宽的,高度是100或者200;窗口的宽度是1280,所以图形的显示区域要有一个滚动条。
以下是实现图片流量查看功能的Qt C++完整代码,包含刻度标识和滚动条支持:
#include <QApplication>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include <QDir>
#include <QFileDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
class RulerItem : public QGraphicsItem {
public:
RulerItem(qreal width, qreal height)
: m_width(width), m_height(height) {}
QRectF boundingRect() const override {
return QRectF(0, 0, m_width, m_height);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override {
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);
painter->fillRect(boundingRect(), Qt::white);
// 绘制横轴
painter->drawLine(0, m_height/2, m_width, m_height/2);
// 绘制刻度和数字
for (int i = 0; i <= 2560; i += 100) {
qreal x = i;
painter->drawLine(x, 0, x, m_height);
QString text = QString::number(i);
QRectF textRect(x - 15, m_height - 20, 30, 20);
painter->drawText(textRect, Qt::AlignCenter, text);
}
}
private:
qreal m_width;
qreal m_height;
};
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(QWidget *parent = nullptr)
: QWidget(parent), currentIndex(0) {
setupUI();
loadImages(QDir::homePath() + "/Pictures");
}
private:
QGraphicsView *graphicsView;
QGraphicsScene *scene;
QGraphicsPixmapItem *imageItem;
RulerItem *rulerItem;
QPushButton *openFolderBtn;
QPushButton *prevBtn;
QPushButton *nextBtn;
QStringList imagePaths;
int currentIndex;
const int VIEW_WIDTH = 1280;
const int RULER_HEIGHT = 50;
void setupUI() {
// 创建控件
openFolderBtn = new QPushButton("Open Folder", this);
prevBtn = new QPushButton("Previous");
nextBtn = new QPushButton("Next");
// 按钮布局
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(prevBtn);
btnLayout->addWidget(nextBtn);
btnLayout->addWidget(openFolderBtn);
// 图形视图设置
scene = new QGraphicsScene(this);
graphicsView = new QGraphicsView(scene, this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setFixedWidth(VIEW_WIDTH);
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(graphicsView);
mainLayout->addLayout(btnLayout);
// 连接信号槽
connect(openFolderBtn, &QPushButton::clicked, this, &ImageViewer::browseFolder);
connect(prevBtn, &QPushButton::clicked, this, &ImageViewer::showPrevious);
connect(nextBtn, &QPushButton::clicked, this, &ImageViewer::showNext);
// 初始场景设置
imageItem = new QGraphicsPixmapItem;
scene->addItem(imageItem);
rulerItem = new RulerItem(2560, RULER_HEIGHT);
scene->addItem(rulerItem);
}
void loadImages(const QString &directory) {
QDir dir(directory);
QStringList filters = {"*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"};
imagePaths = dir.entryList(filters, QDir::Files | QDir::Readable);
// 转换为绝对路径并排序
for (int i = 0; i < imagePaths.size(); ++i) {
imagePaths[i] = dir.absoluteFilePath(imagePaths[i]);
}
std::sort(imagePaths.begin(), imagePaths.end());
currentIndex = 0;
updateDisplay();
}
void updateDisplay() {
if (imagePaths.isEmpty()) {
imageItem->setPixmap(QPixmap());
return;
}
QPixmap pixmap(imagePaths[currentIndex]);
if (pixmap.isNull()) {
imageItem->setPixmap(QPixmap());
return;
}
// 计算缩放比例
qreal targetHeight = 100.0;
qreal scaleFactor = targetHeight / pixmap.height();
qreal targetWidth = pixmap.width() * scaleFactor;
// 缩放图片并保持宽高比
QPixmap scaledPixmap = pixmap.scaledToWidth(targetWidth, Qt::SmoothTransformation);
// 更新场景内容
imageItem->setPixmap(scaledPixmap);
imageItem->setPos(0, 0);
rulerItem->setPos(0, scaledPixmap.height());
// 设置场景大小
scene->setSceneRect(0, 0, targetWidth,
scaledPixmap.height() + RULER_HEIGHT);
graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
// 更新按钮状态
prevBtn->setEnabled(currentIndex > 0);
nextBtn->setEnabled(currentIndex < imagePaths.size() - 1);
}
private slots:
void browseFolder() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Folder");
if (!dir.isEmpty()) {
loadImages(dir);
}
}
void showPrevious() {
if (currentIndex > 0) {
--currentIndex;
updateDisplay();
}
}
void showNext() {
if (currentIndex < imagePaths.size() - 1) {
++currentIndex;
updateDisplay();
}
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ImageViewer viewer;
viewer.setWindowTitle("Image Flow Viewer");
viewer.resize(1280, 600);
viewer.show();
return app.exec();
}
#include "main.moc"
功能特点:
- 图片浏览:
- 支持常见图片格式(jpg/jpeg/png/bmp/gif)
- 自动按文件名排序
- 固定窗口宽度1280,自动高度
- 刻度标识:
- 显示0-2560范围
- 每100单位一个刻度线
- 底部固定位置显示
- 滚动支持:
- 水平滚动条自动启用
- 支持鼠标拖拽滚动
- 保持图片宽高比缩放
- 界面控制:
- 打开文件夹按钮
- 上一页/下一页导航按钮
- 智能按钮状态管理
使用说明:
- 编译运行程序
- 点击"Open Folder"选择包含2560宽度图片的文件夹
- 使用"Previous"和"Next"按钮切换图片
- 拖动滚动条查看图片不同部分
- 刻度始终显示在图片底部,随滚动移动
注意事项:
- 图片宽度必须为2560像素
- 图片高度会自动缩放至100像素(保持宽高比)
- 刻度区域高度固定为50像素
- 支持任意数量的图片文件(需符合格式要求)
4.添加滚动条
qt c++ 做一个图片流量功能的窗口,图片从一个文件夹内读取,将文件夹的呢图片文件生成一个列表。窗口有两个按钮是上一页和下一页,用于图片的切换;图片的下面有一个刻度的标识,从0~2560,刻度值显示100的整倍数的刻度值;这个文件夹的图片都是2560宽的,高度是100或者200;窗口的宽度是1280,所以图形的显示区域要有一个滚动条,一个刻度值,等于一个 像素,这个刻度的目的就是为了显示图片的像素位置
以下是实现精确像素位置显示的图片流量查看器完整代码:
#include <QApplication>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include <QDir>
#include <QFileDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPainter>
#include <QScrollBar>
class PixelRuler : public QGraphicsItem {
public:
PixelRuler(qreal width) : m_width(width) {}
QRectF boundingRect() const override {
return QRectF(0, 0, m_width, 30);
}
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override {
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setRenderHint(QPainter::Antialiasing);
painter->fillRect(boundingRect(), Qt::white);
painter->setPen(Qt::black);
// 绘制主刻度线
painter->drawLine(0, 15, m_width, 15);
// 绘制次要刻度线(每10像素)
for (int i = 0; i <= m_width; i += 10) {
if (i % 100 == 0) continue; // 主刻度线已绘制
painter->drawLine(i, 10, i, 15);
}
// 绘制主刻度值和标签
painter->setFont(QFont("Arial", 8));
for (int i = 0; i <= m_width; i += 100) {
painter->drawLine(i, 10, i, 20);
QString text = QString::number(i);
QRectF textRect(i - 15, 5, 30, 20);
painter->drawText(textRect, Qt::AlignCenter, text);
}
}
private:
qreal m_width;
};
class ImageViewer : public QWidget {
Q_OBJECT
public:
ImageViewer(QWidget *parent = nullptr)
: QWidget(parent), currentIndex(0) {
setupUI();
loadImages(QDir::homePath() + "/Pictures");
}
private:
QGraphicsView *graphicsView;
QGraphicsScene *scene;
QGraphicsPixmapItem *imageItem;
PixelRuler *ruler;
QPushButton *openFolderBtn;
QPushButton *prevBtn;
QPushButton *nextBtn;
QStringList imagePaths;
int currentIndex;
const int VIEW_WIDTH = 1280;
const int RULER_HEIGHT = 30;
const int IMAGE_WIDTH = 2560;
void setupUI() {
// 创建控件
openFolderBtn = new QPushButton("Open Folder", this);
prevBtn = new QPushButton("Previous");
nextBtn = new QPushButton("Next");
// 按钮布局
QHBoxLayout *btnLayout = new QHBoxLayout;
btnLayout->addWidget(prevBtn);
btnLayout->addWidget(nextBtn);
btnLayout->addWidget(openFolderBtn);
// 图形视图设置
scene = new QGraphicsScene(this);
graphicsView = new QGraphicsView(scene, this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
graphicsView->setFixedWidth(VIEW_WIDTH);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
// 主布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(graphicsView);
mainLayout->addLayout(btnLayout);
// 连接信号槽
connect(openFolderBtn, &QPushButton::clicked, this, &ImageViewer::browseFolder);
connect(prevBtn, &QPushButton::clicked, this, &ImageViewer::showPrevious);
connect(nextBtn, &QPushButton::clicked, this, &ImageViewer::showNext);
connect(graphicsView->horizontalScrollBar(), &QScrollBar::valueChanged,
this, &ImageViewer::handleScroll);
// 初始场景设置
imageItem = new QGraphicsPixmapItem;
scene->addItem(imageItem);
ruler = new PixelRuler(IMAGE_WIDTH);
scene->addItem(ruler);
}
void loadImages(const QString &directory) {
QDir dir(directory);
QStringList filters = {"*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif"};
imagePaths = dir.entryList(filters, QDir::Files | QDir::Readable);
// 转换为绝对路径并排序
for (int i = 0; i < imagePaths.size(); ++i) {
imagePaths[i] = dir.absoluteFilePath(imagePaths[i]);
}
std::sort(imagePaths.begin(), imagePaths.end());
currentIndex = 0;
updateDisplay();
}
void updateDisplay() {
if (imagePaths.isEmpty()) {
imageItem->setPixmap(QPixmap());
return;
}
QPixmap pixmap(imagePaths[currentIndex]);
if (pixmap.isNull()) {
imageItem->setPixmap(QPixmap());
return;
}
// 计算缩放比例
qreal scaleFactor = static_cast<qreal>(VIEW_WIDTH) / IMAGE_WIDTH;
qreal scaledHeight = pixmap.height() * scaleFactor;
// 缩放图片并保持宽高比
QPixmap scaledPixmap = pixmap.scaledToWidth(VIEW_WIDTH, Qt::SmoothTransformation);
// 更新场景内容
imageItem->setPixmap(scaledPixmap);
imageItem->setPos(0, 0);
ruler->setPos(0, scaledHeight);
// 设置场景大小
scene->setSceneRect(0, 0, VIEW_WIDTH,
scaledHeight + RULER_HEIGHT);
graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
// 更新滚动条位置
graphicsView->horizontalScrollBar()->setValue(0);
// 更新按钮状态
prevBtn->setEnabled(currentIndex > 0);
nextBtn->setEnabled(currentIndex < imagePaths.size() - 1);
}
private slots:
void browseFolder() {
QString dir = QFileDialog::getExistingDirectory(this, "Select Image Folder");
if (!dir.isEmpty()) {
loadImages(dir);
}
}
void showPrevious() {
if (currentIndex > 0) {
--currentIndex;
updateDisplay();
}
}
void showNext() {
if (currentIndex < imagePaths.size() - 1) {
++currentIndex;
updateDisplay();
}
}
void handleScroll(int value) {
// 将滚动条值转换为实际像素位置
qreal pixelPos = static_cast<qreal>(value) /
graphicsView->horizontalScrollBar()->singleStep() *
graphicsView->horizontalScrollBar()->singleStep();
// 这里可以添加显示当前像素位置的功能
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
ImageViewer viewer;
viewer.setWindowTitle("Pixel-Precise Image Viewer");
viewer.resize(1280, 600);
viewer.show();
return app.exec();
}
#include "main.moc"
功能增强说明:
- 精确像素刻度:
- 主刻度线每100像素一个,带数字标签
- 次要刻度线每10像素一个
- 刻度值与实际像素位置完全对应
- 滚动同步:
- 水平滚动条始终可见
- 滚动时保持图片与刻度的同步移动
- 支持鼠标拖拽滚动
- 图片缩放:
- 固定窗口宽度1280像素
- 图片按宽度自动缩放,保持原始宽高比
- 自动计算并显示实际高度
- 用户体验优化:
- 智能导航按钮状态管理
- 平滑的图片缩放效果
- 清晰的刻度标识
使用说明:
- 编译运行程序
- 点击"Open Folder"选择包含2560宽度图片的文件夹
- 使用"Previous"和"Next"按钮切换图片
- 拖动滚动条查看图片不同部分
- 观察底部刻度与图片像素位置的对应关系
注意事项:
- 图片宽度必须为2560像素
- 图片高度会自动缩放至窗口宽度比例(保持宽高比)
- 刻度区域高度固定为30像素
- 滚动条位置直接对应图片像素位置(需根据缩放比例计算)