I am trying to create a basic program that has two side-by-side panels: one with image, and one with text. I am using splitter to be able to resize both areas up to the point of completely collapsing one of them. This works fine with QTextEdit
.
However, when I collapse the QGraphicsView
(with additional code that fixes division by zero error), neither the image reappears, nor the background I set as default. I find myself lost at what I should do to make the image and background reappear.
This is the image after loading the image – moving the splitter will also resize the image (this is also the desired final state):
This is how it looks after I completely collapse the image and move the splitter back to the center:
Debug indicates that QPixMap
still has the data, but they are not displayed properly. I tried adding self.scene().update()
, but that still didn't influence the outcome.
Code:
from PyQt5 import QtCore, QtGui, QtWidgets
class ImageViewer(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(ImageViewer, self).__init__(parent)
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 setPhoto(self, pixmap=None):
if pixmap and not pixmap.isNull():
self._empty = False
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
self.fitInView()
else:
self._empty = True
self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
self._photo.setPixmap(QtGui.QPixmap())
def fitInView(self, scale=True):
if self._empty:
return
rect = QtCore.QRectF(self._photo.pixmap().rect())
if not rect.isNull():
self.setSceneRect(rect)
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
if scenerect.width() == 0 or scenerect.height() == 0:
return
factor = min(viewrect.width() / scenerect.width(), viewrect.height() / scenerect.height())
self.scale(factor, factor)
def resizeEvent(self, event):
if not self._empty:
self.fitInView()
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle("Panel Editor")
self.setGeometry(250, 150, 1600, 1200)
self.centralWidget = QtWidgets.QWidget()
self.setCentralWidget(self.centralWidget)
self.splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
self.viewer = ImageViewer()
self.splitter.addWidget(self.viewer)
self.textEditor = QtWidgets.QTextEdit()
self.splitter.addWidget(self.textEditor)
# Set the initial sizes of the splitter's panels to divide the space equally
self.splitter.setSizes([800, 800])
layout = QtWidgets.QVBoxLayout(self.centralWidget)
layout.addWidget(self.splitter)
self.createMenu()
def createMenu(self):
self.menuBar = QtWidgets.QMenuBar()
self.setMenuBar(self.menuBar)
fileMenu = self.menuBar.addMenu('&File')
openAction = QtWidgets.QAction('&Open', self)
openAction.setShortcut('Ctrl+O')
openAction.setStatusTip('Open image')
openAction.triggered.connect(self.openImage)
fileMenu.addAction(openAction)
def openImage(self):
imagePath, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Image", "", "Image Files (*.png *.jpg *.bmp)")
if imagePath:
self.viewer.setPhoto(QtGui.QPixmap(imagePath))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
You're just checking the scenerect, but you're not ensuring that the viewport size is actually valid. The result is that you could end up with a null dimension (width and/or height) of the viewport that will cause the viewrect/scenerect
fractions equal to zero, and that will become the factor.
Since the current factor is also used for self.transform().mapRect(rect)
, restoring the size of the view will have no effect because the current transform uses the 0 factor above.
The solution is then to only apply the transform as long as the viewport rectangle is valid (both height and width > 0), which can be done by checking those values or the helper functions isValid()
or isNull()
.
Alternatively, just set the scale as long as factor > 0
.