1

This issue is with VS Code on Win10 and Python 3.6.6. I'm new both to Python and PySide2.

I've read a lot of topics on this here at StackOverflow and possibly this is a duplicate of another topic, but I'm not able to get my widget painted.

I understand that the paintEvent() of the widget object have to be overridden somehow. Most of the examples out there does some painting on the main window but I'm not able to transfer this on widgets from an ui.file.

I've created two classes in my .py-file, MainForm and Drawer. The MainForm contains implementation of UI and I'm trying to get a widget (named "widget") painted. In my .ui-file there's a widget and a graphicsview. I'm trying to implement painting on the widget.

paintEventTest.py file looks like this:

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 looks like this and it's implemented in a folder "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>

I'm getting this with code above. I'm not expecting it to work, but have really no idea on how to reference the specific widget to paint on.

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


I'm also interested in equivalent code painting on the graphicsview with graphicsscene and graphicsitem.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Cobse
  • 73
  • 11

1 Answers1

2

As you point out, the paintEvent should only overridden. So one option is to promote the widget, you can see several examples in these answers:

You must have the following structure:

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

In the mywidget.py file, implement the class you require:

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)

Then you have to open your .ui with Qt Designer, right click on the widget and select promote To... in the contextual menu, then fill in the dialog with the following:

enter image description here

press the add button and then the promote button generating the following .ui file

<?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>

On the other the QUiLoader only loads the widgets that Qt provides by default so if you want to use new widget you must overwrite the createWidget method:

main.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_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thank you very much, this worked well. Is this the normal way to do this or are there other ways? Do you have some thaughts regarding the use of a graphicsview instead? – Cobse Jun 07 '19 at 13:20
  • Is it also possible to implement stylesheet when we create the new widget? I'll bet the answer is yes, but how? – Cobse Jun 07 '19 at 13:34
  • @Grohl 1) Normal is relative, with the restrictions you have (ie the use of .ui without converting it to .py) this is the only way I think it can be done. 2) I did not understand your question. 3) Have you tried to set a stylesheet? If so, what problems have you had? Finally I will only answer the questions that you have indicated in the previous comment and if you have other questions create another post, the comments are to discuss issues related to your publication. :-) – eyllanesc Jun 07 '19 at 13:48
  • 1.) OK. 2.) I did actually address graphicsview question at the bottom of my post, but I can make another question about it later. 3.) I tried to set the stylesheet in the MainForm class, UiLoader class and in Designer. I'll edit the original post with code stylesheet code. – Cobse Jun 07 '19 at 13:59
  • @Grohl 2) you must use the same, my solution works for any widget, for other widgets check the links I have provided in my answer 3) Do not edit the question, your question is already answered, if you have another question create another post – eyllanesc Jun 07 '19 at 14:03
  • @Grohl The posts must be specific, so you should not extend yourself. The best thing is to create another question in another post :-) – eyllanesc Jun 07 '19 at 14:05