PyQt5 将 QImage 放在小部件中

问题描述

我创建了一个允许我绘制数字的小图形用户界面。这个数字应该用 CNN 分类。 CNN 尚未连接到此 GUI。以后会这样做。我对 PyQt5 仍然很陌生,并使用了我在网上找到的一些代码来使用 QImage 进行绘图。它有效,但目前我可以在整个 GUI 上绘制。是否可以将其放在小部件中?这样我只能在特定的框架内绘制,而不能在整个 GUI 中绘制?

所以基本上我怎样才能在小部件或我之前在我的 GUI 上创建的东西旁边获得 self.image = QImage(...) ?这是否可能以某种方式或您甚至建议以完全不同的方式解决它?

import sys
from PyQt5 import QtWidgets
from PyQt5.QtGui import QIcon,QImage,QPainter,QPen,QBrush
from PyQt5.QtCore import Qt,QPoint
from UI.mainwindow_2 import Ui_MainWindow
import matplotlib.pyplot as plt
import numpy as np


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self,parent=None):
        super().__init__(parent)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
     
        self.image = QImage(self.size(),QImage.Format_RGB32)
        self.image.fill(Qt.white)

        self.drawing = False
        self.brushSize = 28
        self.brushColor = Qt.black
        self.lastPoint = QPoint()
        self.ui.Clear.clicked.connect(self.clear)

    def mousepressEvent(self,event):
        if event.button() == Qt.LeftButton:
            self.drawing = True
            self.lastPoint = event.pos()

    def mouseMoveEvent(self,event):
        if(event.buttons() & Qt.LeftButton) & self.drawing:
            painter = QPainter(self.image)
            painter.setPen(QPen(self.brushColor,self.brushSize,Qt.solidLine,Qt.RoundCap,Qt.RoundJoin))
            painter.drawLine(self.lastPoint,event.pos())
            self.lastPoint = event.pos()
            self.update()

    def mouseReleaseEvent(self,event):
        if event.button() == Qt.LeftButton:
            self.drawing = False

    def paintEvent(self,event):
        canvasPainter  = QPainter(self)
        canvasPainter.drawImage(self.rect(),self.image,self.image.rect())

    def clear(self):
        self.image.fill(Qt.white)
        self.update()


def main():
    app = QtWidgets.QApplication(sys.argv)

    window = MainWindow()
    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()

解决方法

首先要考虑以下几个方面:

  1. 任何(可见的)QWidget 子类都会接收到绘制事件
  2. 绘画总是受限于小部件的几何形状
  3. 绘制总是从底部到顶部进行,因此每当小部件有一些子部件时,在该父部件上绘制的任何内容都可能被这些子部件覆盖

因此,首先要做的是仅在要为其绘制的实际小部件上实现paintEvent。由于您使用的是 Designer,这会使事情变得更加复杂,因为无法对 ui 中已存在的小部件进行子类化。

幸运的是,Qt 有一个名为“promoted widgets”的概念:它允许通过指定在程序中最终生成 ui 时实际使用的自定义子类来“扩展”某个小部件。

  • 选择用户界面中的哪个小部件将用于绘画;我建议你从一个基本的 QWidget 开始,我稍后会详细解释;
  • 右键单击它,然后从上下文菜单中选择“升级到...”;
  • 在“Promoted class name”中输入您要使用的确切类名(比如“Canvas”);
  • 在“头文件”字段中,输入将包含该类的文件名,因为它将出现在 import 语句中(意味着它应该具有 {{ 1}} 扩展名!);假设您想在单个脚本中完成所有操作,并且您的脚本名为“mycanvas.py”,请在该字段中输入“mycanvas”;
  • 确保“基类名称”组合设置为您选择的小部件的确切类类型(QWidget,在这种情况下,通常会自动选择)
  • 点击“添加”,然后点击“推广”;
  • 保存ui并用pyuic生成文件;

现在,您的 py 文件的实现很简单:

mycanvas.py

两个注意事项:

  • QFrame 通常有一些边框,并且由于绘制总是在整个 小部件大小内进行,因此您最终也可能在边框上进行绘制。如果你想要一个 QFrame 并在里面绘制那个小部件,那么将一个 QWidget 作为那个框架的子元素添加并确保为该框架设置了布局;
  • 在 init 中设置 QImage 大小不是一个好方法,因为小部件可能会改变它的大小,或者它可能会被初始化为小于它最终拥有的大小;您可以在创建 QImage 之前在 class Canvas(QtWidgets.QWidget): def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.image = QImage(self.size(),QImage.Format_RGB32) self.image.fill(Qt.white) self.drawing = False self.brushSize = 28 self.brushColor = Qt.black self.lastPoint = QPoint() # ... all the painting related methods,as you did in your code class MainWindow(QtWidgets.QMainWindow): def __init__(self,parent=None): super().__init__(parent) self.ui.Clear.clicked.connect(self.ui.canvas.clear) def main(): # ... 中设置固定大小,或者跟踪内部容器中的点/线并在paintEvent 中连续绘制内容;