How to Enable Pan and Zoom in a Qgraphicsview

How to enable Pan and Zoom in a QGraphicsView

This is not too difficult to do using the built in capabilities of QGraphicsView.

The demo script below has left-button panning and wheel zoom (including anchoring to the current cursor position). The fitInView method has been reimplemented because the built in version adds a weird fixed margin that can't be removed.

PyQt4 version:

from PyQt4 import QtCore, QtGui

class PhotoViewer(QtGui.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)

def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self._zoom = 0
self._empty = True
self._scene = QtGui.QGraphicsScene(self)
self._photo = QtGui.QGraphicsPixmapItem()
self._scene.addItem(self._photo)
self.setScene(self._scene)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
self.setFrameShape(QtGui.QFrame.NoFrame)

def hasPhoto(self):
return not self._empty

def fitInView(self, scale=True):
rect = QtCore.QRectF(self._photo.pixmap().rect())
if not rect.isNull():
self.setSceneRect(rect)
if self.hasPhoto():
unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height())
self.scale(factor, factor)
self._zoom = 0

def setPhoto(self, pixmap=None):
self._zoom = 0
if pixmap and not pixmap.isNull():
self._empty = False
self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
else:
self._empty = True
self.setDragMode(QtGui.QGraphicsView.NoDrag)
self._photo.setPixmap(QtGui.QPixmap())
self.fitInView()

def wheelEvent(self, event):
if self.hasPhoto():
if event.delta() > 0:
factor = 1.25
self._zoom += 1
else:
factor = 0.8
self._zoom -= 1
if self._zoom > 0:
self.scale(factor, factor)
elif self._zoom == 0:
self.fitInView()
else:
self._zoom = 0

def toggleDragMode(self):
if self.dragMode() == QtGui.QGraphicsView.ScrollHandDrag:
self.setDragMode(QtGui.QGraphicsView.NoDrag)
elif not self._photo.pixmap().isNull():
self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)

def mousePressEvent(self, event):
if self._photo.isUnderMouse():
self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
super(PhotoViewer, self).mousePressEvent(event)

class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = PhotoViewer(self)
# 'Load image' button
self.btnLoad = QtGui.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
# Button to change from drag/pan to getting pixel info
self.btnPixInfo = QtGui.QToolButton(self)
self.btnPixInfo.setText('Enter pixel info mode')
self.btnPixInfo.clicked.connect(self.pixInfo)
self.editPixInfo = QtGui.QLineEdit(self)
self.editPixInfo.setReadOnly(True)
self.viewer.photoClicked.connect(self.photoClicked)
# Arrange layout
VBlayout = QtGui.QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QtGui.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
HBlayout.addWidget(self.btnPixInfo)
HBlayout.addWidget(self.editPixInfo)
VBlayout.addLayout(HBlayout)

def loadImage(self):
self.viewer.setPhoto(QtGui.QPixmap('image.jpg'))

def pixInfo(self):
self.viewer.toggleDragMode()

def photoClicked(self, pos):
if self.viewer.dragMode() == QtGui.QGraphicsView.NoDrag:
self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))

if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 600)
window.show()
sys.exit(app.exec_())

PyQt5 version:

from PyQt5 import QtCore, QtGui, QtWidgets

class PhotoViewer(QtWidgets.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)

def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self._zoom = 0
self._empty = True
self._scene = QtWidgets.QGraphicsScene(self)
self._photo = QtWidgets.QGraphicsPixmapItem()
self._scene.addItem(self._photo)
self.setScene(self._scene)
self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
self.setFrameShape(QtWidgets.QFrame.NoFrame)

def hasPhoto(self):
return not self._empty

def fitInView(self, scale=True):
rect = QtCore.QRectF(self._photo.pixmap().rect())
if not rect.isNull():
self.setSceneRect(rect)
if self.hasPhoto():
unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height())
self.scale(factor, factor)
self._zoom = 0

def setPhoto(self, pixmap=None):
self._zoom = 0
if pixmap and not pixmap.isNull():
self._empty = False
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
else:
self._empty = True
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
self._photo.setPixmap(QtGui.QPixmap())
self.fitInView()

def wheelEvent(self, event):
if self.hasPhoto():
if event.angleDelta().y() > 0:
factor = 1.25
self._zoom += 1
else:
factor = 0.8
self._zoom -= 1
if self._zoom > 0:
self.scale(factor, factor)
elif self._zoom == 0:
self.fitInView()
else:
self._zoom = 0

def toggleDragMode(self):
if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
elif not self._photo.pixmap().isNull():
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

def mousePressEvent(self, event):
if self._photo.isUnderMouse():
self.photoClicked.emit(self.mapToScene(event.pos()).toPoint())
super(PhotoViewer, self).mousePressEvent(event)

class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = PhotoViewer(self)
# 'Load image' button
self.btnLoad = QtWidgets.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
# Button to change from drag/pan to getting pixel info
self.btnPixInfo = QtWidgets.QToolButton(self)
self.btnPixInfo.setText('Enter pixel info mode')
self.btnPixInfo.clicked.connect(self.pixInfo)
self.editPixInfo = QtWidgets.QLineEdit(self)
self.editPixInfo.setReadOnly(True)
self.viewer.photoClicked.connect(self.photoClicked)
# Arrange layout
VBlayout = QtWidgets.QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QtWidgets.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
HBlayout.addWidget(self.btnPixInfo)
HBlayout.addWidget(self.editPixInfo)
VBlayout.addLayout(HBlayout)

def loadImage(self):
self.viewer.setPhoto(QtGui.QPixmap('image.jpg'))

def pixInfo(self):
self.viewer.toggleDragMode()

def photoClicked(self, pos):
if self.viewer.dragMode() == QtWidgets.QGraphicsView.NoDrag:
self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))

if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 600)
window.show()
sys.exit(app.exec_())

how to pan images in QGraphicsView

QGraphicsView has build-in mouse-panning support. Set correct DragMode and it will handle the rest. You do need the enable scroll bars for that to work.

Qt: How to implement Panning/Zooming in QGraphicsScene with two finger gestures on laptop trackpad (win/mac)?

I finally manged to get this work. What i found out:

Pan Gestures (in Qt) are converted to Mouse Wheel messages which can have a y and also a x offset. This seems strange to me, as there is no Mouse with a horizontal Wheel.
Even more confusing, by MS definition Pan events are converted to WM_VSCROLL/WM_HSCROLL events (https://learn.microsoft.com/en-us/windows/win32/wintouch/windows-touch-gestures-overview)

Most of this (i.e panning) is handled quite well by Qt, but not all:

  • It seems impossible to distinguish between a gesture event on the track pad and a real wheel event on the mouse. I originally wanted to use the Mouse Wheel always for Zoom AND use the Touch Pads Pan gesture. This combination seems to be impossible. I now use Ctrl/Shift for mouse wheel zoom. This solved the problem

The zoom gesture with two fingers i had to handle differently on Mac and Windows:

  • On windows a wheel event is sent with “(event->modifiers() & Qt::ControlModifier) == true”. On the mac not. So i wrote:

    void myGraphicsView::wheelEvent(QWheelEvent* event)
    {
    if(event->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier))
    myZoomFunction(event, 0);
    else
    QGraphicsView::wheelEvent(event);
    }
  • On MacOs we have to listen to a QEvent::NativeGesture - but thats not called on Windows:

    bool myGraphicsView::viewportEvent ( QEvent * event )
    {
    switch (event->type())
    {
    case QEvent::NativeGesture:
    auto nge = dynamic_cast<QNativeGestureEvent*>(event);
    if (nge)
    {
    if(nge->gestureType() == Qt::ZoomNativeGesture)
  • On MacOs scrolling up/down is sent in angleDelta().x(), instead of angleDelta().y(). Both values are swapped for any reason.

QGraphicsView Zooming in and out under mouse position using mouse wheel

Such zooming is a bit tricky. Let me share my own class for doing that.

Header:

#include <QObject>
#include <QGraphicsView>

/*!
* This class adds ability to zoom QGraphicsView using mouse wheel. The point under cursor
* remains motionless while it's possible.
*
* Note that it becomes not possible when the scene's
* size is not large enough comparing to the viewport size. QGraphicsView centers the picture
* when it's smaller than the view. And QGraphicsView's scrolls boundaries don't allow to
* put any picture point at any viewport position.
*
* When the user starts scrolling, this class remembers original scene position and
* keeps it until scrolling is completed. It's better than getting original scene position at
* each scrolling step because that approach leads to position errors due to before-mentioned
* positioning restrictions.
*
* When zommed using scroll, this class emits zoomed() signal.
*
* Usage:
*
* new Graphics_view_zoom(view);
*
* The object will be deleted automatically when the view is deleted.
*
* You can set keyboard modifiers used for zooming using set_modified(). Zooming will be
* performed only on exact match of modifiers combination. The default modifier is Ctrl.
*
* You can change zoom velocity by calling set_zoom_factor_base().
* Zoom coefficient is calculated as zoom_factor_base^angle_delta
* (see QWheelEvent::angleDelta).
* The default zoom factor base is 1.0015.
*/
class Graphics_view_zoom : public QObject {
Q_OBJECT
public:
Graphics_view_zoom(QGraphicsView* view);
void gentle_zoom(double factor);
void set_modifiers(Qt::KeyboardModifiers modifiers);
void set_zoom_factor_base(double value);

private:
QGraphicsView* _view;
Qt::KeyboardModifiers _modifiers;
double _zoom_factor_base;
QPointF target_scene_pos, target_viewport_pos;
bool eventFilter(QObject* object, QEvent* event);

signals:
void zoomed();
};

Source:

#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>

Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
: QObject(view), _view(view)
{
_view->viewport()->installEventFilter(this);
_view->setMouseTracking(true);
_modifiers = Qt::ControlModifier;
_zoom_factor_base = 1.0015;
}

void Graphics_view_zoom::gentle_zoom(double factor) {
_view->scale(factor, factor);
_view->centerOn(target_scene_pos);
QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
_view->viewport()->height() / 2.0);
QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
_view->centerOn(_view->mapToScene(viewport_center.toPoint()));
emit zoomed();
}

void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
_modifiers = modifiers;

}

void Graphics_view_zoom::set_zoom_factor_base(double value) {
_zoom_factor_base = value;
}

bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
QPointF delta = target_viewport_pos - mouse_event->pos();
if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
target_viewport_pos = mouse_event->pos();
target_scene_pos = _view->mapToScene(mouse_event->pos());
}
} else if (event->type() == QEvent::Wheel) {
QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
if (QApplication::keyboardModifiers() == _modifiers) {
if (wheel_event->orientation() == Qt::Vertical) {
double angle = wheel_event->angleDelta().y();
double factor = qPow(_zoom_factor_base, angle);
gentle_zoom(factor);
return true;
}
}
}
Q_UNUSED(object)
return false;
}

Usage example:

Graphics_view_zoom* z = new Graphics_view_zoom(ui->graphicsView);
z->set_modifiers(Qt::NoModifier);

Zooming and panning an image in a QScrollArea

Given that the paintEvent implementation for a custom zoomable and pannable pixmap viewer is 5 lines long, one might as well implement it from scratch:

// https://github.com/KubaO/stackoverflown/tree/master/questions/image-panzoom-40683840
#include <QtWidgets>
#include <QtNetwork>

class ImageViewer : public QWidget {
QPixmap m_pixmap;
QRectF m_rect;
QPointF m_reference;
QPointF m_delta;
qreal m_scale = 1.0;
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.translate(rect().center());
p.scale(m_scale, m_scale);
p.translate(m_delta);
p.drawPixmap(m_rect.topLeft(), m_pixmap);
}
void mousePressEvent(QMouseEvent *event) override {
m_reference = event->pos();
qApp->setOverrideCursor(Qt::ClosedHandCursor);
setMouseTracking(true);
}
void mouseMoveEvent(QMouseEvent *event) override {
m_delta += (event->pos() - m_reference) * 1.0/m_scale;
m_reference = event->pos();
update();
}
void mouseReleaseEvent(QMouseEvent *) override {
qApp->restoreOverrideCursor();
setMouseTracking(false);
}
public:
void setPixmap(const QPixmap &pix) {
m_pixmap = pix;
m_rect = m_pixmap.rect();
m_rect.translate(-m_rect.center());
update();
}
void scale(qreal s) {
m_scale *= s;
update();
}
QSize sizeHint() const override { return {400, 400}; }
};

A comparable QGraphicsView-based widget would be only slightly shorter, and would have a bit more overhead if the pixmap was very small. For large pixmaps, the time spent rendering the pixmap vastly overshadows any overhead due to the QGraphicsScene/QGraphicsView machinery. After all, the scene itself is static, and that is the ideal operating point for performance of QGraphicsView.

class SceneImageViewer : public QGraphicsView {
QGraphicsScene m_scene;
QGraphicsPixmapItem m_item;
public:
SceneImageViewer() {
setScene(&m_scene);
m_scene.addItem(&m_item);
setDragMode(QGraphicsView::ScrollHandDrag);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setResizeAnchor(QGraphicsView::AnchorViewCenter);
}
void setPixmap(const QPixmap &pixmap) {
m_item.setPixmap(pixmap);
auto offset = -QRectF(pixmap.rect()).center();
m_item.setOffset(offset);
setSceneRect(offset.x()*4, offset.y()*4, -offset.x()*8, -offset.y()*8);
translate(1, 1);
}
void scale(qreal s) { QGraphicsView::scale(s, s); }
QSize sizeHint() const override { return {400, 400}; }
};

And a test harness:

int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QWidget ui;
QGridLayout layout{&ui};
ImageViewer viewer1;
SceneImageViewer viewer2;
QPushButton zoomOut{"Zoom Out"}, zoomIn{"Zoom In"};
layout.addWidget(&viewer1, 0, 0);
layout.addWidget(&viewer2, 0, 1);
layout.addWidget(&zoomOut, 1, 0, 1, 1, Qt::AlignLeft);
layout.addWidget(&zoomIn, 1, 1, 1, 1, Qt::AlignRight);

QNetworkAccessManager mgr;
QScopedPointer<QNetworkReply> rsp(
mgr.get(QNetworkRequest({"http://i.imgur.com/ikwUmUV.jpg"})));
QObject::connect(rsp.data(), &QNetworkReply::finished, [&]{
if (rsp->error() == QNetworkReply::NoError) {
QPixmap pixmap;
pixmap.loadFromData(rsp->readAll());
viewer1.setPixmap(pixmap);
viewer2.setPixmap(pixmap);
}
rsp.reset();
});
QObject::connect(&zoomIn, &QPushButton::clicked, [&]{
viewer1.scale(1.1); viewer2.scale(1.1);
});
QObject::connect(&zoomOut, &QPushButton::clicked, [&]{
viewer1.scale(1.0/1.1); viewer2.scale(1.0/1.1);
});
ui.show();
return a.exec();
}

Synchronize two QGraphicsView with different images

You only need to syncronize scrollbar values of two QGraphicsViews

def bindScrollBars(scrollBar1, scrollBar2):

# syncronizing scrollbars syncrnonously somehow breaks zooming and doesn't work
# scrollBar1.valueChanged.connect(lambda value: scrollBar2.setValue(value))
# scrollBar2.valueChanged.connect(lambda value: scrollBar1.setValue(value))

# syncronizing scrollbars asyncronously works ok
scrollBar1.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar2.setValue(scrollBar1.value())))
scrollBar2.valueChanged.connect(lambda _: QtCore.QTimer.singleShot(0, lambda: scrollBar1.setValue(scrollBar2.value())))

class Window(QtWidgets.QWidget):
def __init__(self):
...
bindScrollBars(self.viewer.horizontalScrollBar(), self.viewerSecondImage.horizontalScrollBar())
bindScrollBars(self.viewer.verticalScrollBar(), self.viewerSecondImage.verticalScrollBar())

Also wheelEvent can be simplified

    def wheelEvent(self, event):
if self.hasPhoto():
factor = 1.25
if event.angleDelta().y() > 0:
self.scale(factor, factor)
else:
self.scale(1/factor, 1/factor)
self.viewUpdated.emit(self.transform())

Pyqt5 QgraphicsView pan past scroll bar limits

Update2

Please try this Code. Is this appropriate answer for you?
If so, I want to add new explanation.

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
from math import sqrt
class Line(QGraphicsLineItem):
def __init__(self, x1, y1, x2, y2):
super(Line, self).__init__()
pen = self.pen()
pen.setWidth(10)
pen.setColor(Qt.gray)
pen.setStyle(Qt.SolidLine)

self.setPen(pen)
self.origin = self.pos()
self.setZValue(1)
self.setLine(QLineF(x1, y1, x2, y2))
class Point(QGraphicsItem):
def __init__(self, x, y):
super(Point, self).__init__()
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.rectF = QRectF(0, 0, 30, 30)
self.x=x
self.y=y
self.origin = QPointF(self.pos())
self._brush = QBrush(Qt.black)
self.setZValue(2)
def setBrush(self, brush):
self._brush = brush
self.update()

def boundingRect(self):
return self.rectF

def paint(self, painter=None, style=None, widget=None):
painter.fillRect(self.rectF, self._brush)

def hoverMoveEvent(self, event):
point = event.pos().toPoint()
print(point)
QGraphicsItem.hoverMoveEvent(self, event)

class Viewer(QGraphicsView):
photoClicked = pyqtSignal(QPoint)
rectChanged = pyqtSignal(QRect)

def __init__(self, parent):
super(Viewer, self).__init__(parent)
self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
self.setMouseTracking(True)
self.origin = QPoint()
self.changeRubberBand = False
self.mid_panning = False

self._zoom = 0
self._empty = True
self._scene = QGraphicsScene(self)
self._scene.setBackgroundBrush(Qt.white)
self.white_board = QGraphicsRectItem()
self.white_board.setZValue(1)
self.white_board.setBrush(Qt.white)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setFrameShape(QFrame.NoFrame)
self.area = float()
self.setPoints()
self._old_x = QCursor.pos().x()
self._old_y = QCursor.pos().y()

QTimer.singleShot(0, self.fitInView) # This is done so that it can fit into view on load

def setItems(self):
self.data = {'x': [-2414943.8686, -2417160.6592, -2417160.6592, -2417856.1783, -2417054.7618, -2416009.9966, -2416012.5232, -2418160.8952, -2418160.8952, -2416012.5232, -2417094.7694, -2417094.7694], 'y': [10454269.7008,
10454147.2672, 10454147.2672, 10453285.2456, 10452556.8132, 10453240.2808, 10455255.8752, 10455183.1912, 10455183.1912, 10455255.8752, 10456212.5959, 10456212.5959]}
maxX = max(self.data['x'])
minX = min(self.data['x'])
maxY = max(self.data['y'])
minY = min(self.data['y'])
distance = sqrt((maxX-minX)**2+(maxY-minY)**2)

self.area = QRectF(minX , minY , distance , distance )
self.white_board.setRect(QRectF(minX , minY , distance , distance ))
self._scene.addItem(self.white_board)
line1 = Line(minX, minY, minX+distance, minY+distance)
line2 = Line(minX+distance, minY, minX, minY+distance)
self._scene.addItem(line1)
self._scene.addItem(line2)
for i,x in enumerate(self.data['x']):
x = self.data['x'][i]
y = self.data['y'][i]
p = Point(x,y)
p.setPos(x,y)
self._scene.addItem(p)

self.setScene(self._scene)

def make_area2(self, area):
x = area.x()
y = area.y()
width = area.width()
height = area.height()
x -= 2*x
y -= 2*y
width = width*2
height = height*2
area = QRectF(x, y, width, height)
return area
def fitInView(self, scale=True):
rect = QRectF(self.area)
if not rect.isNull():
self.setSceneRect(rect)
unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
print(unity.width(), unity.height())
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height())
print(scenerect.width(), scenerect.height())
self.scale(factor, factor)

self._zoom = 0

def setPoints(self):
self._zoom = 0
self.setItems()
self.setDragMode(self.ScrollHandDrag)
# self.fitInView()

def wheelEvent(self, event):
if event.angleDelta().y() > 0:
factor = 1.25
self._zoom += 1
else:
factor = 0.8
self._zoom -= 1
if self._zoom > 0:
self.scale(factor, factor)
elif self._zoom == 0:
self.fitInView()
else:
self._zoom = 0

def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:

self.origin = event.pos()
self.rubberBand.setGeometry(QRect(self.origin, QSize()))
self.rectChanged.emit(self.rubberBand.geometry())
self.rubberBand.show()
self.changeRubberBand = True
return
#QGraphicsView.mousePressEvent(self,event)
elif event.button() == Qt.MidButton:
self.viewport().setCursor(Qt.ClosedHandCursor)
self.origin = event.pos()
self.original_event = event
self.mid_panning = True
self.scene_origin = self.mapToScene(event.pos())
self._old_x = QCursor.pos().x()
self._old_y = QCursor.pos().y()
for i in self._scene.items():

i.origin = i.pos()
# I recommend that you get the all item position.
handmade_event = QMouseEvent(QEvent.MouseButtonPress,QPointF(event.pos()),Qt.LeftButton,event.buttons(),Qt.KeyboardModifiers())
QGraphicsView.mousePressEvent(self,handmade_event)

super(Viewer, self).mousePressEvent(event)

def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.changeRubberBand = False
QGraphicsView.mouseReleaseEvent(self,event)
elif event.button() == Qt.MidButton:
self.viewport().setCursor(Qt.OpenHandCursor)
handmade_event = QMouseEvent(QEvent.MouseButtonRelease,QPointF(event.pos()),Qt.LeftButton,event.buttons(),Qt.KeyboardModifiers())
self.mid_panning = False
# here you set the original point.
for i in self._scene.items():
i.setPos(i.origin)
QGraphicsView.mouseReleaseEvent(self,handmade_event)
super(Viewer, self).mouseReleaseEvent(event)
def calc_offset(self, x, y):
offset_x = x - int(self.viewport().width()/2)
offset_y = y - int(self.viewport().height()/2)
return offset_x, offset_y

def mouseMoveEvent(self, event):
if self.changeRubberBand:
self.rubberBand.setGeometry(QRect(self.origin, event.pos()).normalized())
self.rectChanged.emit(self.rubberBand.geometry())
QGraphicsView.mouseMoveEvent(self,event)
elif self.mid_panning:

new_x = event.x()
new_y = event.y()
offset_x, offset_y = self.calc_offset(new_x, new_y)
for item in self._scene.items():
item.setPos(QPointF(item.pos().x() - (new_x - self._old_x)*10, item.pos().y() - (new_y - self._old_y)*10))

self._old_x = new_x
self._old_y = new_y
return

super(Viewer, self).mouseMoveEvent(event)

class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = Viewer(self)
self.btnLoad = QToolButton(self)
self.btnLoad.setText('Fit Into View')
self.btnLoad.clicked.connect(self.fitPoints)

VBlayout = QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QHBoxLayout()
HBlayout.setAlignment(Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)

VBlayout.addLayout(HBlayout)
self.viewer.fitInView()

def fitPoints(self):
self.viewer.fitInView()

if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.setGeometry(300, 400, 800, 600)
window.show()
sys.exit(app.exec_())


Related Topics



Leave a reply



Submit