2 Zeichenfelder (Canvas) in einem `QMainWindow`

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Hallo

Für mein neustes Projekt würde ich gerne 2 verschiedene `QWidget` (aktuell `QLabel`), die ich mit dem Qt Designer in einem `QMainWindow` platziert habe, zu jeweils einem eigenen Zeichenfeld (Canvas) machen, in denen man mit gedrückter Maustaste Linien zeichnen kann.

Mit der Hilfe von Beispielcode konnte ich schon einen Prototypen mit einem Zeichenfeld bauen. In diesem Prototypen wird aber leider nicht an der aktuellen Position des Mauszeigers gezeichnet, sondern darunter. Ich vermute das Problem bei der Größenangabe der `QPixmap(400, 300)`.

Ist meine Vermutung richtig und wie kann man das Problem lösen?

Mit dem Code für das zweite Zeichenfeld (unten auskommentiert) funktioniert dann leider gar nichts mehr. Es erscheinen folgende Fehlermeldungen, wenn man versucht etwas zu zeichnen:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted
Traceback (most recent call last):
  File "/home/ata/source/paint/main.py", line 34, in mouseMoveEvent
    painter_left = QPainter(self.label_left.pixmap())
RuntimeError: wrapped C/C++ object of type QLabel has been deleted

main.py

Code: Alles auswählen

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtGui import QPixmap, QPainter
from PyQt5.QtCore import Qt
from PyQt5.uic import loadUi


class MainWindow(QMainWindow):
    # Based on
    # https://www.learnpyqt.com/courses/custom-widgets/bitmap-graphics/ and
    # https://codeloop.org/python-gui-how-to-create-paint-application-in-pyqt5/

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

        canvas_left = QPixmap(400, 300)
        canvas_left.fill(Qt.white)
        self.label_left.setPixmap(canvas_left)
        self.setCentralWidget(self.label_left)

        # canvas_right = QPixmap(400, 300)
        # canvas_right.fill(Qt.white)
        # self.label_right.setPixmap(canvas_right)
        # self.setCentralWidget(self.label_right)

        self.last_x, self.last_y = None, None

    def mouseMoveEvent(self, e):
        if self.last_x is None: # First event.
            self.last_x = e.x()
            self.last_y = e.y()

        painter_left = QPainter(self.label_left.pixmap())
        painter_left.drawLine(self.last_x, self.last_y, e.x(), e.y())
        painter_left.end()

        # painter_right = QPainter(self.label_right.pixmap())
        # painter_right.drawLine(self.last_x, self.last_y, e.x(), e.y())
        # painter_right.end()

        self.update()

        # Update the origin for next time.
        self.last_x = e.x()
        self.last_y = e.y()

    def mouseReleaseEvent(self, e):
        self.last_x = None
        self.last_y = None


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


if __name__ == "__main__":
    main()
mainwindow.ui

Code: Alles auswählen

<?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>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <widget class="QLabel" name="label_left">
      <property name="text">
       <string>TextLabel</string>
      </property>
     </widget>
    </item>
    <item row="0" column="1">
     <widget class="Line" name="line">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
     </widget>
    </item>
    <item row="0" column="2">
     <widget class="QLabel" name="label_right">
      <property name="text">
       <string>TextLabel</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>

Gruß
Atalanttore
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Den ursprünglichen Ansatz mit `QPixmap()` im `QLabel` habe ich verworfen und zu meiner ersten Idee mit `QGraphicsView` zurückgekehrt. Dazu fand ich ein interessantes Codebeispiel.

Das Codebeispiel habe ich zur Einbindung meiner in Qt Designer erstellten `mainwindow.ui` mit mehreren `QGraphicsView` angepasst. Als erstes wollte ich das Zeichnen im oberen linken `QGraphicsView` (`graphicsView_top_left`) ermöglichen. Leider tut sich weder im `QGraphicsView` noch in der Konsole etwas, wenn man mit gedrückter Maustaste im `graphicsView_top_left` umher fährt.

Was habe ich falsch gemacht?

main.py:

Code: Alles auswählen

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


class MainWindow(QMainWindow, QGraphicsView):
    start = None
    end = None
    item = None
    path = None

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

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

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

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


class GraphicsPathItem(QGraphicsPathItem):
    def __init__(self):
        super(GraphicsPathItem, self).__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_()


if __name__ == "__main__":
    main()

mainwindow.ui:

Code: Alles auswählen

<?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>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="2" column="0">
     <widget class="QGraphicsView" name="graphicsView_bottom_left"/>
    </item>
    <item row="0" column="0">
     <widget class="QGraphicsView" name="graphicsView_top_left"/>
    </item>
    <item row="1" column="1">
     <widget class="QPushButton" name="pushButton_right">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="1" column="0">
     <widget class="QPushButton" name="pushButton_left">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="2" column="1">
     <widget class="QGraphicsView" name="graphicsView_bottom_right"/>
    </item>
    <item row="0" column="1">
     <widget class="QGraphicsView" name="graphicsView_top_right"/>
    </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>
Gruß
Atalanttore
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Die `QGraphicsView` habe ich nun im Qt Designer durch `QVBoxLayout` ersetzt und die 4 `GraphicsView` in der `__init__()` Methode des `MainWindow` mit Python-Code erstellt.

Mit den Änderungen erscheinen tatsächlich 4 voneinander unabhängige Felder zum Zeichnen mit der Maus.

Ein kleines Problem besteht aber noch: Zeichnet man mit dem Mauszeiger über ein `GraphicsView` hinaus, erscheinen zwar Scrollbalken, aber das Bild folgt nur noch für einem kurzen Moment dem Mauszeiger und bleibt dann fest stehen. Auch bei Bewegungen in die andere Richtung bleibt das Bild fest stehen.

Wie folgt das `GraphicsView` dauerhaft dem zeichnenden Mauszeiger?

main.py

Code: Alles auswählen

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


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

        self.verticalLayout_top_left.addWidget(GraphicsView())
        self.verticalLayout_top_right.addWidget(GraphicsView())
        self.verticalLayout_bottom_left.addWidget(GraphicsView())
        self.verticalLayout_bottom_right.addWidget(GraphicsView())


class GraphicsView(QGraphicsView):

    start = None
    end = None
    item = None
    path = None

    def __init__(self):
        super(GraphicsView, self).__init__()
        self.setScene(QGraphicsScene())
        self.path = QPainterPath()
        self.item = GraphicsPathItem()
        self.scene().addItem(self.item)

    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(GraphicsPathItem, self).__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_()


if __name__ == "__main__":
    main()

mainwindow.ui

Code: Alles auswählen

<?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>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="1" column="0">
     <widget class="QPushButton" name="pushButton_left">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <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="1" column="1">
     <widget class="QPushButton" name="pushButton_right">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </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>
   </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>
Gruß
Atalanttore
Antworten