PyQt5中QWidget、QScrollArea与QBrush结合的绘图不显示问题
问题:启用窗口滚动后,图像上的绘制内容无法显示
需求:
- 显示尺寸大于窗口的图像
- 窗口可滚动以浏览完整图像
- 在图像上进行绘制
问题:启用滚动功能后,绘制的矩形无法在图像上显示,最小复现代码如下:
class DocumentWindow(QWidget): def __init__(self, main_obj): super().__init__() self.WIDTH = 500 self.HEIGHT = 500 self.resize(self.WIDTH, self.HEIGHT) file_path = 'invoice_example.jpeg' self.pages = {0: Image.open(file_path)} image = QImage(file_path) self.doc_original_width = image.width() self.doc_original_height = image.height() self.doc_new_width = image.width() self.doc_new_height = image.height() palette = QPalette() palette.setBrush(QPalette.Window, QBrush(image)) self.setPalette(palette) label = QLabel(None, self) label.setGeometry(0, 0, self.WIDTH, self.HEIGHT) conWin = QWidget() conLayout = QVBoxLayout(self) conLayout.setContentsMargins(0, 0, 0, 0) conWin.setLayout(conLayout) scroll = QScrollArea() scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scroll.setWidgetResizable(True) scroll.setWidget(conWin) scrollLayout = QVBoxLayout(self) # 启用这两行后,绘制内容不可见 scrollLayout.addWidget(scroll) scrollLayout.setContentsMargins(0, 0, 0, 0) self.begin = QtCore.QPoint() self.end = QtCore.QPoint() self.show() def paintEvent(self, event): qp = QtGui.QPainter(self) br = QtGui.QBrush(QtGui.QColor(100, 10, 10, 40)) qp.setBrush(br) qp.drawRect(QtCore.QRect(self.begin, self.end)) rect = QtCore.QRect(self.begin, self.end) self.coordinates = rect.getCoords() def mousePressEvent(self, event): self.begin = event.pos() self.end = event.pos() self.update() def mouseMoveEvent(self, event): self.end = event.pos() self.update() def mouseReleaseEvent(self, event): x1, y1, x2, y2 = self.coordinates if x1 == x2 or y1 == y2: return self.width_factor = self.doc_original_width / self.doc_new_width self.height_factor = self.doc_original_height / self.doc_new_height self.normalized_coordinates = [ int(x1*self.width_factor), int(y1*self.height_factor), int(x2*self.width_factor), int(y2*self.height_factor) ] self.update()
解决方案
问题根源
你把绘制逻辑写在主窗口DocumentWindow的paintEvent里,但启用滚动后,滚动区域的子控件(conWin)会覆盖主窗口的绘制层,导致绘制的矩形被遮挡。同时,鼠标事件的坐标没有考虑滚动偏移,即使绘制可见,坐标也会和图像位置不匹配。
修复方案
创建一个自定义的图像画布控件,把图像显示、绘制逻辑、鼠标事件都放在这个控件里,再将该控件放入滚动区域中。这样滚动和绘制就能正确关联,且不会被遮挡。
修改后的完整代码
from PyQt5 import QtCore, QtGui, QtWidgets from PIL import Image class ImageCanvas(QtWidgets.QWidget): def __init__(self, image_path, parent=None): super().__init__(parent) self.image = QtGui.QImage(image_path) self.doc_original_width = self.image.width() self.doc_original_height = self.image.height() # 画布尺寸和图像一致 self.setFixedSize(self.doc_original_width, self.doc_original_height) self.begin = QtCore.QPoint() self.end = QtCore.QPoint() self.normalized_coordinates = [] def paintEvent(self, event): qp = QtGui.QPainter(self) # 先绘制图像 qp.drawImage(QtCore.QPoint(0,0), self.image) # 再绘制矩形 if not self.begin.isNull() and not self.end.isNull(): br = QtGui.QBrush(QtGui.QColor(100, 10, 10, 40)) qp.setBrush(br) qp.drawRect(QtCore.QRect(self.begin, self.end)) def mousePressEvent(self, event): self.begin = event.pos() self.end = event.pos() self.update() def mouseMoveEvent(self, event): self.end = event.pos() self.update() def mouseReleaseEvent(self, event): x1, y1, x2, y2 = QtCore.QRect(self.begin, self.end).getCoords() if x1 == x2 or y1 == y2: self.begin = QtCore.QPoint() self.end = QtCore.QPoint() self.update() return # 这里因为画布尺寸和原图一致,所以比例系数为1,若后续有缩放可调整 self.normalized_coordinates = [int(x1), int(y1), int(x2), int(y2)] self.update() class DocumentWindow(QtWidgets.QWidget): def __init__(self, main_obj=None): super().__init__() self.WIDTH = 500 self.HEIGHT = 500 self.resize(self.WIDTH, self.HEIGHT) file_path = 'invoice_example.jpeg' # 创建图像画布 self.canvas = ImageCanvas(file_path) # 设置滚动区域 scroll = QtWidgets.QScrollArea() scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) scroll.setWidgetResizable(False) # 保持画布原始尺寸,否则会被拉伸 scroll.setWidget(self.canvas) # 主布局 main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(scroll) self.show() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = DocumentWindow() sys.exit(app.exec_())
关键改动说明
- 分离画布与主窗口:
ImageCanvas专门负责图像显示和交互,所有绘制、鼠标事件都在这里处理,避免被滚动区域控件遮挡。 - 滚动区域配置:设置
setWidgetResizable(False),确保画布保持图像原始尺寸,滚动区域仅提供滚动功能,不会拉伸图像。 - 绘制顺序:先绘制图像,再绘制矩形,保证矩形在图像上方显示。
- 坐标逻辑修正:鼠标事件的坐标直接对应画布(图像)的坐标,无需额外转换(若后续需要缩放图像,可在
ImageCanvas中添加缩放逻辑,再调整坐标转换)。
内容的提问来源于stack exchange,提问作者Julio S.
相关产品推荐
相关产品推荐

