Guidelines und Beispiele zur Strukturierung von pyqt-Code

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
derhendrik
User
Beiträge: 17
Registriert: Donnerstag 15. Januar 2015, 16:39

Hallo Python-Community!

Derzeit befasse ich mich im Rahmen meiner Masterarbeit mit PyQt um eine Anwendung zu schreiben, welche zur Maschinensteuerung genutzt wird, diverse Sensordaten erfassen und visualisieren soll und schlussendlich auf einem Raspberry Pi laufen soll. Soviel zu meinem Hintergrund.

Da ich als Maschinenbauer (noch?) kein richtiges Gespür für das Programmieren umfangreicherer Anwendungen habe, wollte ich euch fragen, ob ihr einige Richtlinien bzw. Beispiele für das Strukturieren von solchen Anwendungen kennt. Habe beispielsweise dieses Projekt hier gefunden, welches bis jetzt sehr hilfreich war:
https://gist.github.com/PhilReinhold/05 ... 4c37e2f95a
Dieses sieht dann so aus:
http://i.imgur.com/uuS3YPM.jpg


Um eine konkrete Frage zu stellen:

Insbesondere geht es mir um das TabWidget. Oft sehe ich in PyQtCode, dass von erstellten Klassen jeweils nur ein einzelnes Objekt erstellt wird. Ist das so korrekt und inwiefern bietet mir eine Klasse dann einen Vorteil?

Ist es folglich eher üblich für jeden Tab eine eigene Klasse zu erstellen (wo entsprechend nur ein Objekt von erstellt wird, nämlich der entsprechende Tab)?

Ich habe zum Beispiel versucht, eine Klasse für alle Tabs zu erstellen und entsprechend dann mehrere Objekte davon zu erstellen. Leider ist das alles sehr "hakelig", da die verschiedenen Tabs sowohl Gemeinsamkeiten (beispielsweise Layout) als auch Unterschiede haben (diverse Buttons).

Hier der Code:

In meiner MainWindow-Klasse erstelle ich das TabWidget und setze es als Zentrales Widget:

Code: Alles auswählen

class MainWindow(QtGui.QMainWindow):
    
    def __init__(self):
        super(MainWindow,self).__init__()
        self.setGeometry(200,200,1000,500)
        self.setWindowTitle("test")
        self.createComponents()
        self.setupTabs()
        self.createLayout()
        
    def createMenu(self):
        pass
    
    def createComponents(self):        
        self.tabwidget = QtGui.QTabWidget()
    
    def setupTabs(self):        
        self.tab1 = TabInhalt(self,'Seite_1')
        self.tab2 = TabInhalt(self,'Seite_2')
        self.tab3 = TabInhalt(self,'Seite_3')      
        
    def createLayout(self):
        layoutZentral = QtGui.QVBoxLayout()
        layoutZentral.addWidget(self.tabwidget)
        widgetZentral = QtGui.QWidget()
        widgetZentral.setLayout(layoutZentral)   
        self.setCentralWidget(widgetZentral)
Und entsprechend "TabInhalt" und die main:

Code: Alles auswählen

class TabInhalt(QtGui.QWidget):
    def __init__(self,mainwindow,tabname):
        self.tabname = tabname
        super(TabInhalt,self).__init__()              
        self.populate_tab(self.tabname)
        mainwindow.tabwidget.addTab(self, self.tabname)

    def populate_tab(self, tabname):              
        #Tab 1 
        if (self.tabname=='Seite_1'):
            self.labelHalloWelt =  QtGui.QLabel("Hallo Welt")
            self.labelHalloMom = QtGui.QLabel("Hallo Mom")
            self.labelHalloDad = QtGui.QLabel("Hallo Dad")   
            
            layout = QtGui.QVBoxLayout()            
            layout.addWidget(self.labelHalloWelt)
            layout.addWidget(self.labelHalloMom)
            layout.addWidget(self.labelHalloDad)            
            
            self.setLayout(layout)
        
        #Tab 2   
        elif (self.tabname =='Seite_2'):
            return #(Hier habe ich mal den Code für die Leserlichkeit abgekürzt)

        #Tab 3   
        elif (self.tabname =='Seite_3'):
            return #(Hier habe ich mal den Code für die Leserlichkeit abgekürzt)

      


def main(argv):    
    app = QtGui.QApplication(argv)
    mainwindow = MainWindow()
    #mainwindow.showFullScreen()
    mainwindow.show()
    sys.exit(app.exec_())
        
        
if __name__ == '__main__':
    main(sys.argv)



Ich hoffe, dass meine Frage wenigstens etwas verständlich ist und würde mich über eine Rückmeldung freuen!
Bis jetzt macht mir die GUI-Programmierung richtig Spaß, ich habe mithilfe des pyqtgraph-Moduls schon eine zügige Darstellung der Messwerte realisieren können, auch das Arbeiten mit dem Threading-Modul klappt schon ganz gut.

PS: Warum werden die Methoden in PyQt eigentlich in "CamelCase" geschrieben...?

Gruß
Hendrik
BlackJack

@derhendrik: Wenn Die Tabs gemeinsames verhalten haben würde man das eher in eine gemeinsame Basisklasse schreiben, statt eine Tabklasse die sich je nach übergebenen Namen unterschiedlich verhält.

Die Frage nach dem Vorteil einer Klasse von der sehr wahrscheinlich nur ein Exemplar erstellt wird stellt sich IMHO nicht. Vorteil gegenüber was denn? Wie würdest Du denn sonst Daten und Operationen des Tabs *zusammenfassen*, wenn nicht in einer Klasse? Der Tabinhalt muss ja auch ein `QWidget` oder etwas davon abgeleitetes sein.

Der `TabInhalt` sollte sich nicht selbst zu einen Tabwidget im Hauptfenster hinzufügen. Das geht ja auch gar nicht, denn `mainwindow` in Zeile 6 gibt es gar nicht. Und das sollte es auf Modulebene auch gar nicht geben.

Für meinen Geschmack teilst Du das erstellen des Hauptfensters auf zu viele Methoden auf.

Die Klammern um die Bedingungen bei ``if`` und ``elif`` sind überflüssig.

Qt ist ein in C++ geschriebenes GUI-Rahmenwerk und das verwendet die mixedCase-Schreibweise für Methoden und Attribute. Die Python-Anbindung übernimmt diese Schreibweise einfach nur statt alles umzubenennen.
derhendrik
User
Beiträge: 17
Registriert: Donnerstag 15. Januar 2015, 16:39

Hallo BlackJack!

Erst einmal vielen Dank, dass du dir die Zeit genommen hast, mir zu antworten.

Zu deinem ersten Kommentar bezüglich "Vorteil von Klassen bei nur einem Exemplar gegenüber was denn".
Viele Beispiele, die ich mir angeschaut habe, waren nur kleine Anwendungen, bei denen quasi der ganze Inhalt in der "MainWindow"-Klasse geschrieben wurden. Der Code sah dann ähnlich aus, wie der Code, der durch die pyuic.bat erstellt wird. Auch hier steht das ganze "Gerüst", welches aus der .ui-Datei generiert wurde, in einer Klasse.

Hier hätte ich zum Beispiel gedacht, dass ich für die Leserlichkeit eine Funktion (bzw. Methode?) in der MainWindow-Klasse schreibe, welche mir die Tabs aufbaut und somit gar keine eigene Klasse für die Tabs brauche. Als Anfänger finde ich die Objektorientierung teilweise noch verwirrend, ich hoffe das legt sich bald.


Zu dem erwähnten Problem mit dem "TabInhalt", welcher sich nicht selbst hinzufügen soll.
Ja, da wusste ich schon, dass ich mich auf dünnem Eis bewege =)
Das werde ich noch korrigieren, zurzeit funktioniert es aber. Im ersten Codeschnipsel wird in Zeile 19,20,21 der Parameter 'self' übergeben, welcher in diesem Fall 'mainwindow' (vgl. main-Funktion: mainwindow = MainWindow()) ist. Da werde ich aber nochmal reinschauen, gerade weil ich sowohl die Instanz von MainWindow 'mainwindow' genannt habe, als auch 'mainwindow' in der TabInhalt-Klasse als Variable verwende...

Zu den vielen Methoden zum Erstellen des Hauptfensters: Das habe ich so aus einem Lehrbuch übernommen, so ganz glücklich bin ich damit auch noch nicht.

Aber jetzt zum allerwichtigsten Teil:
"Wenn die Tabs gemeinsames Verhalten haben würde man das eher in eine gemeinsame Basisklasse schreiben"
Ich muss gestehen, dass ich mir gerade erstmal den Wikipedia-Artikel zu Basisklassen durchlesen musste, aber das macht Sinn.

Würde das dann circa so aussehen? (eher sinngemäß als syntaktisch richtig)

Code: Alles auswählen

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        
        # erstelle tab1,tab2,tab3 aus ihrer jeweils eigenen Klasse,
        # welche jeweils von der Basisklasse erben
        self.tab1 = Tab1()
        self.tab2 = Tab2()
        self.tab3 = Tab3()


        #unwichtiges Zeug zum Layout, soll hier nicht relevant sein
        self.tabwidget = QtGui.QTabWidget()
        layoutZentral = QtGui.QVBoxLayout()
        layoutZentral.addWidget(self.tabwidget)
        widgetZentral = QtGui.QWidget()
        widgetZentral.setLayout(layoutZentral)
        self.setCentralWidget(widgetZentral)

        #da sich die tabs nicht selber zum tabwidget hinzufügen sollen, passiert das jetzt hier:
        self.tabwidget.addTab(self.tab1)
        self.tabwidget.addTab(self.tab2)
        self.tabwidget.addTab(self.tab3)
        



class TabBasis(QtGui.QWidget):
    def __init__(self):
        super(TabBasis,self).__init__()
        #Hier kommen die Gemeinsamkeiten rein
        #z.B. haben alle Tabs ein HBoxLayout etc...

class Tab1(TabBasis):
    def __init__(self):
        super(Tab1,self).__init__()
        #Hier kommt alles rein, was nur in Tab1 gehört

class Tab2(TabBasis):
    def __init__(self):
        super(Tab2,self).__init__()
        #Hier kommt alles rein, was nur in Tab2 gehört

class Tab3(TabBasis):
    def __init__(self):
        super(Tab3,self).__init__()
        #Hier kommt alles rein, was nur in Tab3 gehört
Danke nochmals für deine Hilfe!
Zurzeit ist mein gesamter Code noch etwas Kraut und Rüben, da ich an zu vielen Ecken gleichzeitig angefangen habe zu basteln. Ich schau mal, dass ich den ein bisschen aufgeräumt kriege und werde ihn bei Bedarf nochmal posten.

Dir ein schönes Wochenende
Hendrik
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@derhendrik: ob man für jedes Tab eine eigene Klasse macht, kommt stark auf den Inhalt an. Bei einem Editor, wo jedes Tab von der Art her gleich, und nur unterschiedliche Dateien anzeigt, ist das natürlich immer die selbe Klasse und es gibt mehrere Instanzen. Wenn aber jeder Tab etwas anderes darstellt (Graphische Darstellung, Tabellarische Darstellung, Infos zur Maschine) macht es Sinn, einzelne Klassen mit unterschiedlichen Funktionen zu schreiben. Du kannst Dir Klassen eher als ein Ordnungssystem vorstellen, alles was zusammengehört, wird in einer Klasse gebündelt. Dann gibt es noch die Situation, wie man sie oft in Konfigurationsfenstern findet: Verschiedene Tabs bündeln nur verschiedene Gruppen von Einstellungen, die eigentliche Funktionalität, sammeln von Parametern, ist übergeordnet. DAnn macht man nur eine Klasse und eine ui-Datei, die alle Tabs enthält.
Antworten