Checkbox --> Name an Funktion übergeben

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
doerflia
User
Beiträge: 20
Registriert: Freitag 3. Mai 2019, 14:49

Hi,

ich erzeuge aus einem Array, das aus einer Datenbank stammt, beliebig viele Checkboxen über eine Schleife:

Code: Alles auswählen

                for idx, item in enumerate(items[1]):
                    # vbox checkbox
                    checkbox = QCheckBox(item)
                    checkbox.setChecked(False)
                    layout_vbox_checkbox.addWidget(checkbox)
                
                # create GroupBox for Checkboxes and add it to the layout
                checkbox_group_box = QGroupBox(radio_group_name)
                checkbox_group_box.setLayout(layout_vbox_checkbox)
                layout_vbox_sign.addWidget(checkbox_group_box)
(zum Code: Der Begriff radio_group_name kommt daher, dass in einem anderen Array alle Namen sowohl für Radio Buttons, als auch für Checkboxen gespeichert sind.
Ich aber manchmal Radio Buttons, manchmal Checkboxen verwende...)

Die Erzeugung des Formulars funktioniert. Die Darstellung Checkbox : Radio_Group_Name macht das Formular richtig.

Meine Frage:
Wenn ich zum Beispiel folgende Werte in Radio_Group_Name drin stehen habe: {Kind 1, Kind 2, Kind 3, Kind 4}
Woher weiß ich, welche "checkbos" zu Kind 1, zu Kind 2, zu Kind 3, zu Kind 4 gehört?

Ist nachfolgendes möglich? Also wenn item[1]="Kind 1" wäre?

Code: Alles auswählen

QCheckBox("Kind 1")
Wie kann ich den Namen der Checkbox an eine Funktion übergeben, um bspw. eine StatusÄnderung zu erkennen?
z. B.

Code: Alles auswählen

                    # stateChanged.connect
                    checkbox.stateChanged.connect(self.checkBoxState_listener(item))
Wäre das denkbar mit folgender Methode:

Code: Alles auswählen

    def checkBoxState_listener(self, checkBoxName):
    
    .... code, der dann abhängig vom Status und ckeckBoxName etwas macht
Bin blutiger Qt Anfänger.
Habe das mit dem Event-Handler noch nicht so raus, wie ich das aus anderen Sprachen kenne...


Vielen Dank schon Mal für Hilfe!
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Eine gerne genutzte Möglichkeit besteht darin, functools.partial zu verwenden. Damit kannst du ein oder mehrere Argumente an eine Funktion/Callable binden, und erhältst ein Callable zurück, das dann entsprechend weniger (bis keine) Argumente kennt.

Code: Alles auswählen

checkbox.stateChanged.connect(functools.partial(callback, kind))
doerflia
User
Beiträge: 20
Registriert: Freitag 3. Mai 2019, 14:49

Man lernt nie aus ;-)

Musste die Funktionsweise erst googeln, um das ganz zu verstehen.
Sehr interessante Methode.

Wie muss der aufgerufene Funktionskopf aussehen?
def checkBoxState_listener(self, kind)
doerflia
User
Beiträge: 20
Registriert: Freitag 3. Mai 2019, 14:49

Ok, danke. Ein Problem gelöst. Tipp war super!

Zweites Problem: bei der aufgerufnen Methode die richtige radio_button_group_box zu erwischen.

Code: Alles auswählen

    def checkBoxState_listener(self, checkBoxName):
        if self.sender().isChecked() == True and checkBoxName == "FOLDABLE":
            self.radio_button_group_box.setEnabled(True)
        else:
            self.radio_button_group_box.setEnabled(False)

        if self.sender().isChecked() == True and checkBoxName == "DAMAGED":
            self.radio_button_group_box.setEnabled(True)
        else:
            self.radio_button_group_box.setEnabled(False)
Mit dem angezeigten Code finde ich nur die letzte erzeugte Gruppe.
Aber auch hier ist die Anzahl abhängig von einem Array, das dynamisch aus der Datenbank stammt.

Im Prinzip bräcuhte ich etwas wie
self. radio_button_group_box("DAMAGED") oder so etwas in der Art...
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@doerflia: dann mußt Du Dir die Gruppen ebene irgendwie merken. Wie erzeugst Du denn die ganze GUI?
doerflia
User
Beiträge: 20
Registriert: Freitag 3. Mai 2019, 14:49

Die Erzeugung erfolgt aus einer Struktur

Code: Alles auswählen

items_lst = (
    ("Geometry", ("POINT",)),  # LINE
    ("Map Feature", ("TRAFFIC SIGN", "POLE", "OVERHEAD OBSTRUCTION", "OVERHEAD STRUCTURE FACE",
                                   "PERPENDICULAR WALLS", "EOADSIDE BARRIER", "ENTRY", "EXIT",
                                   "SHOULDER LANE", "STACKED ROADS", "BRIDGE START", "BRIDGE START",
                                   "BRIDGE END", "TUNNEL START", "TUNNEL END", "TRAFFIC LIGHTS")),
    ("Sign Characteristic", ("RECTANGLE", "ROUND", "Triangle", "YIELD", "OTHER")),
    ("Further Sign Information", ("TEMPORARY STATIONARY", "TEMPORARY MOVEABLE", "FOLDABLE", "DAMAGED", "OUT OF ORDER", "OTHER")),
    ("Foldable Sign Information", ("TURNED BUT VISIBLE", "TURNED AND NOT VISIBLE")),
    ("Damaged Sign Information", ("DAMAGED BUT VISIBLE", "DAMAGED COMPLETELY")),
    ("Additional Information on Exit Distance Sign", ("NONE", "EXIT NUMBER")),
    ("Additional Information on Exit Arrow Sign", ("NONE", "ALTERNATIVE ROUTE")),
    ("Status", ("OK", "FALSE POSITIVE", "FALSE NEGATIVE", "ROUGH POSITION", "ROUGH DIRECTION",
                                   "ROUGH BOUNDING BOX DIMENSION", "WRONG CLASSIFICATION", "WRONG VALUE", 
                                   "WRONG SHAPE", "WRONG CONDITION", "OTHER MISTAKE")))
und wird wie folgt umgesetzt:

Code: Alles auswählen

        for items in items_lst: # creates all categories
            layout_vbox_radio = QVBoxLayout()
            layout_vbox_map_feature = QVBoxLayout()
            layout_vbox_status = QVBoxLayout()
            layout_vbox_sign_characteristics = QVBoxLayout()
            layout_vbox_checkbox = QVBoxLayout()

            radio_group_name = items[0] # name
            button_group_radio = QButtonGroup() # for other items besides map feature selection or further sign information
            self.combo_box_map_feature = QComboBox() # for map feature selection
            self.combo_box_status = QComboBox() # for status selection
            self.combo_box_sign_characteristics = QComboBox() # for sign characteristics selection
            
            .....
            .....
            
            elif radio_group_name == "Further Sign Information":
                for idx, item in enumerate(items[1]):
                    # vbox checkbox
                    checkbox = QCheckBox(item)
                    checkbox.setChecked(False)
        
                    # stateChanged.connect
                    checkbox.stateChanged.connect(partial(self.checkBoxState_listener,item))
        
                    layout_vbox_checkbox.addWidget(checkbox)
        
                # create GroupBox for Checkboxes and add it to the layout
                checkbox_group_box = QGroupBox(radio_group_name)
                checkbox_group_box.setLayout(layout_vbox_checkbox)
                layout_vbox_sign.addWidget(checkbox_group_box) # add Widget for further sign information to sign attributes
Ich kann relativ leicht Änderungen an meinem Design und am Funktionsumfang vornehmen.
Lediglich Checkboxen machen mir die Arbeit schwer,
da ich durch gewisses Setzen von Optionen wie "Damaged Sign" andere Group_Boxen aktivieren bzw.
deaktivieren will.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@doerflia: Ich denke Du solltest das vielleicht nicht alles über den gleichen Callback behandeln. Alleine schon das Du `QObject.sender()` verwendest ”riecht” komisch aus OOP sicht. Dazu steht ja auch eine Warnung in der Qt-Dokumentation. Den Sender würde ich auch mit `partial()` binden. Und wenn Du noch mehr brauchst, dann das halt auch.

Was an dem *einen* Checkboxhandler ja auch sehr ungünstig ist: Verschiedene Gruppen dürfen nicht den gleichen Text für eine Checkbox verwenden. 'NONE' hast Du beispielsweise in zwei Gruppen stehen.

Weitere Anmerkungen: Konkrete Grunddatentypen haben in Namen nichts zu suchen. Wenn man den mal ändert, hat man entweder einen falschen, irreführenden Namen im Programm, oder man muss alle betroffenen Stellen ändern. `items_lst` ist keine Liste. Sollte aber eigentlich eine Sein. Genau wie das zweite Element in den Tupeln. Tupel sind für Werte gedacht, wo der Index bestimmt was für eine Bedeutung der Wert hat. Wenn alle Werte die gleiche Bedeutung haben, dann ist das ein Fall für eine Liste.

Abkürzungen sollte man auch vermeiden. Spart dem Leser Ratespielchen.

Die Wahlmöglichkeiten für `items_lst` währen ein Kandidat für `enum.Enum`.

`items` würde ich gleich im Schleifenkopf ”zerlegen” – magische Indexzahlen sind nicht so gut lesbar:

Code: Alles auswählen

       for radio_group_name, options in items_lst:
            # ...
Man vergleicht nicht auf literale Wahrheitswerte. Man hat ja einen Wahrheitswert. Wenn man den mit einem literalen Wahrheitswert vergleicht, kommt da nur wieder ein Wahrheitswert heraus. Beim Vergleich ``x == True`` kommt `True` heraus wenn `x` den Wert `True` hat und `False` wenn `x` den Wert `False` hat. Der Vergleich macht also keinen Sinn, weil da der Wert von `x` heraus kommt.

Und wenn man eine Bedingung in einem ``if`` hat, und in beiden Zweigen dann daraufhin das gleiche macht, was sich nur durch einen Wahrheitswert unterscheidet, kann man sich das ``if``/``else`` sparen, denn die Bedingung beim ``if`` ergibt ja `True` oder `False`.

Code: Alles auswählen

    def checkBoxState_listener(self, checkBoxName):
        self.radio_button_group_box.setEnabled(
            self.sender().isChecked() and checkBoxName == 'FOLDABLE'
        )
        # ...
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Der gezeigte Code setzt `radio_button_group_box` gar nicht, also hilft er auch nicht, Dein Problem im vorherigen Post zu beantworten.

Innerhalb einer for-Schleife einem Attribut einen Wert zuzuweisen, ist fast immer ein Zeichen dafür, dass man etwas falsch macht, denn Attribute sollten ja einen Zustand über das Ende einer Funktion hinaus bewahren, innerhalb einer Funktion erfüllen diese Aufgabe lokale Variablen. combo_box_map_feature, combo_box_status und combo_box_sign_characteristics werden also immer wieder überschrieben.

items_lst sollte keinen konkreten Datentyp enthalten, `items` ist auch sehr generell, was sind denn das für Dinge? Tuple sind keine Listen. Tuple enthalten verschiedenartige Dinge, Listen gleichartige. Also zwei von den drei verschachtelten Tuple müssen Listen sein.
doerflia
User
Beiträge: 20
Registriert: Freitag 3. Mai 2019, 14:49

Danke __blackjack__.
Das hat mir weitergeholfen, auch wenn ich jetzt einiges überarbeiten musste.
Lösung gefunden.
Antworten