QAbstractItemModel mit variablem "ColumnCount"

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
MicheMache
User
Beiträge: 10
Registriert: Donnerstag 30. Juli 2020, 15:46

Hallo zusammen,

ich sitze an einem Problem jetzt schon über 3 Wochen und nach langem hin- und her ohne wirkliches Ergebnis dachte ich mir, frage ich dochmal die Experten :wink:

Folgendes Problem habe ich :

Ich rufe aus einer Oracle DB Werte ab, das ganze will ich als TreeView darstellen.
Soweit sogut, funktioniert auch an sich, aber es will mir nicht gelingen, die Child - Spalten, die weniger als 5 Spalten haben anzuzeigen unter den Parent - Zeilen, die 5 Spalten haben.

Die Beispiele, die ich gefunden habe, benutzen für die Parent- und Child - Zeilen eine identische "ColumnCount", daher wird der Aufruf meist hardcoded mit in dem Fall z.B. "return 5" (wirft in dem Fall aber auch einen Fehler auf, da die letzten Child - Zeile weniger als 5 Spalten hat). "hardcode" ich 4 oder weniger funktioniert´s, aber die übergeordneten Parent - Zeilen werden dann beschnitten zu eben 4 oder weniger Spalten.
(und hier dreh ich mich im Kreis, ich bekomm´s einfach nicht hin :roll: )


Das funktioniert dann halt in meinem Fall nicht mehr für die Child - Zeile, da die ja weniger als 5 Spalten haben und es wird ein Fehler aufgeworfen :

line 197, in data
return node.data[index.column()]
IndexError: tuple index out of range

Die relevanten Code - Zeilen sehen so aus :

Code: Alles auswählen

class TreeModel(QtCore.QAbstractItemModel):
....
    def index(self, row, column, parent=QtCore.QModelIndex()):  
        
        if parent.isValid():
            parent_node = parent.internalPointer()
            node = parent_node.children[row]
            index = self.createIndex(row, column, node)
            return index
        
        else:
            index = self.createIndex(row, column, self.__top)
            return index
            
        childItem = parent_node.children[row]  
            
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()  
            
            
    def data(self, index, role): 

        node = index.internalPointer()
        data = [str(node.data)]

        if role == QtCore.Qt.DisplayRole: 
            
            return node.data[index.column()]     # -> das ist die besagte Zeile 197, für die dann der Fehler aufgeworfen wird. 
            
        return None


    def columnCount(self, parent=QtCore.QModelIndex()): 
        
        if parent.isValid():
            return parent.internalPointer().columnCount() # wird mit 5 ausgegeben, beschneidet aber trotzdem auf 4 Spalten, anscheinend wird zuerst die else - Sektion ausgewertet und damit die Header - Zeilen beschnitten %) ...
        else: 
            return 4	# setze ich hier "return 5" ein, bekomme ich für die Parent - Zeilen dann 5 Spalten, aber eben den Fehler sobald ich mich zur letzten Child - Zeile durchklicke.... 


-> ganz grundsätzliche Frage, weil ich kein Beispiel gefunden habe, das unterschiedliche Spaltenzahlen für Parent- und Child - Zeilen benutzt :
Ist das überhaupt möglich, oder müssen Parent- und Child- Zeilen identische Spaltenanzahl haben ?

-> Und falls nicht, wie müsste der Code aussehen für den "else" - Abschnitt in der "ColumnCount" um die Spaltenanzahl dann variabel statt hardcoded zurückgeben zu können ?!
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Dokumentation ist da leider nicht besonders aussagekräftig. Ich frage mich allerdings wie du dir die Darstellung mit unterschiedlichen Spaltenzahlen vorstellst?

Der einzige Hinweis darauf ist übrigens “muss 0 zurück geben, wenn man ein Tabellen Modell baut und der Parent gültig ist”. Das könnte ein Hinweis sein.
Benutzeravatar
MicheMache
User
Beiträge: 10
Registriert: Donnerstag 30. Juli 2020, 15:46

__deets__ hat geschrieben: Freitag 31. Juli 2020, 07:35 Die Dokumentation ist da leider nicht besonders aussagekräftig. Ich frage mich allerdings wie du dir die Darstellung mit unterschiedlichen Spaltenzahlen vorstellst?

Der einzige Hinweis darauf ist übrigens “muss 0 zurück geben, wenn man ein Tabellen Modell baut und der Parent gültig ist”. Das könnte ein Hinweis sein.
Da hast du recht, ich finde die Doku auch nicht besonders aussagekräftig.....


-> Vorstellung :
5 Parent - Spalten :
Bild

-> Realität :
entweder 4 Spalten für alle:
Bild

oder ich bekomme eben die Fehlermeldung :
Bild
wenn ich mich im Falle von 5 Spalten zum letzten Child durchklicke.

und wenn ich 0 zurückgebe, wenn der Parent gültig ist, bekomme ich nur noch den obersten Parent :

Bild

Code: Alles auswählen

    def columnCount(self, parent=QtCore.QModelIndex()): 
                
        if parent.isValid():
            return 0
        else: 
            return 5


Deshalb bin ich im Moment arg im Zweifel, ob das TreeModel überhaupt so gedacht war, unterschiedliche Spaltenzahlen zu haben (was ich mir aber nicht vorstellen kann, was wäre das für ein TreeModel - ich bin schon fast soweit, daß ich das QAbstractTableModel verwende und 2 Fenster mache mit dann halt 2 TableModels...... mit dem QAbstractItemModel könnt ich grad verzweifeln.
Wäre nicht schön, aber was soll man machen wenn noch nichtmal die Doku und Beispiele einen Hinweis hergeben außer alles zu hardcoden -.- )
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na das sieht ja aber im Grunde nur danach aus, als ob du eben *abfragen* musst, welche Spalte da jetzt ist. Und wenn dein Kind nur 4 sinnvolle Werte hat, aber das Parent 5, dann liegt die Loesung doch darin, fuer Spalte 5 (bzw 4, von 0 an) einfach None oder so zurueck zu geben. Statt blindlings in die Daten zu indizieren. Alternativ kannst du den Daten da auch einfach einen None-Wert anhefter.
Benutzeravatar
MicheMache
User
Beiträge: 10
Registriert: Donnerstag 30. Juli 2020, 15:46

hört sich ja einfach an, aber bei mir ist irgendwo der Wurm drin %)

ich hab mal ein paar DebugZeilen eingebaut und es sieht so aus :

Code: Alles auswählen


(nur relevante Teile) 

class MyData:

    def __init__(self, Data, parent=None):
        
        self.data = Data
        self.children = []
        self.parent = parent
        if parent is not None:
            parent.addChild(self)

    def columnCount(self):
        
        print("MyData columnCount = ", len(self.data))
        print("MyData self.data = ", self.data)
        
        return len(self.data)


class TreeModel(QtCore.QAbstractItemModel):

    def __init__(self, top, HeaderTitle, Data_Parent, Data_Child):
        
        super(TreeModel, self).__init__()
        self.__top = top
        self.Header = HeaderTitle
        self.Data_Parent = Data_Parent
        self.Data_Child = Data_Child

    def rowCount(self, index=QtCore.QModelIndex()):
        
        node = index.internalPointer()
        
        if node is None:
            count = 1
            
        else:
            count = len(node.children)
            
            print("rowCount node.columnCount() = ", node.columnCount())
            data = [str(node.data)]        
            print("rowCount node.data = ", data)

        return count

    
    def columnCount(self, parent=QtCore.QModelIndex()): 
        
        node = parent.internalPointer()
        
        if node is None:
            count = 5 
            
        else:
            count = node.columnCount()
            
            print("columnCount node.columnCount() = ", count)
            data = [str(node.data)]        
            print("columnCount node.data = ", data)
            
        return count

Die Ausgabe ist dann folgende :

Bild
-> die relevante Zeile mit 4 Spalten kommt natürlich nicht ohne den Fehler aufzuwerfen


Und hier ist die Debug-Ausgabe :

-> und hier ist mein Problem, rufe ich den columnCount aus der rowCount auf, würden hier die Zeilen gesehen werden und der count = 4 ist richtig,
dasselbe in der MyData -> columnCount.
-> Nur die columnCount in der TreeModel sieht diese Zeilen nie.... wie kann das sein, müssten hier nicht am ehesten für alle Zeilen der columnCount aufgerufen werden ?
Und wieso sehe ich sie selbst in der rowCount, aber dann eben nicht mehr in der columnCount ? :roll:

Bild
Benutzeravatar
__blackjack__
User
Beiträge: 13533
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hat jetzt nichts mit dem Problem zu tun, aber was ist denn bitte der Gedanke hinter dieser unsinnigen Aktion gewesen: ``data = [str(node.data)]``? Ein Tupel in eine Zeichenkette zu wandeln und als einziges Element in eine Liste zu stecken bringt genau was?

Code: Alles auswählen

- (void)countSheep {
    unsigned int sheep = 0;
    while ( ! [self isAsleep]) { ++sheep; }
}
Benutzeravatar
MicheMache
User
Beiträge: 10
Registriert: Donnerstag 30. Juli 2020, 15:46

man möge mir verziehen, ich bin relativ neu in Python, ich wollte nur die Daten sehen und es hat funktioniert %)

Hab´s korrigiert und die Daten sehe ich ja in dem Fall ebenfalls =)

ich poste hier mal zusätzlich die "index" und "data" (da ich auch die im Verdacht hatte, aber auch die sehen die Daten korrekt - würde ich sagen - siehe nächster Screenshot) :

Code: Alles auswählen


    def index(self, row, column, parent=QtCore.QModelIndex()):  
        
        if parent.isValid():
            parent_node = parent.internalPointer()
            node = parent_node.children[row]
            index = self.createIndex(row, column, node)
            
            data = str(node.data) 
            print("index data = ", data)
            
            return index
        
        else:
            index = self.createIndex(row, column, self.__top)
            return index


    def data(self, index, role): 
    
        node = index.internalPointer()
        data = str(node.data)
        print("data from data = ", data)

        if role == QtCore.Qt.DisplayRole: #and data[0] != 2006: # (not isinstance(data[0], int)): 
            #print("index.column() = ", index.column())
            return node.data[index.column()]
            
        return None


Bild

Ich muß gestehen, ich hab mir dieses TreeModel aus 2 Beispielen (simpletreemodel und TreeViewExample) zusammengezimmert und da ich
mit der Implementierung des QAbstractItemModel null Erfahrung habe, liegt der Fehler ganz sicher irgendwo zwischen den Kopfhörern, aber ich
seh leider den Wald vor lauter Bäumen nicht mehr.
Wenn ich es debugge sehe ich überall eigentlich die richtige Anzahl an Spalten, außer dort wo es relevant wäre, in der "columnCount" des TreeModels....
Heißt, alle Pointer wären - soweit ich sehen kann - auf der richtigen Zeile, nur leider wird die columnCount aus dem TreeModel zur nötigen Zeit nicht aufgerufen -.-
Benutzeravatar
MicheMache
User
Beiträge: 10
Registriert: Donnerstag 30. Juli 2020, 15:46

so, hab´s geschafft, eine saubere Lösung sieht aber anders aus %)

-> da die "columnCount" in der TreeModel nachweislich nicht angesprungen wird beim zeichnen des letzten Childs mit 4 Spalten :

Bild

-> beschneide ich die falsche columnCount in der "data" :

Code: Alles auswählen


    def data(self, index, role): 
    
        node = index.internalPointer()
        DataLength = len(node.data)
        Column = index.column()

        if role == QtCore.Qt.DisplayRole: 

            if Column >= DataLength: 		# wenn die Spalte größer wäre als die tatsächlichen Daten wird halt nichts gemacht
                pass
            else: 
                return node.data[Column]	        # ansonsten werden die Daten zurückgegeben
            
        return None
 
 
    def rowCount(self, index=QtCore.QModelIndex()):
        
        node = index.internalPointer()
        
        if node is None:
            count = 1
        else:
            count = len(node.children)

        return count

    
    def columnCount(self, parent=QtCore.QModelIndex()): 

        if parent.isValid():
            return parent.internalPointer().columnCount() # 5
        else:
            return 5      


-> jetzt weiß ich zwar immer noch nicht, ob das nur ein Fehler meinerseits war oder ob das TreeModel tatsächlich so furchtbar (unlogisch) ist....
Die tatsächliche columnCount zurückgeben zu können wäre halt perfekt (aber mittlerweile versteh ich die Beispiele, in denen gehardcoded wird, variabel scheint´s ja nicht zu funktionieren).
.....irgendwie funktioniert´s halt jetzt(ich bin überhaupt kein Fan von "irgendwie", ich hätte es gerne verstanden -.-)
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie gesagt, ich halte das für eine Misskonzeption deinerseits, das du denkst, eine Variable Anzahl spalten wäre sinnvoll. Wie bestimmst du denn, welche der Spalten in einer Zeile mit weniger Spalten auf welche der mit mehreren abgebildet wird? Und wie geht man damit um, das das aufklappen eines Kindes plötzlich 100 Spalten anfordert?

Der mehrspaltige Ansatz des TreeView ist konzeptionell eben eine Baumstruktur, aber daran angeschlossen ist eine Tabelle. Und die hat halt eine fixe Anzahl Spalten für alle. Wenn man das anders haben will, muss man eben alle diese Fragen ob und was da zusammen gehört wund wie man das darstellen will beantworten, und selbst programmieren.

Das es besser dokumentiert gehört ist allerdings unzweifelhaft.
Benutzeravatar
MicheMache
User
Beiträge: 10
Registriert: Donnerstag 30. Juli 2020, 15:46

__deets__ hat geschrieben: Montag 3. August 2020, 15:31 Wie gesagt, ich halte das für eine Misskonzeption deinerseits, das du denkst, eine Variable Anzahl spalten wäre sinnvoll. Wie bestimmst du denn, welche der Spalten in einer Zeile mit weniger Spalten auf welche der mit mehreren abgebildet wird? Und wie geht man damit um, das das aufklappen eines Kindes plötzlich 100 Spalten anfordert?

Der mehrspaltige Ansatz des TreeView ist konzeptionell eben eine Baumstruktur, aber daran angeschlossen ist eine Tabelle. Und die hat halt eine fixe Anzahl Spalten für alle. Wenn man das anders haben will, muss man eben alle diese Fragen ob und was da zusammen gehört wund wie man das darstellen will beantworten, und selbst programmieren.

Das es besser dokumentiert gehört ist allerdings unzweifelhaft.
Danke für diese Antwort -> "Der mehrspaltige Ansatz des TreeView ist konzeptionell eben eine Baumstruktur, aber daran angeschlossen ist eine Tabelle. Und die hat halt eine fixe Anzahl Spalten für alle."

Das war es ja, was ich hören wollte, denn genau das dachte ich mir schon ^^ - war aber leider so nirgends dokumentiert, das macht´s für mich als Anfänger schwer das (nichtdokumentierte) Konzept zu verstehen....

Und zu Deiner Frage : "Und wie geht man damit um, das das aufklappen eines Kindes plötzlich 100 Spalten anfordert? " -> das dachte ich, macht eben die "columnCount", daß diese variabel (je nach Zeile, die eben angefordert wird) die Anzahl der Spalten zurückgibt.
Macht sie halt leider nicht (damit sehe ich dieses TreeModel aber schon als etwas unflexibel an, da hätte ich mir mehr erwartet, aber .... wohl mein Fehler %) )
Antworten