文章目录
- QT图形视图系统
- 介绍
- 开始搭建MainWindow框架
- 设置scene的属性
- 缩放功能的添加
- 加上标尺
QT图形视图系统
介绍
详细的介绍可以看QT的官方助手,那里面介绍的详细且明白,需要一定的英语基础,我这里直接使用一个开源项目来介绍QGraphicsView、QGraphicsScene的使用。
先提供一个项目的图片
先来一个简单的例子,这个例子是介绍了一下QGraphicsView 和 QGraphicsScene的关系,并且如何在View中展示Scene
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsScene scene;
scene.addText("Hello, QGraphicsView");
QGraphicsView view(&scene);
view.show();
return app.exec();
}
上面的是最基本的QGraphicsView 中显示QGraphicsScene, 并且打印Hello, QGraphicsView在界面上的例子。由此我们可以看到,scene对象需要被view对象管理之后再显示出来。
接下来,我们将重写QGraphicsView 来实现我们自己要的效果。
开始搭建MainWindow框架
使用mainwindowz作为整个项目的外部界面框架,并且将自己的view放在mainwindow中
mainwindow 之后的代码我会将头文件代码和cpp代码放在一个代码块中,请注意区分
// mainwindow.h
#ifndef GRAPHICSVIEWQ_MAINWINDOW_H
#define GRAPHICSVIEWQ_MAINWINDOW_H
#include <QMainWindow>
class GraphicsView;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow() override;
protected:
private:
GraphicsView *graphics_view_;
};
#endif //GRAPHICSVIEWQ_MAINWINDOW_H
// mainwindow.cpp
#include <QHBoxLayout>
#include "mainwindow.h"
#include "graphicsview.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setMouseTracking(true);
resize(1600, 1000);
graphics_view_ = new GraphicsView(this);
graphics_view_->setObjectName(QString::fromUtf8("graphicsView"));
graphics_view_->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
graphics_view_->setResizeAnchor(QGraphicsView::AnchorUnderMouse);
QWidget *centralWidget = new QWidget(this);
centralWidget->setObjectName(QString::fromUtf8("centralwidget"));
QHBoxLayout *horizontalLayout= new QHBoxLayout(centralWidget);
horizontalLayout->setSpacing(0);
horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
horizontalLayout->setContentsMargins(3, 3, 3, 3);
horizontalLayout->addWidget(graphics_view_);
setCentralWidget(centralWidget);
QGraphicsScene *scene = new QGraphicsScene();
scene->addText("Hello, MainWindow");
graphics_view_->setScene(scene);
}
MainWindow::~MainWindow()
{
}
graphicsview
// graphicsview.h
#ifndef GRAPHICSVIEWQ_GRAPHICSVIEW_H
#define GRAPHICSVIEWQ_GRAPHICSVIEW_H
#include <QGraphicsView>
#include <QWidget>
class GraphicsView : public QGraphicsView
{
Q_OBJECT
public:
explicit GraphicsView(QWidget *parent = nullptr);
explicit GraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);
~GraphicsView() override;
protected:
private:
};
#endif //GRAPHICSVIEWQ_GRAPHICSVIEW_H
// graphicsview.cpp
#include "graphicsview.h"
GraphicsView::GraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
}
GraphicsView::GraphicsView(QGraphicsScene *scene, QWidget *parent)
: QGraphicsView(scene, parent)
{
}
GraphicsView::~GraphicsView()
{
}
这个时候我们展示mainwindow的时候是能正常看到 hello mainwindow的时候,我们离我们的目标又进一步了。
设置scene的属性
接下来给我们的view在构造的时候加一些属性,并且删除掉mainwindow中的scene
void GraphicsView::setBaseAttribute()
{
// 设置场景
QGraphicsScene *scene = new QGraphicsScene(this);
scene->addText("Hello, MainWindow");
setScene(scene);
// 设置接收场景交互
setInteractive(true);
// 接收Drop事件
setAcceptDrops(true);
// 接收鼠标移动事件
setMouseTracking(true);
// CacheNone 所有的绘画都是直接在视窗上完成的.
// 背景被缓存,这影响自定义背景和基于backgroundBrush属性的背景.当这个标志被启用,QGraphicsView将分配一个像素图与viewport的完整尺寸.
setCacheMode(CacheBackground);
// 渲染时,QGraphicsView在渲染背景或前景以及渲染每个项目时保护画家状态(参见QPainter::save())。这允许你让画工处于一个改变的状态(例如,你可以调用QPainter::setPen()或QPainter::setBrush(),而不需要在绘画后恢复状态)。但是,如果项目始终恢复状态,则应该启用此标志以防止QGraphicsView做同样的事情。
setOptimizationFlag(DontSavePainterState);
// 禁用QGraphicsView对曝光区域的抗锯齿自动调整。
setOptimizationFlag(DontAdjustForAntialiasing);
// QGraphicsView将通过分析需要重绘的区域来尝试找到最佳的更新模式。
setViewportUpdateMode(SmartViewportUpdate);
// 一个橡皮筋会出现。鼠标拖动将设置橡皮筋的几何形状,并选择橡皮筋覆盖的所有项目。非交互式视图禁用此模式。
setDragMode(RubberBandDrag);
// 设置支持鼠标右键弹出菜单
setContextMenuPolicy(Qt::DefaultContextMenu);
// 设置横向和纵向滚动条常开
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
// 设置黑色背景
setStyleSheet("QGraphicsView { background: #000000 }");
scene->setSceneRect(-1000, -1000, +2000, +2000);
// 流出添加标尺的空间
setViewportMargins(24, 0, 0, 24);
}
这个时候我们再运行的时候,可以看到整个背景就编程黑色的了。并且出现了滚动条
缩放功能的添加
接下来我们给界面添加缩放功能
首先我们需要注释掉黑色背景,方便我们查看文字的变化, 并且添加以下代码,以便放大缩小的时候更好的跟随鼠标
// 设置抗锯齿
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// 设置放大缩小的时候跟随鼠标
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
接下来我们添加缩放函数,同时我们重写鼠标事件
void GraphicsView::zoomIn()
{
if(transform().m11() > 1000.0) return;
scale(zoomFactor, zoomFactor);
}
void GraphicsView::zoomOut()
{
if(transform().m11() < 1.0) return;
scale(1.0 / zoomFactor, 1.0 / zoomFactor);
}
void GraphicsView::wheelEvent(QWheelEvent *event)
{
const auto delta = event->angleDelta().y();
const auto pos = event->position().toPoint();
static auto sbUpdate = [&delta, this, scale = 3](QScrollBar* sb) {
// @TODO 如果是多个view的话 会不会出问题
sb->setValue(sb->value() - delta);
};
if (event->buttons() & Qt::RightButton) {
if (abs(delta) == 120) {
setInteractive(false);
if (delta > 0)
zoomIn();
else
zoomOut();
setInteractive(true);
}
} else {
switch (event->modifiers()) {
case Qt::ControlModifier:
if (abs(delta) == 120) {
setInteractive(false);
if (delta > 0)
zoomIn();
else
zoomOut();
setInteractive(true);
}
break;
case Qt::ShiftModifier:
if (!event->angleDelta().x())
sbUpdate(QAbstractScrollArea::horizontalScrollBar());
break;
case Qt::NoModifier:
if (!event->angleDelta().x())
sbUpdate(QAbstractScrollArea::verticalScrollBar());
break;
default:
break;
}
}
emit sig_mouseMove(mapToScene(pos));
// QGraphicsView::wheelEvent(event);
}
通过鼠标,我们可以看到对应的变化,我这里添加了混合按钮操作,ctrl是缩放,shift是移动横轴,我这里就不贴效果图了,你们按照此步骤加函数即可,自己去尝试效果去吧。
我们还需要回到最初始的大小,这个时候我们需要添加回到100%比例的函数。并且添加一个键盘事件,按下空格的时候则回到100%的状态。这里可以在初始化的时候直接给设置成百分百
QSizeF GraphicsView::getRealSize()
{
static QSizeF size;
if (!size.isEmpty())
return size;
if (size.isEmpty())
FIXME 当前界面的物理尺寸
size = QGuiApplication::screens()[0]->physicalSize();
return size;
}
void GraphicsView::zoomTo100()
{
根据物理尺寸设置大小, 因为后面我们会引入尺子,因此这里设置为根据物理尺寸设置
double x = 1.0, y = 1.0;
const double m11 = QGraphicsView::transform().m11(), m22 = QGraphicsView::transform().m22();
const double dx = QGraphicsView::transform().dx(), dy = QGraphicsView::transform().dy();
const QSizeF size(getRealSize()); // size in mm
const QRect scrGeometry(QApplication::primaryScreen()->geometry()); // size in pix
x = qAbs(1.0 / m11 / (size.height() / scrGeometry.height()));
y = qAbs(1.0 / m22 / (size.width() / scrGeometry.width()));
std::cout << dx << " " << dy << std::endl;
scale(x, y);
恢复到初始状态(位移状态未记录)
// QMatrix q;
// q.setMatrix(1,this->matrix().m12(),this->matrix().m21(),1,this->matrix().dx(),this->matrix().dy());
// this->setMatrix(q,false);
}
void GraphicsView::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Space:
zoomTo100();
break;
case Qt::Key_F:
zoomFit();
break;
default:
break;
}
QGraphicsView::keyPressEvent(event);
}
void GraphicsView::zoomFit()
{
fitInView(scene()->itemsBoundingRect(), false);
}
void GraphicsView::fitInView(QRectF dstRect, bool withBorders)
{
if (dstRect.isNull())
return;
if (withBorders)
dstRect += QMarginsF(dstRect.width() / 5, dstRect.height() / 5, dstRect.width() / 5, dstRect.height() / 5); // 5 mm
QGraphicsView::fitInView(dstRect, Qt::KeepAspectRatio);
}
加上标尺
接下来我们来给我们的视图加上左边和下面的标尺
先上一张图片
ruler
#ifndef GRAPHICSVIEWLEARN_RULER_H
#define GRAPHICSVIEWLEARN_RULER_H
#include <QWidget>
#include <QPen>
class Ruler final : public QWidget
{
Q_OBJECT
public:
enum { Width = 24};
explicit Ruler(Qt::Orientation rulerType, QWidget* parent);
void drawAScaleMeter(QPainter* painter, QRectF rulerRect, double scaleMeter, double startPosition);
// 绘制刻度线
void drawFromOriginTo(QPainter* painter, QRectF rect, double startMark, double endMark, int startTickNo, double step, double startPosition);
protected:
void paintEvent(QPaintEvent* event) override;
void drawMousePosTick(QPainter* painter);
private:
Qt::Orientation orientation_;
double grid_step_ {1.0};
double origin_ {};
double ruler_unit_ {1.0};
double ruler_zoom_ {1.0};
double tick_koef_ {1.0};
QPoint cursor_pos_;
QPen meter_pen_;
bool draw_text_ {};
};
#endif //GRAPHICSVIEWLEARN_RULER_H
#include "ruler.h"
#include <QPainter>
Ruler::Ruler(Qt::Orientation rulerType, QWidget *parent)
: QWidget(parent)
, orientation_ { rulerType }
{
setMouseTracking(true);
setStyleSheet("QWidget{ background:black; }");
}
void Ruler::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHints(QPainter::TextAntialiasing);
painter.setPen(QPen(Qt::darkGray, 0.0)); // 零宽度笔是装饰笔
QRectF rulerRect(rect()); // 需要QRectF
// 首先填充矩形
painter.fillRect(rulerRect, QColor().rgb());
if (qFuzzyIsNull(ruler_zoom_))
return;
// fixme 这个地方需要修改成带单位转换的
grid_step_ = pow(10.0, ceil(log10(8.0 / ruler_zoom_)));
// ViewSettings::instance().gridStep(rulerZoom_);
// 绘制小刻度
if ((grid_step_ * ruler_zoom_) > 35) {
tick_koef_ = 0.1;
draw_text_ = true;
}
meter_pen_ = QPen(Qt::darkGray, 0.0);
drawAScaleMeter(&painter, rulerRect, grid_step_ * 1, static_cast<double>(Ruler::Width) * 0.6);
draw_text_ = false;
// 绘制中间刻度
if ((grid_step_ * ruler_zoom_) <= 35) {
tick_koef_ = 0.5;
draw_text_ = true;
}
meter_pen_ = QPen(Qt::green, 0.0);
drawAScaleMeter(&painter, rulerRect, grid_step_ * 5, static_cast<double>(Ruler::Width) * 0.3);
draw_text_ = false;
// 绘制整刻度线
meter_pen_ = QPen(Qt::red, 0.0);
drawAScaleMeter(&painter, rulerRect, grid_step_ * 10, static_cast<double>(Ruler::Width) * 0);
// 绘制当前鼠标位置十字线
drawMousePosTick(&painter);
// 在视图和标尺之间分割线 红色的线(看是否需要)
if ((1)) {
QPointF starPt((Qt::Horizontal == orientation_) ? rulerRect.topLeft() : rulerRect.topRight());
QPointF endPt((Qt::Horizontal == orientation_) ? rulerRect.topRight() : rulerRect.bottomRight()); // FIXME same branches!!!!!!
painter.setPen(QPen(Qt::red, 2));
painter.drawLine(starPt, endPt);
}
QWidget::paintEvent(event);
}
void Ruler::drawAScaleMeter(QPainter* painter, QRectF rulerRect, double scaleMeter, double startPosition)
{
bool isHorzRuler = Qt::Horizontal == orientation_;
scaleMeter = scaleMeter * ruler_unit_ * ruler_zoom_;
double rulerStartMark = isHorzRuler ? rulerRect.left() : rulerRect.top();
// Ruler rectangle ending mark
double rulerEndMark = isHorzRuler ? rulerRect.right() : rulerRect.bottom();
if (origin_ >= rulerStartMark && origin_ <= rulerEndMark) {
drawFromOriginTo(painter, rulerRect, origin_, rulerEndMark, 0, scaleMeter, startPosition);
drawFromOriginTo(painter, rulerRect, origin_, rulerStartMark, 0, -scaleMeter, startPosition);
} else if (origin_ < rulerStartMark) {
int tickNo = int((rulerStartMark - origin_) / scaleMeter);
drawFromOriginTo(painter, rulerRect, origin_ + scaleMeter * tickNo,
rulerEndMark, tickNo, scaleMeter, startPosition);
} else if (origin_ > rulerEndMark) {
int tickNo = int((origin_ - rulerEndMark) / scaleMeter);
drawFromOriginTo(painter, rulerRect, origin_ - scaleMeter * tickNo,
rulerStartMark, tickNo, -scaleMeter, startPosition);
}
}
void Ruler::drawFromOriginTo(QPainter* painter, QRectF rect, double startMark, double endMark, int startTickNo, double step, double startPosition)
{
const auto isHorzRuler = (Qt::Horizontal == orientation_);
// fixme 这个地方要修改成单位转换的
const auto K = grid_step_ * tick_koef_ * 1.0;
QColor color(0xFFFFFFFF - QColor(Qt::black).rgb());
painter->setPen(QPen(color, 0.0));
painter->setFont(font());
QVector<QLineF> lines;
lines.reserve(abs(ceil((endMark - startMark) / step)));
constexpr double padding = 3;
for (double current = startMark; (step < 0 ? current >= endMark : current <= endMark); current += step) {
double x1, y1;
lines.push_back(
QLineF(x1 = isHorzRuler ? current : rect.left() + startPosition,
y1 = isHorzRuler ? rect.top() : current,
/*x2*/ isHorzRuler ? current : rect.right(),
/*y2*/ isHorzRuler ? rect.bottom() - startPosition : current)
);
if (draw_text_) {
painter->save();
auto number { QString::number(startTickNo * K) };
if (startTickNo)
number = ((isHorzRuler ^ (step > 0.0)) ? "-" : "+") + number;
QRectF textRect(QFontMetricsF(font()).boundingRect(number));
textRect.setWidth(textRect.width() + 1);
if (isHorzRuler) {
painter->translate(x1 + padding, textRect.height());
painter->drawText(textRect, Qt::AlignCenter, number);
} else {
painter->translate(textRect.height() - padding, y1 - padding);
painter->rotate(-90);
painter->drawText(textRect, number);
}
painter->restore();
}
++startTickNo;
}
painter->setPen(meter_pen_);
painter->drawLines(lines.data(), lines.size());
}
void Ruler::drawMousePosTick(QPainter* painter)
{
QPoint starPt = cursor_pos_;
QPoint endPt;
if (Qt::Horizontal == orientation_) {
starPt.setY(this->rect().top());
endPt.setX(starPt.x());
endPt.setY(this->rect().bottom());
} else {
starPt.setX(this->rect().left());
endPt.setX(this->rect().right());
endPt.setY(starPt.y());
}
painter->drawLine(starPt, endPt);
}
好了,本篇先介绍到这里,接下来我会写下一篇,让我们一起去实现后续的效果。