1

The given code below is derived from another question on SO. It displays a QMainWindow with 4 QGraphicsView to draw with the mouse in it and a QPushButton to clear the 4 QGraphicsView.

There is a clear() method in QPainterPath class, but it was introduced in Qt 5.13 and I'm using Qt 5.12. So I wrote my own method to clear the views and its paths.

Drawing works fine and clicking on the Clear button does not cause an error, but the views are only cleared at the next time drawing in and not immediately.

What's a better solution to clear the views immediately?


main.py

import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtCore import Qt
from PyQt5.uic import loadUi

# Based on code from https://stackoverflow.com/a/44248794/7481773


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi("mainwindow.ui", self)

        self.layouts = (self.verticalLayout_top_left, self.verticalLayout_top_right,
                        self.verticalLayout_bottom_left, self.verticalLayout_bottom_right)

        self._views = []

        for layout in self.layouts:
            graphics_view = GraphicsView()
            self._views.append(graphics_view)
            layout.addWidget(graphics_view)

        self.clear_button.clicked.connect(self.clear_views)

    def clear_views(self):
        for view in self._views:
            view.clear_view()


class GraphicsView(QGraphicsView):
    def __init__(self):
        super().__init__()
        self.start = None
        self.end = None

        self.setScene(QGraphicsScene())
        self.path = QPainterPath()
        self.item = GraphicsPathItem()
        self.scene().addItem(self.item)

        self.contents_rect = self.contentsRect()
        self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def clear_view(self):
        # self.path.clear()  # Qt 5.13
        self.path = QPainterPath()
        self.scene().update()

    def mousePressEvent(self, event):
        self.start = self.mapToScene(event.pos())
        self.path.moveTo(self.start)

    def mouseMoveEvent(self, event):
        self.end = self.mapToScene(event.pos())
        self.path.lineTo(self.end)
        self.start = self.end
        self.item.setPath(self.path)


class GraphicsPathItem(QGraphicsPathItem):
    def __init__(self):
        super().__init__()
        pen = QPen()
        pen.setColor(Qt.black)
        pen.setWidth(5)
        self.setPen(pen)


def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    app.exec_()
    del main_window, app


if __name__ == "__main__":
    main()

mainwindow.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>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Paint and Clear</string>
  </property>
  <property name="locale">
   <locale language="English" country="UnitedKingdom"/>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_top_left"/>
    </item>
    <item row="0" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_top_right"/>
    </item>
    <item row="2" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_bottom_left"/>
    </item>
    <item row="2" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_bottom_right"/>
    </item>
    <item row="1" column="0" colspan="2">
     <widget class="QPushButton" name="clear_button">
      <property name="text">
       <string>Clear</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
Atalanttore
  • 349
  • 5
  • 22

1 Answers1

2

Even using the clear() method does not imply that the view is cleaned but you have to set the path in the item. Going to the problem, an equivalent method is to establish a new QPainterPath, in addition it is not necessary to call the update() method of the scene. The use of clear() or set an empty QPainterPath are equivalent from the python side, but from the C++ side it causes the same memory that optimizes the application to be reused.

def clear_view(self):
    self.path = QPainterPath()
    self.item.setPath(self.path)

If you want to have compatibility for both versions you can use try-except:

def clear_view(self):
    try:
        self.path.clear()
    except AttributeError as e:
        self.path = QPainterPath()
    self.item.setPath(self.path)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241