上一篇博客给了一个QImageView类,其中实现了图片浏览器的基础鼠标操作,在此基础之上,进一步实现鼠标框选的操作。具体实现时,我们先构建一个场景类QImageScene,用于管理场景中的Item,场景中的Item主要有两类,一类是图像,另一类是选择框。对于选择框的操作,我们把鼠标事件定义在QImage Scene类中。
为了方便我们理清逻辑,这里强调下消息流的方向。视图框架事件的传递顺序是view->scene->item,如果需要将事件继续向后传递,使用event->ignore()是没用的,猜测因为view看做是一个控件,scene和item都是控件内的组件,ignore只能处理控件到控件的事件,但是控件内的事件无能为力。这里可以使用QGraphicsView::mousexxxEvent(event)这样的函数,将event事件再次传入视图。上面的例子也是用到了这样的方法。
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, QMetaObject, QObject, QPoint, QPointF, QRect, QRectF, QSize, QSizeF, QTime, QUrl, Qt, Signal) from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QGradient, QIcon, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) from PySide6.QtWidgets import (QAbstractScrollArea, QApplication, QGraphicsView, QMainWindow, QMenuBar, QSizePolicy, QStatusBar, QVBoxLayout, QWidget) from PySide6 import QtGui, QtWidgets class QImageScene(QtWidgets.QGraphicsScene): def __init__(self, parent=None): super(QImageScene, self).__init__(QRectF(-500, -500, 1000, 1000), parent) self._start = QPointF() self.it_img = None self.it_rect = None def remove_img(self): if self.it_img is not None: self.removeItem(self.it_img) self.it_img = None def remove_rect(self): if self.it_rect is not None: self.removeItem(self.it_rect) self.it_rect = None def has_rect_roi(self): if self.it_rect is None or self.it_img is None: return False return True def get_rect_roi(self): if self.it_img is None: return None img_rect = self.it_img.boundingRect() if self.it_rect is None: return img_rect lab_rect = self.it_rect.rect() if not lab_rect.intersects(img_rect): return img_rect return lab_rect.intersected(img_rect) def set_image(self, img): if self.it_img is not None: self.removeItem(self.it_img) self.it_img = QtWidgets.QGraphicsPixmapItem(img) self.addItem(self.it_img) def mousePressEvent(self, event): #if self.itemAt(event.scenePos(), QtGui.QTransform()) is None: if event.button() == Qt.RightButton \ and self.it_img is not None: if self.it_rect is not None: self.removeItem(self.it_rect) self.it_rect = QtWidgets.QGraphicsRectItem() self.it_rect.setPen(QtGui.QPen(Qt.red, 0, Qt.SolidLine)) self.it_rect.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, False) self.addItem(self.it_rect) self._start = event.scenePos() r = QRectF(self._start, self._start) self.it_rect.setRect(r) super(QImageScene, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self.it_rect is not None and event.buttons() == Qt.RightButton: r = QRectF(self._start, event.scenePos()).normalized() self.it_rect.setRect(r) super(QImageScene, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): if self.it_img is None: self.remove_img() if self.it_rect is not None: rect = self.it_rect.rect() rect_img = self.it_img.boundingRect() if rect.width() < 4 or rect.height() < 4 or not rect.intersects(rect_img): self.remove_rect() else: r = rect.intersected(rect_img).normalized() self.it_rect.setRect(r) super(QImageScene, self).mouseReleaseEvent(event)
下面是QImageView类,调用QImageScene实现完整功能(总体和前节博客给出的代码类似):
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, QMetaObject, QObject, QPoint, QPointF, QRect, QRectF, QSize, QSizeF, QTime, QUrl, Qt, Signal) from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QGradient, QIcon, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) from PySide6.QtWidgets import (QAbstractScrollArea, QApplication, QGraphicsView, QMainWindow, QMenuBar, QSizePolicy, QStatusBar, QVBoxLayout, QWidget) from PySide6 import QtGui, QtWidgets from window.viewer.qimagescene import QImageScene class QImageView(QGraphicsView): #signal_lclick = Signal(str) def __init__(self, parent): super(QImageView, self).__init__(parent) self.setMouseTracking(True) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setAcceptDrops(False) self.setStyleSheet(u"broder: none") #self.setCacheMode(QGraphicsView.CacheBackground) #self.setDragMode(QGraphicsView.ScrollHandDrag) #self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) #self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) self.it_img = QtWidgets.QGraphicsPixmapItem() self.scene = QImageScene() self.img = QtGui.QPixmap() self.img_sz = [] self.scale_current = 1 self.scale_fill = 1 self.scale_index = 0 self.scale_lut_base = [0.01, 0.02, 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8, 1.0, 1.25, 1.5, 1.75, 2.0, 3.0, 5.0, 7.0, 10.0, 20.0, 30.0, 50.0] self.scale_lut = self.scale_lut_base.copy() self.left_click = QPoint(0, 0) self.pointer_pos = QPoint(0, 0) def update_shown_image(self, img): self.img = img self.img_sz = self.img.size() self.scene.setSceneRect(0, 0, self.img_sz.width(), self.img_sz.height()) self.scene.set_image(img) #self.scene.removeItem(self.it_img) #self.it_img = QtWidgets.QGraphicsPixmapItem(self.img) #self.scene.addItem(self.it_img) def set_shown_image(self, img): #self.img.load(filename) self.img = img self.img_sz = self.img.size() self.scene.setSceneRect(0, 0, self.img_sz.width(), self.img_sz.height()) self.scene.set_image(img) self.setScene(self.scene) self.setTransform(QtGui.QTransform()) # init scale lut size = self.size() scale_fill_h = size.height() / self.img_sz.height() scale_fill_w = size.width() / self.img_sz.width() self.scale_fill = min(scale_fill_w, scale_fill_h) self.scale_lut = self.scale_lut_base.copy() if scale_fill_h not in self.scale_lut: self.scale_lut.append(scale_fill_h) if scale_fill_w not in self.scale_lut: self.scale_lut.append(scale_fill_w) self.scale_lut.sort() self.scale_index = self.scale_lut.index(self.scale_fill) self.scale_current = self.scale_lut[self.scale_index] img_center = QPoint(self.img_sz.width() // 2, self.img_sz.height() // 2) self.setTransform(QtGui.QTransform(self.scale_fill, 0, 0, 0, self.scale_fill, 0, 0, 0, 1)) self.centerOn(img_center) def wheelEvent(self, event): self.setTransformationAnchor(QGraphicsView.NoAnchor) self.setResizeAnchor(QGraphicsView.NoAnchor) if event.angleDelta().y() > 0.5 and self.scale_index < len(self.scale_lut) - 1: # zoom in self.scale_index = self.scale_index + 1 elif event.angleDelta().y() < 0.5 and self.scale_index > 0: # zoom out self.scale_index = self.scale_index - 1 self.scale_current = self.scale_lut[self.scale_index] oldpos = self.mapToScene(QPoint(event.position().x(), event.position().y())) self.setTransform(QtGui.QTransform(self.scale_current, 0, 0, 0, self.scale_current, 0, 0, 0, 1)) newpos = self.mapToScene(QPoint(event.position().x(), event.position().y())) delta = newpos - oldpos self.translate(delta.x(), delta.y()) event.ignore() #super(QImageView, self).wheelEvent(event) def mousePressEvent(self, event): button = event.button() # move image if button == Qt.LeftButton: self.setCursor(Qt.OpenHandCursor) self.left_click = self.mapToScene(event.pos()) event.accept() super(QImageView, self).mousePressEvent(event) def mouseMoveEvent(self, event: QtGui.QMouseEvent) -> None: button = event.buttons() self.pointer_pos = self.mapToScene(event.pos()) # move image if button == Qt.LeftButton: mouse_pos = self.mapToScene(event.pos()) delta = mouse_pos - self.left_click delta = delta * self.transform().m11() # 乘以缩放比 self.centerOn(self.mapToScene(QPoint(self.viewport().rect().width() / 2 - delta.x(), self.viewport().rect().height() / 2 - delta.y()))) event.accept() else: event.ignore() super(QImageView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): button = event.button() # move image if button == Qt.LeftButton: self.setCursor(Qt.ArrowCursor) mouse_pos = self.mapToScene(event.pos()) delta = mouse_pos - self.left_click delta = delta * self.transform().m11() # 乘以缩放比 if delta.x() != 0 or delta.y() != 0: self.centerOn(self.mapToScene(QPoint(self.viewport().rect().width() / 2-delta.x(), self.viewport().rect().height() / 2-delta.y()))) event.accept() elif button == Qt.RightButton: pass super(QImageView, self).mouseReleaseEvent(event)
我们在窗体中加入提升过后的graphview就可以使用了。