PySide2 在设计师创建的小部件上绘制

Posted

技术标签:

【中文标题】PySide2 在设计师创建的小部件上绘制【英文标题】:PySide2 paint on widget created by designer 【发布时间】:2019-06-07 09:38:33 【问题描述】:

Win10 和 Python 3.6.6 上的 VS Code 存在此问题。我是 Python 和 PySide2 的新手。

我在 *** 上阅读了很多关于此的主题,可能这是另一个主题的重复,但我无法绘制我的小部件。

我知道小部件对象的paintEvent() 必须以某种方式被覆盖。那里的大多数示例都在主窗口上进行了一些绘画,但我无法从 ui.file 将其传输到小部件上。

我在 .py 文件中创建了两个类,MainForm 和 Drawer。 MainForm 包含 UI 的实现,我正在尝试绘制一个小部件(名为“小部件”)。在我的 .ui 文件中有一个小部件和一个图形视图。我正在尝试在小部件上实现绘画。

paintEventTest.py 文件如下所示:

import sys

from PySide2 import QtWidgets 
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import (
    QApplication, QPushButton, QLineEdit, QTextEdit, QSpinBox, QMainWindow, QDesktopWidget, QTableWidget, 
    QTableWidgetItem, QToolButton, QToolTip)
from PySide2.QtCore import QFile, QObject, Qt


class MainForm(QMainWindow):

    def __init__(self, ui_file, parent=None):
        super(MainForm, self).__init__(parent)
        ui_file = QFile(ui_file)
        ui_file.open(QFile.ReadOnly)


        ### Load UI file from Designer ###
        loader = QUiLoader()
        self.ui_window = loader.load(ui_file)
        ui_file.close()

        self.ui_window.show()

        ### THIS IS NOT WORKING (OBVIOUSLY?) ###

        widget = self.ui_window.widget
        drawer = Drawer()
        drawer.paintEvent(widget)



class Drawer(QtWidgets.QWidget):

    def paintEvent(self, e):
        '''
        the method paintEvent() is called automatically
        the QPainter class does all the low-level drawing
        coded between its methods begin() and end()
        '''

        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawGeometry(qp)
        qp.end()

    def drawGeometry(self, qp):
        qp = QtGui.QPainter(self)
        qp.setPen(QtGui.QPen(Qt.green, 8, Qt.DashLine))
        qp.drawEllipse(40, 40, 400, 400)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    form = MainForm('./UI designer/testUI.ui')
    sys.exit(app.exec_())

testUI.ui 看起来像这样,它在“UI Designer”文件夹中实现:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>731</width>
    <height>633</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGraphicsView" name="graphicsView">
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>200</height>
       </size>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QWidget" name="widget" native="true">
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>250</height>
       </size>
      </property>
      <property name="maximumSize">
       <size>
        <width>16777215</width>
        <height>300</height>
       </size>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>731</width>
     <height>21</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

我通过上面的代码得到了这个。我不希望它起作用,但真的不知道如何引用特定的小部件来绘制。

QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
QPainter::end: Painter not active, aborted


我也对使用 graphicsscene 和 graphicsitem 在 graphicsview 上绘制等效代码感兴趣。

【问题讨论】:

【参考方案1】:

正如您所指出的,paintEvent 应该只被覆盖。因此,一种选择是推广小部件,您可以在这些答案中看到几个示例:

https://***.com/a/46616419 https://***.com/a/52131384 https://***.com/a/47273625 https://***.com/a/46671439

您必须具有以下结构:

├── main.py
├── mywidget.py
└── UI designer
    └── testUI.ui

在 mywidget.py 文件中,实现你需要的类:

mywidget.py

from PySide2 import QtCore, QtGui, QtWidgets


class Drawer(QtWidgets.QWidget):
    def paintEvent(self, e):
        """
        the method paintEvent() is called automatically
        the QPainter class does all the low-level drawing
        coded between its methods begin() and end()
        """
        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawGeometry(qp)
        qp.end()

    def drawGeometry(self, qp):
        qp.setPen(QtGui.QPen(QtCore.Qt.green, 8, QtCore.Qt.DashLine))
        qp.drawEllipse(40, 40, 400, 400)

然后您必须使用 Qt Designer 打开您的 .ui,右键单击小部件并在上下文菜单中选择提升至...,然后在对话框中填写以下内容:

按添加按钮,然后按升级按钮生成以下 .ui 文件

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>731</width>
    <height>633</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QGraphicsView" name="graphicsView">
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>200</height>
       </size>
      </property>
     </widget>
    </item>
    <item>
     <widget class="Drawer" name="widget" native="true">
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>250</height>
       </size>
      </property>
      <property name="maximumSize">
       <size>
        <width>16777215</width>
        <height>300</height>
       </size>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>731</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <customwidgets>
  <customwidget>
   <class>Drawer</class>
   <extends>QWidget</extends>
   <header>mywidget</header>
   <container>1</container>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>

另一方面,QUiLoader 仅加载 Qt 默认提供的小部件,因此如果您想使用新的小部件,则必须覆盖 createWidget 方法:

ma​​in.py

import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets, QtUiTools

from mywidget import Drawer


class UiLoader(QtUiTools.QUiLoader):
    def createWidget(self, className, parent=None, name=""):
        if className == "Drawer":
            widget = Drawer(parent)
            widget.setObjectName(name)
            return widget
        return super(UiLoader, self).createWidget(className, parent, name)


class MainForm(QtCore.QObject):
    def __init__(self, ui_file, parent=None):
        super(MainForm, self).__init__(parent)
        ui_file = QtCore.QFile(ui_file)
        ui_file.open(QtCore.QFile.ReadOnly)

        ### Load UI file from Designer ###
        loader = UiLoader()
        self.ui_window = loader.load(ui_file)
        ui_file.close()
        self.ui_window.show()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle("Fusion")
    file = os.path.join(
        os.path.dirname(os.path.realpath(__file__)), "./UI designer/testUI.ui"
    )
    form = MainForm(file)
    sys.exit(app.exec_())

【讨论】:

非常感谢,效果很好。这是执行此操作的正常方法还是有其他方法?您对使用图形视图有什么想法吗? 是否也可以在创建新小部件时实现样式表?我敢打赌,答案是肯定的,但如何? @Grohl 1) 正常是相对的,有限制(即使用 .ui 而不将其转换为 .py)这是我认为可以做到的唯一方法。 2)我不明白你的问题。 3)您是否尝试过设置样式表?如果是这样,你有什么问题?最后,我只会回答您在上一条评论中提出的问题,如果您还有其他问题,请创建另一个帖子,cmets 将讨论与您的出版物相关的问题。 :-) 1.) 好的。 2.) 我确实在帖子底部解决了 graphicsview 问题,但我可以稍后再提出一个问题。 3.) 我尝试在 MainForm 类、UiLoader 类和 Designer 中设置样式表。我将使用代码样式表代码编辑原始帖子。 @Grohl 2)您必须使用相同的解决方案,我的解决方案适用于任何小部件,对于其他小部件,请检查我在回答中提供的链接 3)不要编辑问题,您的问题已经得到解答,如果您还有其他问题,请创建另一个帖子

以上是关于PySide2 在设计师创建的小部件上绘制的主要内容,如果未能解决你的问题,请参考以下文章

简单的 Qt 小部件来绘制线条和形状(如 tkinter 画布)?

Qt 如何从 QVector 中的数据创建位图并将其显示在小部件上?

如何从python中的小部件在任何窗口上绘制

使用 qt 设计器创建的主应用程序中不会出现升级的小部件

如何在 Qt 中的弹出窗口小部件上创建平滑的圆角

强制在 contentsRect 而不是 rect 上绘制小部件