Portierung eines Beispielprogramms von Tkinter nach PyQt5

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

Hallo

Die Portierung meines Beispielprogramms von Tkinter nach PyQt5 schreitet voran.

1. Mittlerweile bin ich beim zeitverzögerten Aufruf der Methode `change_screen_color()` zum Ändern der Bildschirmhintergrundfarbe angekommen. Hier kommt es nun zu einem Problem, wo ich auch mit meiner Standardsuchmaschine keine Lösung finden konnte. Die Fehlermeldung ist folgende:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/ata/source/Farbkonzentrationstest/main-qt.py", line 88, in show_concentration_test_window
    self.change_screen_color()
  File "/home/ata/source/Farbkonzentrationstest/main-qt.py", line 114, in change_screen_color
    timer.timeout.connect(self.change_screen_color)
AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'
2. Außerdem meldet PyCharm an mehreren Stellen folgendes Problem:

Code: Alles auswählen

Unresolved attribute reference 'connect' for class 'pyqtBoundSignal'
main-qt.py:

Code: Alles auswählen

import sys
from random import choice
#from statistics import mean

from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore



start_color = QtCore.Qt.black
designated_color = QtCore.Qt.red
switch_time = 1000
colors = [QtCore.Qt.black, QtCore.Qt.blue, QtCore.Qt.cyan, QtCore.Qt.green, QtCore.Qt.magenta, QtCore.Qt.red, QtCore.Qt.yellow]



class MainWindow(QMainWindow):

    switch_window = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.pushButton_start_test.clicked.connect(self.switch)

    def switch(self):
        self.switch_window.emit()


class ConcentrationTestWindow(QMainWindow):

    switch_window = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.showFullScreen()

        self.pal = self.palette()
        self.setAutoFillBackground(True)
        self.pal.setColor(self.backgroundRole(), start_color)
        self.setPalette(self.pal)

    def switch(self):
        self.switch_window.emit()


class ResultsWindow(QDialog):

    switch_window = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("resultswindow.ui", self)
        self.pushButton_OK.clicked.connect(self.switch)

    def switch(self):
        self.switch_window.emit()



class Controller:

    def __init__(self):
        self.colors = colors
        self.color = None
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0
        self.stop_test = False
        self.color_locked = False

    def show_main_window(self):
        self.main_window = MainWindow()
        self.main_window.switch_window.connect(self.show_concentration_test_window) # IDE: Unresolved attribute reference 'connect' for class 'pyqtBoundSignal'
        self.main_window.show()

    def show_results_window(self):
        self.results_window = ResultsWindow()
        self.results_window.switch_window.connect(self.results_window.reject) # IDE: Unresolved attribute reference 'connect' for class 'pyqtBoundSignal'
        self.results_window.show()

    def show_concentration_test_window(self):
        self.concentration_test_window = ConcentrationTestWindow()
        self.concentration_test_window.switch_window.connect(self.concentration_test_window.close) # IDE: Unresolved attribute reference 'connect' for class 'pyqtBoundSignal'
        self.concentration_test_window.show()
        self.change_screen_color()

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.concentration_test_window.switch()


    def change_screen_color(self, color_locked=True):
        # if self.stop_test:
        #     self.concentration_test_window.switch()

        if not self.color_locked and self.color == designated_color:
            self.color_missed += 1

        self.color_locked = color_locked
        current_color = self.color

        while self.color == current_color:
            self.color = choice(self.colors)

        self.concentration_test_window.pal.setColor(self.concentration_test_window.backgroundRole(), self.color)
        self.concentration_test_window.setPalette(self.concentration_test_window.pal)

        #self.start_time = time.monotonic()

        timer = QtCore.QTimer
        timer.timeout.connect(self.change_screen_color) # Zeile 114 ist hier; IDE: Unresolved attribute reference 'connect' for class 'pyqtBoundSignal'
        timer.start(switch_time)


def main():
    app = QApplication(sys.argv)
    controller = Controller()
    controller.show_main_window()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
mainwindow.ui: (Logik dafür fehlt noch in main-qt.py):

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>634</width>
    <height>552</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Farbkonzentrationstest</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout_2">
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
       <spacer name="verticalSpacer_2">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <layout class="QGridLayout" name="gridLayout">
        <item row="0" column="0">
         <widget class="QLabel" name="label_start_color">
          <property name="text">
           <string>Startfarbe:</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
         </widget>
        </item>
        <item row="0" column="1">
         <widget class="QComboBox" name="comboBox_start_color"/>
        </item>
        <item row="1" column="0">
         <widget class="QLabel" name="label_designated_color">
          <property name="text">
           <string>Zielfarbe:</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
         </widget>
        </item>
        <item row="1" column="1">
         <widget class="QComboBox" name="comboBox_designated_color">
          <property name="currentText">
           <string/>
          </property>
         </widget>
        </item>
        <item row="2" column="0">
         <widget class="QLabel" name="label_switch_time">
          <property name="text">
           <string>Umschaltzeit (ms):</string>
          </property>
          <property name="alignment">
           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
          </property>
         </widget>
        </item>
        <item row="2" column="1">
         <widget class="QSpinBox" name="spinBox_switch_time">
          <property name="maximum">
           <number>100000</number>
          </property>
          <property name="value">
           <number>1000</number>
          </property>
         </widget>
        </item>
       </layout>
      </item>
      <item>
       <spacer name="verticalSpacer">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
          <width>20</width>
          <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton_start_test">
        <property name="layoutDirection">
         <enum>Qt::LeftToRight</enum>
        </property>
        <property name="text">
         <string>Test starten</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>634</width>
     <height>28</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
resultswindow.ui (Logik dafür fehlt noch in main-qt.py):

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog_results</class>
 <widget class="QDialog" name="Dialog_results">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Ergebnisse</string>
  </property>
  <layout class="QGridLayout" name="gridLayout_2">
   <item row="0" column="0">
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
      <layout class="QGridLayout" name="gridLayout">
       <item row="0" column="1">
        <widget class="QLabel" name="label_result_too_often">
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item row="1" column="0">
        <widget class="QLabel" name="label_color_missed">
         <property name="text">
          <string>Bei richtiger Farbe nicht gedrückt:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="3" column="0">
        <widget class="QLabel" name="label_mean_reaction_time">
         <property name="text">
          <string>Gemittelte Reaktionszeit:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="3" column="1">
        <widget class="QLabel" name="label_result_mean_reaction_time">
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item row="4" column="0">
        <widget class="QLabel" name="label_reaction_times">
         <property name="text">
          <string>Reaktionszeit pro richtig gedrückter Farbe:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="2" column="1">
        <widget class="QLabel" name="label_result_false_positives">
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item row="1" column="1">
        <widget class="QLabel" name="label_result_color_missed">
         <property name="text">
          <string/>
         </property>
        </widget>
       </item>
       <item row="2" column="0">
        <widget class="QLabel" name="label_false_positives">
         <property name="text">
          <string>Bei falscher Farbe gedrückt:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="0" column="0">
        <widget class="QLabel" name="label_too_often">
         <property name="text">
          <string>Bei richtiger Farbe zu oft gedrückt:</string>
         </property>
         <property name="alignment">
          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
         </property>
        </widget>
       </item>
       <item row="4" column="1">
        <widget class="QListView" name="listView_result_reaction_times"/>
       </item>
      </layout>
     </item>
     <item>
      <spacer name="verticalSpacer">
       <property name="orientation">
        <enum>Qt::Vertical</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>20</width>
         <height>40</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <widget class="QPushButton" name="pushButton_OK">
       <property name="text">
        <string>OK</string>
       </property>
      </widget>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>
Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`timer` ist an die *Klasse* `QtCore.Qtimer` gebunden. Du möchtest da aber ein Exemplar haben, also in die Klasse auch *aufrufen*. Dann gibt's auch ein `connect()`.

Ich weiss es war total meine Schuld das ich das einfach ausgeführt habe aber Beispielcode der einfach mal ein komplett schwarzes Fenster als Vollbildschirm öffnet, ohne das man eine direkte Möglichkeit präsentiert bekommt das auch wieder zu schliessen ist etwas überraschend und nervig.

Edit: Konstanten werden per Konvention immer noch KOMPLETT_GROSS geschrieben. Und schau Dir mal die Konvention bei Qt-Signalen an. Das sollte also eher `window_switched` heissen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Danke für die Mühe, dass du meinen stetig größer werdenden Code immer noch durchschaust.

Ich habe mein Beispielprogramm nun noch mehr Richtung MVC umgebaut. Die .ui-Dateien habe ich lediglich in das neue Verzeichnis `views` verschoben und ansonsten nicht verändert.

1. Achtung: Der Abbruch des Vollbildfensters mit der ESC-Taste funktioniert leider immer noch nicht. Woran liegt das?

2. Anstatt einem RGB-Wert liefern die QColor-Objekte nur einen Integer-Wert zurück (siehe Doku). Muss man für jede Farbe einen eigenen RGB-Wert definieren, obwohl es bereits vordefinierte Farben gibt?

main.py:

Code: Alles auswählen

import sys
from PyQt5.QtWidgets import QApplication
from controller import Controller

class Main(QApplication):
    def __init__(self, *args):
        super().__init__(*args)
        self.controller = Controller()
        self.controller.show_main_window()

if __name__ == '__main__':
    app = Main(sys.argv)
    sys.exit(app.exec_())

controller.py:

Code: Alles auswählen

from random import choice
#from statistics import mean

from PyQt5 import QtCore

from views import windows
from model import Data

class Controller():
    def __init__(self, *args, **kwargs):
        super().__init__()

        self.data = Data()
        self.windows = windows

    def show_main_window(self):
        self.main_window = self.windows.MainWindow()
        self.main_window.window_switched.connect(self.show_concentration_test_window)
        self.main_window.show()

    def show_results_window(self):
        self.results_window = self.windows.ResultsWindow()
        self.results_window.window_switched.connect(self.results_window.reject)
        self.results_window.show()

    def show_concentration_test_window(self):
        self.concentration_test_window = self.windows.ConcentrationTestWindow(self.data.start_color)
        self.concentration_test_window.window_switched.connect(self.concentration_test_window.close) #4
        self.concentration_test_window.show()
        self.change_screen_color()

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape: #1
            self.concentration_test_window.switch() #2


    def change_screen_color(self, color_locked=True):
        # if self.stop_test:
        #     self.concentration_test_window.switch()

        if not self.data.color_locked and self.data.color == self.data.designated_color:
            self.data.color_missed += 1

        self.data.color_locked = color_locked
        current_color = self.data.color

        while self.data.color == current_color:
            self.data.color = choice(self.data.COLORS)

        self.concentration_test_window.pal.setColor(self.concentration_test_window.backgroundRole(), self.data.color)
        self.concentration_test_window.setPalette(self.concentration_test_window.pal)

        #self.start_time = time.monotonic()

        timer = QtCore.QTimer()
        timer.timeout.connect(self.change_screen_color)
        timer.start(self.data.switch_time)
model.py:

Code: Alles auswählen

import sys
from PyQt5.QtCore import Qt


class Data():
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.COLORS = [Qt.black,
                       Qt.blue,
                       Qt.cyan,
                       Qt.green,
                       Qt.magenta,
                       Qt.red,
                       Qt.yellow]

        self.start_color = Qt.black # In GUI auswählbar machen
        self.designated_color = Qt.red # In GUI auswählbar machen
        self.switch_time = 1000 # In GUI auswählbar machen

        self.color = None
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0
        self.stop_test = False
        self.color_locked = False

views/windows.py (Logik noch ziemlich unvollständig):

Code: Alles auswählen

from PyQt5.QtWidgets import QMainWindow, QDialog
from PyQt5.QtCore import pyqtSignal
from PyQt5.uic import loadUi

class MainWindow(QMainWindow):

    window_switched = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("views/mainwindow.ui", self)
        self.pushButton_start_test.clicked.connect(self.switch)

    def switch(self):
        self.window_switched.emit()

class ConcentrationTestWindow(QMainWindow):

    window_switched = pyqtSignal()

    def __init__(self, color, parent=None):
        super().__init__(parent)
        self.showFullScreen()
        self.pal = self.palette()
        self.setAutoFillBackground(True)
        self.pal.setColor(self.backgroundRole(), color)
        self.setPalette(self.pal)

    def switch(self):
        self.window_switched.emit() #3


class ResultsWindow(QDialog):

    window_switched = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("views/resultswindow.ui", self)
        self.pushButton_OK.clicked.connect(self.switch)

    def switch(self):
        self.window_switched.emit()
Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Ach Du meine Güte. MVC. Der Anfang vom Ende. Ich bin raus hier. Ich fand das ja in der letzten Iteration schon zu unsinnig aufgeblasen, jetzt wird es langsam aberwitzig. Vielleicht möchtest Du lieber in Java programmieren. ;-)

Bei 2. verstehe ich die Frage nicht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Atalanttore: ein schönes Beispiel, warum MVC meist falsch umgesetzt wird. Main und Controller sind als Klassen völlig unnötig, eine Klasse `Data` ohne Methoden zumindest unschön.
Was ist der Sinn in Controller.__init__ ein Modul an ein Attribut zu binden? `change_screen_color` gehört entweder in die Klasse `Data`, weil es nur mit Attributen aus Data arbeitet, oder aber in die Klasse `ConcentrationTestWindow` weil es dessen Methoden aufruft. Am besten kombinierst Du `Data` und `ConcentrationTestWindow` zu einer Klasse, dann ist nämlich klar, was zusammengehört.

Dein Programm hat eine Größe, die das Aufteilen auf mehr als eine Datei nur unübersichtlicher macht.
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Java ist mir für den Einstieg dann doch zu umständlich.

Zu 2.: Nach dem Umbau des Codes habe ich das Problem jetzt wahrscheinlich selbst gefunden. Die Hintergrundfarbe wird eigentlich in der Methode `change_screen_color()` festgelegt, weil sie ein paar Zeilen nach der Instanziierung eines `ConcentrationTestWindow()`-Objekts aufgerufen wird.


@Sirius3 + __blackjack__: Den Python-Code habe ich nun wieder in einer Datei zusammengefasst. Die .ui-Dateien liegen wieder im gleichen Verzeichnis. Der Abbruch des Vollbildfensters mit der ESC-Taste funktioniert mittlerweile auch, aber der Farbwechsel in der Methode `change_screen_color()` weiterhin nicht. Woran liegt das?

Der Code ist momentan wie folgt:

Code: Alles auswählen

import sys
from random import choice
#from statistics import mean

from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore



start_color = QtCore.Qt.black # In GUI auswählbar machen
designated_color = QtCore.Qt.red # In GUI auswählbar machen
switch_time = 1000 # In GUI auswählbar machen
COLORS = [QtCore.Qt.black,
          QtCore.Qt.blue,
          QtCore.Qt.cyan,
          QtCore.Qt.green,
          QtCore.Qt.magenta,
          QtCore.Qt.red,
          QtCore.Qt.yellow]



class MainWindow(QMainWindow):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.pushButton_start_test.clicked.connect(self.switch)

    def switch(self):
        self.window_switched.emit()


class ConcentrationTestWindow(QMainWindow):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.showFullScreen()

        self.colors = COLORS
        self.color = None
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0
        self.stop_test = False
        self.color_locked = False

        self.pal = self.palette()
        self.setAutoFillBackground(True)
        self.pal.setColor(self.backgroundRole(), start_color)
        self.setPalette(self.pal)


    def change_screen_color(self, color_locked=True):
        # if self.stop_test:
        #     self.concentration_test_window.switch()

        if not self.color_locked and self.color == designated_color:
            self.concolor_missed += 1

        self.color_locked = color_locked
        current_color = self.color

        while self.color == current_color:
            self.color = choice(self.colors)

        self.pal.setColor(self.backgroundRole(), self.color)
        self.setPalette(self.pal)

        #self.start_time = time.monotonic()

        timer = QtCore.QTimer()
        timer.timeout.connect(self.change_screen_color)
        timer.start(switch_time)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.switch()

    def switch(self):
        self.window_switched.emit()


class ResultsWindow(QDialog):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("resultswindow.ui", self)
        self.pushButton_OK.clicked.connect(self.switch)

    def switch(self):
        self.window_switched.emit()



class Controller:

    def __init__(self):
        pass

    def show_main_window(self):
        self.main_window = MainWindow()
        self.main_window.window_switched.connect(self.show_concentration_test_window)
        self.main_window.show()

    def show_results_window(self):
        self.results_window = ResultsWindow()
        self.results_window.window_switched.connect(self.results_window.reject)
        self.results_window.show()

    def show_concentration_test_window(self):
        self.concentration_test_window = ConcentrationTestWindow()
        self.concentration_test_window.window_switched.connect(self.concentration_test_window.close)
        self.concentration_test_window.show()
        self.concentration_test_window.change_screen_color()



def main():
    app = QApplication(sys.argv)
    controller = Controller()
    controller.show_main_window()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
Gruß
Atalanttore
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es könnte sein, dass dein QTimer das Problem darstellt. Da darunter ein C++-Objekt liegt, kann es sein, dass dein Timer sofort wider stirbt, da du dir keine Referenz auf ihn merkst. Binde den mal an self, und schau, ob das die Situation verbessert.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Alternativ könnte man dem `QTimer` beim erstellen wahrscheinlich auch `self` übergeben, damit es ein Qt-Elternobjekt hat und solange lebt wie das lebt.

Du gehst/gingst mit Deinem Code aber sehr in Richtung (unnötige) Komplexität die bei Java nicht unüblich ist. GUI und Logik zu trennen ist sinnvoll, aber einfach so MVC machen zu wollen nur weil man MVC machen will, ist nicht sinnvoll. Ich finde die `Controller`-Klasse auch jetzt immer noch unsinnig. Die hat ja gar keinen Zustand. Und die `window_switched`-Signale die von einer `switch()`-Methode ausgelöst werden die gar nichts umschaltet sondern nur dieses Signal auslöst ist auch sehr verwirrend.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

__deets__ hat geschrieben: Montag 12. November 2018, 17:01 Es könnte sein, dass dein QTimer das Problem darstellt. Da darunter ein C++-Objekt liegt, kann es sein, dass dein Timer sofort wider stirbt, da du dir keine Referenz auf ihn merkst. Binde den mal an self, und schau, ob das die Situation verbessert.
__blackjack__ hat geschrieben: Montag 12. November 2018, 17:43 @Atalanttore: Alternativ könnte man dem `QTimer` beim erstellen wahrscheinlich auch `self` übergeben, damit es ein Qt-Elternobjekt hat und solange lebt wie das lebt.
Bingo! Beide Lösungsvorschläge funktionieren. Welche ist jetzt die bessere/elegantere Lösung?



@__blackjack__: Meinst du mit Zustand den Zustand eines endlichen Automaten?

Sowohl die `Controller`-Klasse als auch die `switch()`-Methode habe ich von dem Beispiel hier übernommen.

Gruß
Atalanttore
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich bevorzuge meine (Überraschung ;) ) weil du dadurch gleich das Problem den timer wieder rekonfigurieren zu wollen löst. Ich würde ihn dann auch nur einmal erzeugen.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Mit Zustand bei Klassen sind normalerweise Daten gemeint. Klassen fassen Daten und Funktionen die darauf operieren zu Objekten zusammen. Wenn es eines von beiden nicht gibt, dann ist das aus OOP-Sicht keine Klasse. Wobei mir gerade auffällt das da ja doch Daten sind, aber die werden nicht in der `__init__()` erstellt/initialisiert, sondern in den Methoden. Und jede Methode hat ihr eigenes Datum und keine verwendet etwas von den anderen – das gehört ganz sicher alles so nicht in eine Klasse. Und die Signale/`switch()`-Methode ist komisch. Das Beispiel würde ich nicht als Vorlage für irgendetwas verwenden.

Edit: Okay, die Methoden benutzen doch Attribute die in anderen Methoden eingeführt wurden. Das ist aber immer noch ein sehr komisches Konstrukt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__deets__: Bis auf folgende Anweisung findet sich nun kein Code mehr zum Timer in der Methode `change_screen_color()`.

Code: Alles auswählen

self.timer.start(switch_time)

@__blackjack__:
1. Bedeutet dass, dass eine Klasse mit folgendem Muster (leere `__init__`-Methode) aus OOP-Sicht niemals eine Klasse sein kann?

Code: Alles auswählen

class Test:

    def __init__(self):
        pass
        
    def mache_etwas(self):
    	<beliebiger Code>
        
2. Wo ist im Beispielprogramm der beste Platz für Daten, auf die sowohl in einem `MainWindow`-Objekt als auch in einem `ConcentrationTestWindow()`-Objekt zugegriffen wird?

Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Man kann die Klasse ja einfach durch folgendes ersetzen:

Code: Alles auswählen

def mache_etwas():
    <beliebiger Code>
Also macht die Klasse als Klasse keinen Sinn.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: Danke für die Antwort. Speichert man Daten, auf die verschiedene Objekte zugreifen, einfach als globale Variablen?

Gruß
Atalanttore
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Atalanttore: Nein‽ Man übergibt die.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@__blackjack__: In meinem Beispielprogramm müsste ich demnach die Werte vom Objekt `main_window` an die Funktion `show_concentration_test_window` übergeben, die sie ihrerseits an die `__init__`-Methode des Objekts `concentration_test_window` weiterleitet. Hast du das mit Übergeben gemeint?

Aktueller, aber nicht lauffähiger Code:

Code: Alles auswählen

import sys, time
from random import choice
#from statistics import mean

from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore


class MainWindow(QMainWindow):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self.COLORS = {"Schwarz": QtCore.Qt.black,
                  "Blau": QtCore.Qt.blue,
                  "Cyan": QtCore.Qt.cyan,
                  "Grün": QtCore.Qt.green,
                  "Magenta": QtCore.Qt.magenta,
                  "Rot": QtCore.Qt.red,
                  "Gelb": QtCore.Qt.yellow} # 1

        self.designated_color = QtCore.Qt.red  # 2
        self.switch_time = 1000  # 3

        loadUi("mainwindow.ui", self)
        self.pushButton_start_test.clicked.connect(self.window_switched.emit)
        self.comboBox_designated_color.addItems(self.COLORS)


class ConcentrationTestWindow(QMainWindow):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, COLORS, designated_color, switch_time, parent=None):
        super().__init__(parent)
        self.showFullScreen()

        self.COLORS = COLORS
        self.designated_color = designated_color
        self.switch_time = switch_time

        self.color = None
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0
        self.stop_test = False
        self.color_locked = False
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.change_screen_color)

        self.pal = self.palette()
        self.setAutoFillBackground(True)



    def change_screen_color(self, color_locked=True):
        if self.stop_test:
            self.window_switched.emit()

        if not self.color_locked and self.color == self.designated_color:
            self.color_missed += 1

        self.color_locked = color_locked
        current_color = self.color

        while self.color == current_color:
            self.color = choice(list(self.colors.values()))

        self.pal.setColor(self.backgroundRole(), self.color)
        self.setPalette(self.pal)
        self.start_time = time.monotonic()  # Ereignis Timer

        self.timer.start(self.switch_time)


    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.window_switched.emit()

        if event.key() == QtCore.Qt.Key_Space:
            if self.color == self.designated_color:
                if self.color_locked:
                    self.key_too_often_pressed += 1
                else:
                    reaction_time = time.monotonic() - self.start_time
                    self.reaction_times.append(reaction_time)
                    self.color_locked = True
            else:
                self.false_positives += 1


class ResultsWindow(QDialog):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("resultswindow.ui", self)
        self.stop_test = True
        self.pushButton_OK.clicked.connect(self.window_switched.emit)




def show_concentration_test_window():
    concentration_test_window = ConcentrationTestWindow()
    concentration_test_window.window_switched.connect(show_results_window)
    concentration_test_window.show()
    concentration_test_window.change_screen_color()

def show_results_window():
    results_window = ResultsWindow()
    concentration_test_window.close()
    results_window.window_switched.connect(results_window.reject)
    results_window.show()



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

    main_window = MainWindow()
    main_window.window_switched.connect(show_concentration_test_window)
    main_window.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
Gruß
Atalanttore
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

Mit dem mehrmaligen Wert übergeben und Rückgabewert hatte ich keine Freude. Die Konstanten zu `COLORS` und weitere Attribute habe ich nun kurzerhand zur Klasse `Controller` hinzugefügt und sie damit zu einer aus OOP-Sicht richtigen Klasse gemacht.

Irgendwie müsste ich die folgende Anweisung nun erweitern/umbauen/ersetzen, um die Werte aus `comboBox_designated_color` und `spinBox_switch_time` an den `controller` übergeben zu können, der damit ein Objekt der Klasse `ConcentrationTestWindow` instanziiert.

Code: Alles auswählen

self.pushButton_start_test.clicked.connect(self.window_switched.emit)
Meine Versuche mit Lambda-Ausdrücken sind leider alle gescheitert. Unter anderem habe ich folgendes probiert und einen SyntaxError ausgelöst:

Code: Alles auswählen

        self.pushButton_start_test.clicked.connect(lambda self.get_designated_color(), self.get_switch_time(): self.window_switched.emit)

Aktueller Code:

Code: Alles auswählen

import sys, time
from random import choice
#from statistics import mean

from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore



class MainWindow(QMainWindow):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, COLORS, parent=None):
        super().__init__(parent)
        self.COLORS = COLORS

        loadUi("mainwindow.ui", self)
        self.comboBox_designated_color.addItems(self.COLORS)

        self.pushButton_start_test.clicked.connect(self.window_switched.emit)

    def get_designated_color(self):
        return self.COLORS[self.comboBox_designated_color.currentText()]

    def get_switch_time(self):
        return self.spinBox_switch_time.value()


class ConcentrationTestWindow(QMainWindow):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, COLORS, designated_color, switch_time, parent=None):
        super().__init__(parent)
        self.showFullScreen()

        self.colors = COLORS
        self.designated_color = designated_color
        self.switch_time = switch_time

        self.color = None
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0
        self.stop_test = False
        self.color_locked = False
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.change_screen_color)

        self.pal = self.palette()
        self.setAutoFillBackground(True)



    def change_screen_color(self, color_locked=True):
        if self.stop_test:
            self.window_switched.emit()

        if not self.color_locked and self.color == self.designated_color:
            self.color_missed += 1

        self.color_locked = color_locked
        current_color = self.color

        while self.color == current_color:
            self.color = choice(list(self.colors.values()))

        self.pal.setColor(self.backgroundRole(), self.color)
        self.setPalette(self.pal)
        self.start_time = time.monotonic()  # Ereignis Timer

        self.timer.start(self.switch_time)


    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.window_switched.emit()

        if event.key() == QtCore.Qt.Key_Space:
            if self.color == self.designated_color:
                if self.color_locked:
                    self.key_too_often_pressed += 1
                else:
                    reaction_time = time.monotonic() - self.start_time
                    self.reaction_times.append(reaction_time)
                    self.color_locked = True
            else:
                self.false_positives += 1


class ResultsWindow(QDialog):

    window_switched = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        loadUi("resultswindow.ui", self)
        self.stop_test = True
        self.pushButton_OK.clicked.connect(self.window_switched.emit)



class Controller:

    def __init__(self):
        self.COLORS = {"Schwarz": QtCore.Qt.black,
                       "Blau": QtCore.Qt.blue,
                       "Cyan": QtCore.Qt.cyan,
                       "Grün": QtCore.Qt.green,
                       "Magenta": QtCore.Qt.magenta,
                       "Rot": QtCore.Qt.red,
                       "Gelb": QtCore.Qt.yellow}

        self.designated_color = QtCore.Qt.green # Nur zu Testzwecken bis das Auslesen aus dem Widget funktioniert.
        self.switch_time = 500 # Nur zu Testzwecken bis das Auslesen aus dem Widget funktioniert.

    def show_main_window(self):
        self.main_window = MainWindow(self.COLORS)
        self.main_window.window_switched.connect(self.show_concentration_test_window)
        self.main_window.show()

    def show_concentration_test_window(self):
        self.concentration_test_window = ConcentrationTestWindow(self.COLORS, self.designated_color, self.switch_time)
        self.concentration_test_window.window_switched.connect(self.show_results_window)
        self.concentration_test_window.show()
        self.concentration_test_window.change_screen_color()

    def show_results_window(self):
        self.results_window = ResultsWindow()
        self.concentration_test_window.close()
        self.results_window.window_switched.connect(self.results_window.reject)
        self.results_window.show()



def main():
    app = QApplication(sys.argv)
    controller = Controller()
    controller.show_main_window()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
Gruß
Atalanttore
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was hast du denn erreichen wollen mit

Code: Alles auswählen

        self.pushButton_start_test.clicked.connect(lambda self.get_designated_color(), self.get_switch_time(): self.window_switched.emit)
?

lambdas sind syntaktischer Zucker fuer

Code: Alles auswählen

lamdba argliste: expr <==>
def f(argliste):
        return expr
Wenn du diese Transformation also auf deinen oben gezeigte Lamda-Ausdruck anwendest - was kommt dabei rum, und warum ist das nicht richtig?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Das mit dem Controller hat immer noch keinen Mehrwert, auch wenn Du ein Attribut fälschlicherweise da reinschiebst. Was sinn macht, ist das M von MVC, völlig ungetestet:

Code: Alles auswählen

import sys
import time
from random import choice

from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore

class ConcentrationTest(object):
    def __init__(self, colors, designated_color, switch_time):
        self.colors = colors
        self.designated_color = designated_color
        self.switch_time = switch_time
        self.start_time = None
        self.color = None
        self.color_locked = True
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0

    def space_pressed(self):
        if self.color == self.designated_color:
            if self.color_locked:
                self.key_too_often_pressed += 1
            else:
                reaction_time = time.monotonic() - self.start_time
                self.reaction_times.append(reaction_time)
                self.color_locked = True
        else:
            self.false_positives += 1

    def next_round(self):
        if not self.color_locked and self.color == self.designated_color:
            self.color_missed += 1
        self.color_locked = False
        current_color = self.color
        while self.color == current_color:
            self.color = choice(list(self.colors.values()))
        self.start_time = time.monotonic()  # Ereignis Timer
        return self.color


class MainWindow(QMainWindow):
    COLORS = {"Schwarz": QtCore.Qt.black,
        "Blau": QtCore.Qt.blue,
        "Cyan": QtCore.Qt.cyan,
        "Grün": QtCore.Qt.green,
        "Magenta": QtCore.Qt.magenta,
        "Rot": QtCore.Qt.red,
        "Gelb": QtCore.Qt.yellow}

    def __init__(self, concentration_test, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.concentration_test = concentration_test
        self.comboBox_designated_color.addItems(self.COLORS)
        self.pushButton_start_test.clicked.connect(self.show_concentration_test_window)

    def get_designated_color(self):
        return self.COLORS[self.comboBox_designated_color.currentText()]

    def get_switch_time(self):
        return self.spinBox_switch_time.value()

    def show_concentration_test_window(self):
        concentration_test = ConcentrationTest(self.colors, self.get_designated_color(), self.get_switch_time())
        concentration_test_window = ConcentrationTestWindow(concentration_test)
        concentration_test_window.show()
        concentration_test_window.change_screen_color()


class ConcentrationTestWindow(QMainWindow):
    def __init__(self, concentration_test, parent=None):
        super().__init__(parent)
        self.showFullScreen()
        self.concentration_test = concentration_test
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.change_screen_color)
        self.pal = self.palette()
        self.setAutoFillBackground(True)

    def change_screen_color(self):
        color = self.concentration_test.next_round()
        self.pal.setColor(self.backgroundRole(), color)
        self.setPalette(self.pal)
        self.timer.start(self.switch_time)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.show_result()

        if event.key() == QtCore.Qt.Key_Space:
            self.concentration_test.space_pressed()

    def show_result(self)
        self.close()
        ResultsWindow(self.concentration_test).show()


class ResultsWindow(QDialog):
    def __init__(self, concentration_test, parent=None):
        super().__init__(parent)
        loadUi("resultswindow.ui", self)
        self.concentration_test = concentration_test
        # TODO: show results?
        self.pushButton_OK.clicked.connect(self.reject)


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


if __name__ == "__main__":
    main()
Atalanttore
User
Beiträge: 407
Registriert: Freitag 6. August 2010, 17:03

@Sirius3: Vielen Dank für deine Änderungen. Nach einem Klick auf "Test starten" im Hauptfenster erscheint nach deinen Änderungen am Code folgender Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/ata/Farbkonzentrationstest/main.py", line 67, in show_concentration_test_window
    concentration_test = ConcentrationTest(self.colors, self.get_designated_color(), self.get_switch_time())
AttributeError: 'MainWindow' object has no attribute 'colors'
Ich habe versucht den Fehler selbst zu beheben (sehr lehrreich). Eine Fehlermeldung kommt jetzt nicht mehr. Allerdings erscheint nach einem Klick auf "Test starten" auch kein Fenster. :roll:

Der Code sieht aktuell so aus:

Code: Alles auswählen

import sys
import time
from random import choice

from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.uic import loadUi
from PyQt5 import QtCore


class ConcentrationTest(object):
    def __init__(self, COLORS, designated_color):
        self.COLORS = COLORS
        self.designated_color = designated_color
        self.start_time = None
        self.color = None
        self.color_locked = True
        self.reaction_times = []
        self.false_positives = 0
        self.color_missed = 0
        self.key_too_often_pressed = 0

    def space_pressed(self):
        if self.color == self.designated_color:
            if self.color_locked:
                self.key_too_often_pressed += 1
            else:
                reaction_time = time.monotonic() - self.start_time
                self.reaction_times.append(reaction_time)
                self.color_locked = True
        else:
            self.false_positives += 1

    def next_round(self):
        if not self.color_locked and self.color == self.designated_color:
            self.color_missed += 1
        self.color_locked = False
        current_color = self.color
        while self.color == current_color:
            self.color = choice(list(self.COLORS.values()))
        self.start_time = time.monotonic()  # Ereignis Timer
        return self.color


class MainWindow(QMainWindow):
    COLORS = {"Schwarz": QtCore.Qt.black,
        "Blau": QtCore.Qt.blue,
        "Cyan": QtCore.Qt.cyan,
        "Grün": QtCore.Qt.green,
        "Magenta": QtCore.Qt.magenta,
        "Rot": QtCore.Qt.red,
        "Gelb": QtCore.Qt.yellow}

    def __init__(self, concentration_test, parent=None):
        super().__init__(parent)
        loadUi("mainwindow.ui", self)
        self.concentration_test = concentration_test
        self.comboBox_designated_color.addItems(self.COLORS)
        self.pushButton_start_test.clicked.connect(self.show_concentration_test_window)

    def get_designated_color(self):
        return self.COLORS[self.comboBox_designated_color.currentText()]

    def get_switch_time(self):
        return self.spinBox_switch_time.value()

    def show_concentration_test_window(self):
        concentration_test = ConcentrationTest(self.COLORS, self.get_designated_color())
        concentration_test_window = ConcentrationTestWindow(concentration_test, self.get_switch_time())
        concentration_test_window.show()
        concentration_test_window.change_screen_color()


class ConcentrationTestWindow(QMainWindow):
    def __init__(self, concentration_test, switch_time, parent=None):
        super().__init__(parent)
        self.showFullScreen()
        self.concentration_test = concentration_test
        self.switch_time = switch_time
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.change_screen_color)
        self.pal = self.palette()
        self.setAutoFillBackground(True)

    def change_screen_color(self):
        color = self.concentration_test.next_round()
        self.pal.setColor(self.backgroundRole(), color)
        self.setPalette(self.pal)
        self.timer.start(self.switch_time)

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Escape:
            self.show_result()

        if event.key() == QtCore.Qt.Key_Space:
            self.concentration_test.space_pressed()

    def show_result(self):
        self.close()
        ResultsWindow(self.concentration_test).show()


class ResultsWindow(QDialog):
    def __init__(self, concentration_test, parent=None):
        super().__init__(parent)
        loadUi("resultswindow.ui", self)
        self.concentration_test = concentration_test
        # TODO: show results?
        self.pushButton_OK.clicked.connect(self.reject)


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


if __name__ == "__main__":
    main()
Zum Code hätte ich ein paar Fragen:
  1. Warum erscheint nach einem Klick auf "Test starten" kein Vollbild-Fenster?
  2. Warum hast du Teile der Klasse `ConcentrationTestWindow` in die Klasse `ConcentrationTest` ausgegliedert?
  3. Warum erbt die Klasse `ConcentrationTest` von der Klasse `object`?

Gruß
Atalanttore
Antworten