Gehen PyQt5-Widgets und Menüs zusammen?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

meine Umgebung ist: Mac OS 10.13? "High Sierra", Python 3.6.5, PyQt5 Version 5.10.1 .

Ich erzeuge eine "Personen-Liste" mit ca. 150 DS (angestoßen von einer Menue-Action-Funktion);
Die Ausgabe im Terminal klappt - nun sollte sie (als String gebündelt) in ein Label (QTextEdit) gelenkt werden ...
Nach gut 6 Tagen Probieren mit hunderter Fehlversuche habe ich gestern ein passendes script (separat) ausprobiert, das NICHT mit Menüs arbeitet.
Und plötzlich klappte die Übergabe mit "label.setText(string)".

Die Main() Klasse wurde von unterschiedlichen Klassen abgeleitet -
anders klappte es auch nicht:
a. für Menüs von "QMainWindow"
b. für Maske (Widgets) von "QWidget"

Der Versuch, von BEIDEN abzuleiten schlug feht - Error sinngemäß:
"Es kann keine solche Mischung erzeugt/verarbeitet werden"

Jetzt meine Frage(n):

A) schließen sich beide Ansätze "Menü" und "Maske" (immer) aus? - wenn nicht...
B) mit welchen Techniken kann man beides vereinen?
B1. Frames
B2. mehrere Fenster
B3. Widget-Gruppierungen
B4. anderes

Hoffe, dass mir jemand weiterhelfen kann, der ähnliches erlebt hat.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dieses Paraphrasieren von Fehlermeldungen und was du da kodiert hast bringt nichts. Bitte zeig uns den Code, und die konkrete Fehlermeldung. Ganz grundsaetzlich aber geht das natuerlich, Menues "stoeren" keine anderen Widgets.
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

die vollst. Error-Message nach dem Versuch, von beiden Klassen abzuleiten
class Main(QWidget, QMainWindow):

ist: "TypeError: Cannot create a consistent method resolution
order (MRO) for bases QWidget, QMainWindow"

Verändere ich die Reihenfolge, dann stimmt plötzlich alles :shock:
( class Main(QMainWindow, QWidget): )

Jetzt mal ein Auszug aus dem code:

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
ZetCode PyQt5 tutorial - ehem. #submenu.py
This program creates a submenu.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
"""

import sys
from PyQt5.QtWidgets import (QWidget, QLabel, QMainWindow, QAction, QMenu, 
    QLineEdit, QTextEdit, QPushButton, QComboBox, QApplication)
# import mysql.connector
from _familie import Person2, Orte

fam2 = Person2()

def prPers(k):
    print()
    if k in fam2.pers.keys():
        print(fam2.pers[k])
    else:
        print('Der Pers-Schlüssel wurde nicht gefunden.')
    print(48 * '-')

def prAllP():
    sAus = ''
    anz = len(fam2.pers); print()
    print(f'Alle  {anz}  DS: \n') 
    sAus += f'Alle  {anz}  DS: '
    for p in fam2.pers.keys():
        sAus += f'{p},{fam2.pers[p]}'
    Main.myLabelSet(sAus)
    return sAus
... ( weitere 6 Funktionen ) ...

class Main(QMainWindow, QWidget):
    
    def __init__(self):
        super().__init__()
        
        self.initUI() 
        
    def initUI(self):         
        # self.statusBar()
        
        menubar = self.menuBar()
        # NEU + WICHTIG !!!!!
        menubar.setNativeMenuBar(False)
        
        
        # *** neu 21.06.18
        exitAct = QAction("&Exit", self)
        exitAct.setShortcut("Ctrl+Q")
        exitAct.setStatusTip('Die App verlassen')
        exitAct.triggered.connect(self.close_app)
        

        fileMenu = menubar.addMenu('Files')
        persMenu = menubar.addMenu('Person')
        ortMenu = menubar.addMenu('Orte')

        iPMenu = QMenu('Im+Export', self)
        iPAct1 = QAction('Import Pers', self)
        iPAct1.setStatusTip('Personen von der DB reinholen')
        iPAct1.triggered.connect(self.dummy)
        iPAct2 = QAction('Export Pers', self)
        iPAct2.setStatusTip('Personen in die DB schreiben')
        iPAct2.triggered.connect(self.dummy)
        
        aPact = QAction('zeig 1 Person', self)
        aPact.setStatusTip('Anzeigen einer Personen')
        aPact.triggered.connect(prAllP)
        
        bPact = QAction('zeig n Personen', self)
        bPact.setStatusTip('Anzeigen mehrer Personen')
        bPact.triggered.connect(prAllP)
        
        cPact = QAction('zeig alle Personen', self)
        cPact.setStatusTip('Anzeigen aller Personen')
        cPact.triggered.connect(prAllP)
        
        dPact = QAction('löschen Person', self)
        dPact.setStatusTip('Löschen einer Personen')
        dPact.triggered.connect(self.dummy)
        
        ePact = QAction('Person heiratet', self)
        ePact.setStatusTip('Person verheiraten')
        ePact.triggered.connect(self.dummy)

        iPMenu.addAction(iPAct1)
        iPMenu.addAction(iPAct2)
        persMenu.addMenu(iPMenu)
        persMenu.addAction(aPact)
        persMenu.addAction(bPact)
        persMenu.addAction(cPact)
        persMenu.addAction(dPact)
        persMenu.addAction(ePact)

        # iOMenu = QMenu('Im+Export', self)
        # iOAct1 = QAction('Import Orte', self) 
        # iOAct1.setStatusTip('Orte von der DB reinholen')
        # iOAct1.triggered.connect(self.dummy)
        # iOAct2 = QAction('Export Orte', self) 
        # iOAct1.setStatusTip('Orte von der DB reinholen')
        # iOAct1.triggered.connect(self.dummy)

        # iOMenu.addAction(iOAct1)
        # iOMenu.addAction(iOAct2)

        # newAct = QAction('New', self)        
        # newAct.setStatusTip('Orte von der DB reinholen')
        # newAct.triggered.connect(self.dummy)
        # fileMenu.addAction(newAct)
        # fileMenu.addAction(exitAct)

        # ortMenu.addMenu(iOMenu)

        btn = QPushButton("Ok", self)
        btn.move(25, 25)
        btn.setStatusTip('es könnte ein Knopf sein.')
        btn.clicked.connect(self.buttonClicked)

        self.label = QLabel('Personen',self)
        self.label.move(20,45)

        self.my_text1 = 25*'Das Wandern ist des Müllers Lust, das Wandern ist des Müllers ...\n'
        self.my_text2 = 35*'Iiiihhhhhmm Waaaalldd da sind die Rääüääüäüäüäübäärr ...\n'

        self.my_label = QLabel('my_text', self)
        self.my_label.move(20,65)
        self.my_label.setText(self.my_text1)

        self.setGeometry(60, 130, 550, 490)
        self.setWindowTitle('Personen und Orte ... Fenster')    
        self.show()

    def buttonClicked(self, sometext):
        # persText = prAllP()
        self.my_label.setText(self.my_text2)
    
    def dummy(self):
        print('hier war "Dummy1"')
        pass
    
    def myLabelSet(self, text):
        self.my_label.setText(text[:370])
        print(f'der Labeltext sollte geändert sein!')
        print(text[(85*7):(85*11)] )
        # print('SOOOO EINFACH könnte das gehen !!!!')
    
    def close_app(self):
            print('ByBy...')
            sys.exit(1)   
def run():
    app = QApplication(sys.argv)
    ex = Main()
    sys.exit(app.exec_())

if __name__ == '__main__':
    run()
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

den code in der Funktion prAllP() habe ich noch korrigiert (der Methodenaufruf stimmte nicht)

Code: Alles auswählen

def prAllP():
    sAus = ''
    anz = len(fam2.pers); print()
    print(f'Alle  {anz}  DS: \n') 
    sAus += f'Alle  {anz}  DS: '
    for p in fam2.pers.keys():
        sAus += f'{p},{fam2.pers[p]}'
    #  neu:
    self = Main()
    Main.myLabelSet(self, sAus)   '  self ist dazu gekommen
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

Hab mein Hauptproblem soeben gelöst: aus QLabel ein QTextEdit gemacht;
ich hatte gedacht, ein QLabel könnte auch viel mehr Zeichen aufnehmen ... :(

Code: Alles auswählen

        self.my_label = QTextEdit('my_text', self)
        self.my_label.setText(self.my_text1)
        self.my_label.resize(430,230)
Das sieht jetzt schon viel besser aus als vorher.
Bild

NUR: Füllen über die Menüfunktion "prAllP -> Main.myLabelSet(self, text)" -
... das klappt immer noch nicht ! ( obwohl der String in der Var. "text" vorliegt !! :roll: )

Hat jemand eine Idee?
Benutzeravatar
__blackjack__
User
Beiträge: 13572
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ebs55: Es macht Null Sinn explizit von `QWidget` zu erben weil `QMainWindow` bereits von `QWidget` erbt. Ein `QMainWindow` *ist* ein `QWidget`.

Als nächstes solltest ganz drng mit den gnz2 Abk und anght6 Num aufhören2. Wenn Du `print_all_persons()` meinst, schreib nicht `prAllP()`.

`fam2` ist keine Konstante und hat damit nichts auf Modulebene zu suchen. Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert.

Alles was eine Funktion oder Methode verwendet (ausser Konstanten) sollte als Argument übergeben werden und nicht auf magische Weise aus der Umgebung genommen werden.

`sys.exit()` gehört nicht irgendwo in eine Methode. Wenn Du das Hauptfenster schliessen willst, dann schliesse das Hauptfenster. Wenn alle Fenster geschlossen sind, wird normalerweise auch die Qt-Hauptschleife beendet und das Programm endet.

`self` ist der Name für das erste, implizit übergebene Argument bei Methoden, den sollte man nicht für lokale Werte in Funktionen verwenden.

Du musst die `myLabelSet()`-Methode auf dem bereits bestehenden Exemplar von `Main` aufrufen. Dafür musst Du das übergeben. Dazu solltest Du Dir entweder `functools.partial()` anschauen, oder besser objektorientierte Programmierung. Das ist ja keine unabhängige Funktion sondern eine Methode Deiner GUI.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

@__deets__ und @__blackjack__
Ganz herzlichen Dank für eure lehrreichen Beiträge.

Ich werde mich spätestens morgen ganz ernsthaft mit der Umsetzung der einleuchtenden Tips befassen;
Nur das mit den geliebten Abkürzungen ... das kann ich noch nicht versprechen - bin hald ein gebürtiger Schwoab. Und da spart man schon gerne - auch an Buchstaben ;-)
Mit partial() hatte ich gestern abend erstmals Berührung, kopiert, eingesetzt, hat was gemacht - nur verstanden hab ich's auf die Schnelle (noch) nicht.

Nochmals vielen Dank!
Benutzeravatar
__blackjack__
User
Beiträge: 13572
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bei den ”gesparten” Buchstaben bezahlt man jedes mal beim lesen, und Quelltext wird öfter gelesen als geschrieben. Es ist ja auch nicht so als wenn Buchstaben knapp wären, oder Plattenplatz. Und tippen braucht man die ja auch nicht alle, denn jeder Editor der sich zum Programmieren eignet hat heute eine Autovervollständigung.
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

OK, du hast ja soooo recht; Jetzt bin ich auch noch in diesem Punkt überzeugt und werde es ganz sicher beherzigen.
Noch etwas: der Begriff "fam2" steht für ein dictionary, das die Personenliste aufnehmen wird;
Diese wird per pickle.load() von der Platte gelesen und hält die Personendaten als "namedTupel" .

Ist der Platz dann auf Modulebene korrekt - oder soll ich es erst innerhalb der Klasse reinholen - als Klassenattribut ?
Benutzeravatar
__blackjack__
User
Beiträge: 13572
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`fam2` scheint nicht direkt ein Wörterbuch zu sein, denn es wird ja über ein `pers`-Attribut auf ein Wörterbuch in diesem Objekt zugegriffen.

Die Daten sind ja nicht wirklich konstant wenn sie aus einer Datendatei kommen. Das Problem ist das die hier fest mit dem anderen Code verbunden werden und man keine Chance hat, zum Beispiel zum testen, mal andere Daten in der GUI darzustellen. Die sollten also auch nicht innerhalb der Klasse geladen werden, sondern von aussen übergeben werden. Es handelt sich dabei auch um Programmlogik bzw. Datenhaltung, was man nicht mit der GUI vermischen sollte.

Pickle ist eher nicht so geeignet für längerfristige Daten. Wenn man sich auf Werte mit Datentypen aus der Standardbibliothek beschränkt, hat man kein Problem damit das die serialisierten Daten und der Code auseinander driften können, aber es ist ein Format das einen im Grunde auf Python als Sprache festnagelt. Ich würde da ein Standardformat vorziehen. JSON, YAML, CSV wenn die Daten dafür geeignet sind, oder wenn es sein muss auch XML. Oder SQLite-Datenbank(en).
„Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.“ — Brian W. Kernighan
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

Sorry - das war nicht korrekt (aus der Erinnerung schnell hingeschrieben).

fam2 ist ein Objekt der Klasse Person2; Diese ist noch "falsch modelliert" und ist vielmehr
eine Personen-LISTE ( das Dictionary self.pers() ) - und das wird per pickle geholt.

Hier ist die Definition der "Klasse Person2()" - ausgelagert in "familie.py":

Code: Alles auswählen

import sys, pickle

PATH2 = './pdaten/pers2.dat'

class Person2(object):
    def __init__(self):
        try:
            f = open(PATH2, "rb")
            self.pers = pickle.load(f)
            f.close()
        except:
            print(f'Error: {sys.exc_info()} ')
            f = open(PATH2, "wb")
            self.pers = dict()
            f.close()

    def addPers(self, dpers, d):
        if not d in self.pers.keys():
            self.pers[d] = dpers
            f = open(PATH2, "wb")
            pickle.dump(self.pers, f)
            f.close()
        else:
            print('Die Person gibt es bereits. ')

    def delPers(self, d):
        if d in self.pers.keys():
            del self.pers[d]
            f = open(PATH2, "wb")
            pickle.dump(self.pers, f)
            f.close()
        else:
            # print('Error ' + str(sys.exc_info()[1]) )
            print('Der key wurde nicht gefunden ')

    def updPers(self, d, daten):
        self.delPers(d)
        self.addPers(daten, d)

    def getPers(self, d):
        f = open(PATH2, "rb")
        self.pers = pickle.load(f)
        f.close()
        if d in self.pers.keys():
            return self.pers[d]  
        else: return ""
    
    def getAllPers(self):
        f = open(PATH, "rb")
        self.pers = pickle.load(f)
        f.close()
        personen = []
        for p in self.pers.keys():
            # eine Liste mit Personendaten erzeugen = das Erg. (return)
            personen.append(self.pers[p])
            # print( p, self.pers[p] )
        return personen
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

SUPER - herzlichen Dank!
Ich hab mal bei T-Systems (Telekom) vor 16 Jahren ca. 4 Jahre lang Datenmodellierer "gespielt" - leider blieb es immer beim theoretischen Teil... (bin seit 10 Jahren Pensionär)

Deshalb graust es mir schon die ganze Zeit, wenn ich auf die "alte Datenstruktur" schaue :oops: :twisted:

In der 3. Version habe ich das Pickeln auch schon aus der Klasse Person() entfernt und in die Programmlogik geholt.
Nur das Problem, eine Personen-Liste NICHT in einem "Label" darstellen zu können, hat mich dermaßen lahmgelegt, dass ich nicht weiter umbauen konnte.
Aber jetzt geht es ja wieder aufwärts - DANKE !!!
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

und die Daten habe ich auch schon seit langem in einer mySQL-Datenbank.
Das Pickeln fand ich anfangs einfach total "spannend";

Aber dass es auf Dauer nicht sinnvoll ist, der Gedanke kam mir auch schon mehrere male.
Sirius3
User
Beiträge: 18054
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Du einfach ein persistentes gepickeltes Wörterbuch haben willst, dann gibt es das im shelve-Modul schon fertig. In __init__ ist es nicht sinnvoll, bei irgend einem Fehler die Datei komplett und unwiederbringlich zu zerstören. Am besten prüfst Du auf genau das, was vorhersehbar schief gehen kann und wo es eine sinnvolle Behebung des Fehlers geben kann: die Datei existiert nicht. Du liest die Datei bei __init__ und bei jedem get-Aufruf, wobei bei get die bei __init__ zerstörte Datei dann wirklich zu einem Abbruch führt. Schreiben wurde auch zwei mal implementiert, wo es eigentlich eine eigene Methode für bräuchte. Statt des `keys`-Aufrufs, der in manchen Python-Versionen langsam ist und immer umständlich kann man direkt mit dem in-Operator auf ein Wörterbuch prüfen, ob ein Schlüssel existiert. getAllPers ist auch nur umständlich ein self.pers.values() geschrieben. Funktionen sollten immer nur eine Art Rückgabewert haben. getPers liefert entweder einen leeren String oder ein Personen-Objekt.
ebs55
User
Beiträge: 17
Registriert: Sonntag 29. April 2018, 14:27

@sirius3 Herzlichsten Dank für deine umfangreiche und tolle Antwort.
Ich bin mir gar nicht sicher, ob ich lange bei "pickle" bleiben will.
Das Ganze ist/war ja mehr oder weniger nur ein Schnellschuss zum Üben;
Und vom Datenmodell her ja mehr als grausam. Bin gerade dabei, alles umzubauen.

Vermutlich werde ich ein geändertes Objekt in "meine mySQL-DB" speichern.
Ich hab dasselbe in einer PHP-Variante schon ausgiebig praktiziert.

Aber deine Anregungen haben mich weiter neugierig gemacht - und vielen Dank auch für die
diversen Korrekturen - ich werde sie immer im Sinn behalten.
Antworten