Row Index von QTableWidget ermitteln

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Donnerstag 11. April 2019, 10:01

Hallo Allerseits,

ich befülle ein QTableWidget per Drag&Drop mit Dateinamen, zusätzlich kommen noch pro Zeile verschiedene Steuerelemente (Spinner, Checkbox, Combobox, etc.) hinzu.

Die Comboboxen werden z.B. so angelegt:

Code: Alles auswählen

self.combo_deinterlace_options = ["None", "Fast", "Slow", "setProg"]
...

combo_deinterlace = QComboBox()

for t in self.combo_deinterlace_options:
    combo_deinterlace.addItem(t)
    
combo_deinterlace.currentIndexChanged.connect(self.combo_selector)  
In meiner combo_selector Funktion muss ich nun den Row und Column Index abfragen, das mache ich mit:

Code: Alles auswählen

def combo_selector(self, state_index):
    selection = self.table_view.selectionModel().selectedIndexes()
    row = selection[0].row()
Das funktioniert allerdings so nicht. Ich vermute mal, dass die Combobox nicht lang genug im "Selected State" verweilt.

Was kann ich denn hier machen, um die Zeile und Spalte zu ermitteln?
Zuletzt geändert von jb_alvarado am Donnerstag 11. April 2019, 10:20, insgesamt 1-mal geändert.
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 11. April 2019, 10:19

Du bekommst doch state_index. Sollte das nicht der Index sein, den du brauchst?
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Donnerstag 11. April 2019, 10:22

Nein das ist nur der Index des Combobox Eintrags. Ich brauche aber den Zeilen Index vom Elternobjekt nämlich QTableWidget.

Ich mache das auch mit Spinnern, dort funktioniert es.
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 11. April 2019, 10:30

Ah. Das habe ich ueberlesen. Dann fueg den Zeilen-Index einfach beim generieren der QComboBox als ge-curry-tes Argument dazu:

Code: Alles auswählen

combo_deinterlace.currentIndexChanged.connect(functools.partial(self.combo_selector, row_index))  
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Donnerstag 11. April 2019, 10:35

Danke! Habe jetzt auch noch so was gefunden:

Code: Alles auswählen

selection = self.table_view.indexAt(obj.pos())
Aber damit muss ich das ganze Combobox Objekt mit schicken... Deine Lösung ist da natürlich eleganter!
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 11. April 2019, 10:36

Ich war mir nicht so 100%ig sicher, wie Qt damit umgeht. Zumindest fuer sehr grosse Tabellen gibt es da ja irgendwann so einen Cell-Mechanismus, und da werden dann die eigentlichen Objekte gar nicht mehr benutzt - zumindest zur Darstellung. Welche Konsequenzen das dann so alles hat etc kann ich aber nicht vorhersagen, und ob meine Loesung dann noch tut, etc.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Donnerstag 11. April 2019, 21:48

Leider habe ich jetzt doch durch die Lösung ein Problem:

Mein QTableWidget hat einen benutzerdefinierten Header mit Checkboxen. Klicke ich dort eine Checkbox werden alle Checkboxen in der Reihe aktiviert bzw. deaktiviert.

Nun habe ich auch noch ein Kontext Menü erstellt, worüber ich einzelne Zeilen löschen kann.
Klicke ich nun nach dem löschen einer Zeile eine Checkbox im Header bekomme ich einen Index Error, weil die CellWidgets nicht mehr die korrekten Indexe haben (die wurden ja mit dem connect fest vergeben).

Zufällig eine Idee, was ich da machen kann?
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 11. April 2019, 22:49

Dann mach das eben nicht. Statt alle zum Zeitpunkt der Erstellung bekannten Zeilen anzunehmen, musst du die halt dynamisch ermitteln. Oder habe ich da was missverstanden? Und was passiert denn mit den entfernten Zeilen? Sind die nicht betroffen, wenn da eine combobox triggert? Wenn doch, musst du ggf eindeutige Kriterien finden & im Modell suchen.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Freitag 12. April 2019, 08:10

Ich befürchte, dass es sich hier um ein Designfehler handelt, den ich mir noch nicht ganz eingestehen will. Zu Anfang hatte ich mich dazu entschieden, eine Dictionary zeitgleich generieren zu lassen, und zwar wenn eine Datei gedropt wird, wird gleich auch das Dictionary aktualisiert. Wenn Werte im QTableWidget verstellt werden, wird auch dann das Dictionary aktualisiert. Dadurch müssen dann natürlich auch die Indexe mit dem QTableWidget übereinstimmen. Beim Löschen von Einträgen ist das nicht das Problem, aber eben wenn Werte im Widget nachträglich noch mal angepasst werden.

Ich denke, ich muss die Tabelle im Gesamten auswerten lassen und nicht noch parallel ein Dictionary pflegen. Ich dachte, das sei einfacher, weil ich die Tabelle dynamisch halten will, also selbst wenn sie schon abgearbeitet wird, soll sie weiterhin befüllbar sein.

Ich könnte ja theoretisch auch den Row/Column Index anhand der Mausposition ermitteln, in etwa so:

Code: Alles auswählen

point = self.table_view.mapFromGlobal(QCursor().pos())
table_cell = self.table_view.indexAt(point)
Komischerweise ist dabei der Rowindex um eins verrutscht.
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 12. April 2019, 10:09

Auf Daten zu arbeiten, während man sie gleichzeitig manipuliert klingt erstmal nach einer ganz schlechten Idee. Und ich sag’s nochmal: schaff dir ein eindeutiges Kriterium, nach dem du einen Eintrag aufsuchen kannst. Damit entfällt das ganze gehummse mit zeitlich instabilen Selektionen oder Bildschirm Position.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Freitag 12. April 2019, 13:27

__deets__ hat geschrieben:
Freitag 12. April 2019, 10:09
Und ich sag’s nochmal: schaff dir ein eindeutiges Kriterium, nach dem du einen Eintrag aufsuchen kannst. Damit entfällt das ganze gehummse mit zeitlich instabilen Selektionen oder Bildschirm Position.
Das habe ich mittlerweile... Meine TableWigdets sind jetzt nicht mehr connectet und ich werte nun den Inhalt der Tabelle als ganzes aus, ohne Umweg über extra Dictionary.
Auf Daten zu arbeiten, während man sie gleichzeitig manipuliert klingt erstmal nach einer ganz schlechten Idee.
Mache das in etwa so:

Code: Alles auswählen

class WorkerThread(QThread):
    progress = Signal(list)

    def __init__(self, parent):
        QThread.__init__(self, parent)

        self._parent = parent
        self.current_index = 0
        self.end = False

    def run(self):
        while not self.end:
            for i in range(self._parent.table_view.rowCount()):
                if i < self.current_index:
                    continue

                file = self._parent.table_view.item(i, 0).text()
                # current tast for compression
                task = {
                    'file': file,
                    ...
                }

                # deactivate widgets
                for j in range(2, 9):
                    self._parent.table_view.cellWidget(i, j).setEnabled(False)

                status = '<html><p><b>PROGRESS: </b>{}</p></html>'.format(
                    os.path.basename(file))

                # update status message
                self.progress.emit([None, status])

                # TODO: encode current clip
                sleep(5)

                # update over all progress
                self.progress.emit([i + 1, None])
                self.current_index += 1

                if i + 1 == self._parent.table_view.rowCount():
                    self.end = True
                    break

                break
Sehe da jetzt nicht so das Problem.
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 12. April 2019, 13:37

Oh, ich seh da eine ganze Reihe von Problemen. Das faengt damit an, dass du in einem Thread auf GUI-Elemente zugreifst. Sowohl lesend als auch schreiben. Und das funktioniert nur durch reinen Zufall. Du darfst NIEMALS Widgets aus einem anderem als aus dem GUI-Thread manipulieren. Dazu gibt es ganze Kapitel in der Qt Dokumentation, wie man das richtig macht. Denn in dem Moment, wo sowohl dein Thread als auch der Main-Thread gleichzeitig an den Innereien eines Widgets rumfummeln (oder jeden anderen QObjektes) sind die in undefiniertem Zustand, und es kann derbe krachen.

Desweiteren missbrauchst du GUI-Objekte als Datenmodell, statt die Daten vernuenftig zu modellieren, und daraus sowohl die GUI als auch deinen Worker-Thread zu befuettern. ZB indem du eine Queue mit Arbeitsauftraegen erstellst, aus welcher der Thread sich bedient.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Freitag 12. April 2019, 13:53

__deets__ hat geschrieben:
Freitag 12. April 2019, 13:37
Oh, ich seh da eine ganze Reihe von Problemen. Das faengt damit an, dass du in einem Thread auf GUI-Elemente zugreifst. Sowohl lesend als auch schreiben. Und das funktioniert nur durch reinen Zufall. Du darfst NIEMALS Widgets aus einem anderem als aus dem GUI-Thread manipulieren. Dazu gibt es ganze Kapitel in der Qt Dokumentation, wie man das richtig macht. Denn in dem Moment, wo sowohl dein Thread als auch der Main-Thread gleichzeitig an den Innereien eines Widgets rumfummeln (oder jeden anderen QObjektes) sind die in undefiniertem Zustand, und es kann derbe krachen.
Kannst du mir hier zwei drei Schlagwörter geben, nach denen ich recherchieren kann? Oder lässt sich "Signal" in beide Richtungen verwenden? Bzw. Würde es mir ja schon reichen wenn ich von meinem WorkerThread immer wieder eine aktualisierte Taskliste bekommen würde. alles anderen kann ich über Signal lösen.
Desweiteren missbrauchst du GUI-Objekte als Datenmodell, statt die Daten vernuenftig zu modellieren, und daraus sowohl die GUI als auch deinen Worker-Thread zu befuettern. ZB indem du eine Queue mit Arbeitsauftraegen erstellst, aus welcher der Thread sich bedient.
Das wäre mein ursprünglicher Gedanke gewesen, den ich jetzt im letzten Schritt verworfen habe. Da müsste ich mit jedem Verändern der TableWidget den Datensatz anpassen, ist das nicht aufwändiger, als das TableWidget direkt auszuwerten?
__deets__
User
Beiträge: 6217
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 12. April 2019, 14:11

Qt und threading sollten da genug liefern, die originale Dokumentation hat da einiges an Beispielen und Erklaerungen. Und eine Queue ist doch ausreichend, alles, was da reingestopft wird, erledigt irgendwann der Worker-Thread.

Signal/Slot-Verbindungen KOENNEN thread-sicher sein, die Bedingungen unter denen das so ist werden in der Dokumentation erklaert, das sind dann sogenannte "Queued Connections". In einem anderen Zusammenhang habe ich dazu mal Beispiele gebaut: viewtopic.php?f=24&t=44250&start=15#p335559

Und was das Modell angeht: da sprach in von einem QAbstractItem-Model, welches du an deinen View binden kannst, und das damit sauber Daten von Darstellung trennt. Klassisches "Model View Controller"-Pattern halt, wie schon in den 90ern beliebt.
jb_alvarado
User
Beiträge: 49
Registriert: Mittwoch 11. Juli 2018, 11:11

Freitag 12. April 2019, 14:23

Ok, danke! Werde mir das anschauen!

Für mich ist das halt alles Neuland.
Antworten