class ImageView(QGraphicsView): # Tell PageWidget that a file is dropped onto view. dropped_relay = Signal(QDropEvent) def __init__(self, image_path): super(ImageView, self).__init__(None) self.scene = QGraphicsScene() self.setScene(self.scene) self.pixmapitem = self.scene.addPixmap(QPixmap.fromImage(QImage(image_path))) self.last_release_time = 0 self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self.refresh_image) # Register file watcher self.watcher.addPath(image_path) def dragEnterEvent(self, drag_enter_event): # QDragEnterEvent if drag_enter_event.mimeData().hasUrls(): drag_enter_event.acceptProposedAction() # https://stackoverflow.com/a/4421835/4112667 def dragMoveEvent(self, event): pass def dropEvent(self, drop_event): # QDropEvent self.dropped_relay.emit(drop_event) ''' When overwriting an image file, I guess Windows will delete it and then create a new file with the same name. So this function will be called twice. The first round is triggered by deleting. In this case, the image file doesn't exist, so QImage and QPixmap are all invalid and as a result, the view will become white background. Only after the image being created and the function is called for the second time, will the view show the image normally. The User will notice a white flicker because of two rounds of callings. To resolve this problem, we need to detect the invalid QImage or QPixmap and skip the unintended round. ''' def refresh_image(self, image_path): qimage = QImage(image_path) if qimage.isNull(): return pixmap = QPixmap.fromImage(qimage) self.scene.removeItem(self.pixmapitem) self.pixmapitem = self.scene.addPixmap(pixmap) # This will make scrollbar fit the image self.setSceneRect(QRectF(pixmap.rect())) def mousePressEvent(self, mouse_event): # QMouseEvent if mouse_event.button() == Qt.LeftButton: self.setDragMode(QGraphicsView.ScrollHandDrag) elif mouse_event.button() == Qt.RightButton: self.setDragMode(QGraphicsView.RubberBandDrag) QGraphicsView.mousePressEvent(self, mouse_event) def mouseReleaseEvent(self, mouse_event): # QMouseEvent QGraphicsView.mouseReleaseEvent(self, mouse_event) if mouse_event.button() == Qt.LeftButton: self.setDragMode(QGraphicsView.NoDrag) elif mouse_event.button() == Qt.RightButton: self.setDragMode(QGraphicsView.NoDrag) now = time.time() delta = now - self.last_release_time self.last_release_time = now if delta < 0.3: # fast double click self.resetTransform() # Reset to original size (reset scale matrix) return # Maybe a selection selection = self.scene.selectionArea().boundingRect() self.scene.setSelectionArea(QPainterPath()) if selection.isValid(): self.fitInView(selection, Qt.KeepAspectRatio) def wheelEvent(self, wheel_event): # QWheelEvent num_degrees = wheel_event.angleDelta().y() / 8 num_steps = num_degrees / 15 coefficient = 1 + (num_steps * 0.25) self.scale(coefficient, coefficient)