I'm trying to link two qgraphicsview objects so that when one window zooms in the other qgraphicsview zooms as well. I'm trying to accomplish this by calling the test function when one window is going to zoom. I then try to call self.scale(factor,factor) in the other qgraphicsview object. I get an error :AttributeError: 'PhotoViewer' object has no attribute 'viewer'. How do I get I'm trying to call the class ui self.viewer but judging from the error I'm still in the Photoviewer object. How do I call the other windows self.scale from when one window is zoomed in? Thanks in advance.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QPixmap, QPainter, QPen
import sys
class PhotoViewer(QtWidgets.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, parent,num_view):
super(PhotoViewer, self).__init__(parent)
#to track which viewer
self.num_view=num_view
self.drawmode=0
self._zoom = 0
self.drawing = False
self.lastPoint = QPoint()
self.image=False
self.image=QPixmap(r"image.jpg")
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:
ui.test(self,self.num_view,factor)
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)
class ui(QtWidgets.QWidget):
def __init__(self):
super(ui, self).__init__()
self.viewer = PhotoViewer(self,1)
self.viewer2 = PhotoViewer(self,2)
# 'Load image' button
self.btnLoad = QtWidgets.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
# draw mode
self.btndraw = QtWidgets.QToolButton(self)
self.btndraw.setText('Draw Mode')
self.btndraw.clicked.connect(self.drawmode)
# 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)
HBlayout2 = QtWidgets.QHBoxLayout()
HBlayout2.setAlignment(QtCore.Qt.AlignLeft)
HBlayout2.addWidget(self.viewer2)
HBlayout2.addWidget(self.viewer)
HBlayout = QtWidgets.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
HBlayout.addWidget(self.btnPixInfo)
HBlayout.addWidget(self.btndraw)
HBlayout.addWidget(self.editPixInfo)
VBlayout.addLayout(HBlayout2)
VBlayout.addLayout(HBlayout)
#scroll bars
self.viewer.horizontalScrollBar().valueChanged.connect(self.horizontal_scroll)
self.viewer.verticalScrollBar().valueChanged.connect(self.vertical_scroll)
self.viewer2.horizontalScrollBar().valueChanged.connect(self.horizontal_scroll2)
self.viewer2.verticalScrollBar().valueChanged.connect(self.vertical_scroll2)
def test(self,num,factor):
if num==1:
self.viewer2.scale(factor,factor)
if num==2:
self.viewer.scale(factor,factor)
#auto scroll
def horizontal_scroll(self):
self.viewer2.horizontalScrollBar().setValue(self.viewer.horizontalScrollBar().value())
def horizontal_scroll2(self):
self.viewer.horizontalScrollBar().setValue(self.viewer2.horizontalScrollBar().value())
def vertical_scroll(self):
self.viewer2.verticalScrollBar().setValue(self.viewer.verticalScrollBar().value())
def vertical_scroll2(self):
self.viewer.verticalScrollBar().setValue(self.viewer2.verticalScrollBar().value())
def loadImage(self):
self.viewer.setPhoto(QtGui.QPixmap(r'temp.png'))
self.viewer2.setPhoto(QtGui.QPixmap(r'temp.png'))
self.image=QPixmap(r"image.jpg")
def drawmode(self):
self.viewer.toggleDrawMode()
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__':
app = QtWidgets.QApplication(sys.argv)
window = ui()
window.setGeometry(500, 300, 800, 600)
window.show()
sys.exit(app.exec_())
Instead of trying to scale the view directly, you could use signals for this:
Add the following code to PhotoViewer
:
class PhotoViewer(QtWidgets.QGraphicsView):
scaled = QtCore.pyqtSignal(int, int, QtGui.QTransform, int)
....
def scale(self, horz, vert):
super().scale(horz, vert)
self.scaled.emit(self.horizontalScrollBar().value(),
self.verticalScrollBar().value(),
self.transform(),
self._zoom
)
def set_transform(self, horz_scroll, vert_scroll, transform, zoom):
# temporary block signals from scroll bars to prevent interference
horz_blocked = self.horizontalScrollBar().blockSignals(True)
vert_blocked = self.verticalScrollBar().blockSignals(True)
self._zoom = zoom
self.setTransform(transform)
dx = horz_scroll - self.horizontalScrollBar().value()
dy = vert_scroll - self.verticalScrollBar().value()
self.horizontalScrollBar().setValue(dx)
self.verticalScrollBar().setValue(dy)
self.horizontalScrollBar().blockSignals(horz_blocked)
self.verticalScrollBar().blockSignals(vert_blocked)
And to ui
:
class ui(QtWidgets.QWidget):
def __init__(self):
....
self.viewer.scaled.connect(self.viewer2.set_transform)
self.viewer2.scaled.connect(self.viewer.set_transform)
Also remove the call to ui.test()
form PhotoViewer.wheelEvent
.