Row Index von QTableWidget ermitteln

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

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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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

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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

__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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

__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: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

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: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

Ok, danke! Werde mir das anschauen!

Für mich ist das halt alles Neuland.
jb_alvarado
User
Beiträge: 55
Registriert: Mittwoch 11. Juli 2018, 11:11

__deets__ hat geschrieben: 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.
Bin da mittlerweile etwas schlauer geworden und ich danke das bekomme ich hin. Mit der Queue ist es bei meinem Fall halt so, dass sich darin immer nur ein Objekt, das aktuelle, drin befinden darf. Habe mir gedacht, dass ich das so lösen könnte, dass mein WorkerThread jedes mal wenn er eine Zeile abgearbeitet hat ein Signal schickt, welches veranlasst dass ein neues Objekt in die Queue geschoben wird.
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
Was ich gelesen habe ist, dass QT wohl automatisch entscheiden kann, wann es welche Art von Verbindung braucht. Ist das immer so?
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.
Verstehe jetzt besser warum man das so macht. Habe auch angefangen wieder ein QTableView mit einem QAbstractTableModel zu nehmen. Glücklich bin ich damit allerdings noch gar nicht. Es gibt nicht viel Beispiele im Internet dafür, mein Code wird um einiges komplexer und für die Spinner und Comboboxen brauche ich QItemDelegate um die in die Tabelle zu bekommen.
Weiß echt nicht, ob sich der Aufwand in meinem Fall lohnt. Im Schnitt hat meine Tabelle vielleicht 1-7 Zeilen, wenn's mal hochkommt 20-25.
__deets__
User
Beiträge: 14528
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zu Punkt zwei: nein, Qt kann das nicht immer automatisch entscheiden. Zum einen gibt es gerade beim threading subtile Fehler, darum ist es so wichtig, dass ein Objekt erst zu einem anderen Thread gehoerig wird, bevor man sich mit Signalen damit verbindet. Und zum zweiten gibt es manchmal Probleme, bei denen man explizit will, das ein Signal erst verarbeitet ist, nachdem das aktuelle Event/Signal komplett durch ist. Das ist glaube ich in C++ relevanter, weil das keinen Garbage Collector hat, aber ist mir auch schon vorgekommen.

Was Punkt drei angeht - hmja. Ich denke es lohnt sich trotzdem, weil die Technik zu beherrschen wichtig ist.
Antworten