QTreeView() und das Verschwinden lassen einer bestimmten Spalte

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

im nachfolgenden, ausführbaren Quelltext arbeite ich mit der QStandardItemModel()-Klasse. Um das ganze zu simulieren, füge ich die verschachtelte Liste (self.items) zu besagten Model hinzu. Was ihr auch sehen könnte, ist, dass ich QTreeView() mittels der setView()-Methode in die QComboBox() hinzufügen möchte. Was ich nun erwarte ist, dass in der QTreeView() eine bestimmte Spalte, hier die erste Spalte, versteckt wird. Dies versuche ich auch vergeblich - vermittels der setColumnHidden()-Methode. Irgendwie will mir das nicht klappen. Derzeit sieht es so aus, dass sich die QComboBox() nicht einmal aufklappt, wenn ich anklicke.

Code: Alles auswählen

import sys
from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtGui import QApplication, QStandardItemModel, QStandardItem, QTreeView, QComboBox, QDialog, \
                         QVBoxLayout

class MyCustomDialog(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        # First we create an instance of QStandardItemModel()
        self.standard_item_model = QStandardItemModel(0, 2)
        
        # Now we create a liste and fill it.
        self.items = [
            ["0", 'Cookie dough'], 
            ["1", 'Hummus'], 
            ["2", 'Spaghetti'], 
            ["3", 'Dal makhani'], 
            ["4", 'Chocolate whipped cream'] 
        ]

        # We simulate it and populate the liste to given model.
        for i in self.items:
            self.populate_data_item(i)

        # We want to show the data, so we create the instance of  QTreeView()           
        self.tree_view = QTreeView()
        self.tree_view.setRootIsDecorated(False)

        # Later we want to set the TreeView to the Combobox, so we have to create the instance of  QComboBox()   
        self.combo_box = QComboBox()
        self.combo_box.setView(self.tree_view)
        self.combo_box.setModel(self.standard_item_model)

        # We don't neeed to see the first column. So hide it.
        self.tree_view.setColumnHidden(0, True)
 
        # Maybe we can also hide the header. Here, comment it out.   
        #self.tree_view.header().hide()

        # Let us set the header with two columns
        self.set_header_data(list_header_data = ["ID", "Data"])
        
        layout = QVBoxLayout(self)
        layout.addWidget(self.combo_box)
        self.setLayout(layout)

        self.selection_changed()       

    def selection_changed(self):
        self.tree_view.selectionModel().selectionChanged.connect(lambda new_index: 
                    self.get_id_tree_view(new_index=new_index, tree_view_object=self.tree_view))


    def generator_header_data(self, list_header):

        for header_data in list_header:
            yield header_data
            
    def set_header_data(self, list_header_data=None):

        count_column = 0

        for header_data in self.generator_header_data(list_header_data):
            self.standard_item_model.setHeaderData(count_column, Qt.Horizontal, header_data)

            count_column +=1

        return
        
    def populate_data_item(self, tuple_items):
       
        count_items = len(tuple_items)

        if count_items == 2:

            item_first, item_second = tuple_items

        two_columns_item = [QStandardItem(str(item_first)), QStandardItem(item_second)]

        self.standard_item_model.appendRow(two_columns_item)

        return      

    def get_id_tree_view(self, new_index=None,
                         tree_view_object=None,):

        try:
            if not new_index is None:

                index = new_index.indexes()[0].data()#.toPyObject()

                if isinstance(index, QVariant):
                    print "index", index.toString()
                    
        except IndexError as InErr:
            print "InErr", InErr
        
           
def main():
    app = QApplication(sys.argv)
    window = MyCustomDialog()
    window.resize(200, 50)
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute, ich habe den obigen Quelltext ein wenig aufgeräumt und nach ewigem Grübeln habe ich es auch geschafft, dass die QTreeView() beim Anklicken der QComboBox() angezeigt wird. Die QHeaderView() lässt sich vermittels der hide()-Methode auch nun verstecken.

Bild

Jetzt fehlt mir noch das Verstecken der ersten Spalte. Dazu habe ich (wie ihr in Zeilen 24 und 25 sehen könnte) mich an zwei Möglichkeiten bedient. Die erste Möglichkeit, die mir auch sofort einfiel, war die setColumnHidden()-Methode. Sie funktioniert nicht, wirft aber auch keinerlei Fehler. Die nächste Möglichkeit ist die hideSection()-Methode, die ich aber erst anwenden kann, wenn ich zunächst über header() gehe, um somit die QHeaderView()-Klasse zu bekommen. Schließlich weißt ja die QHeaderView über die Spalten bescheid, und sollte sie verstecken können. Aber auch erfolglos. Keinerlei Fehlermeldung.

Hat keiner eine Idee?

Code: Alles auswählen

import sys
from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtGui import QApplication, QStandardItemModel, QStandardItem, QTreeView, QComboBox, QDialog, \
                         QVBoxLayout, QPushButton

class MyCustomDialog(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        self.standard_item_model = QStandardItemModel(0, 2)
        self.set_header_data(list_header_data = ["ID", "Data"])

        self.items = [
            ["0", 'Cookie dough'], 
            ["1", 'Hummus'], 
            ["2", 'Spaghetti'], 
            ["3", 'Dal makhani'], 
            ["4", 'Chocolate whipped cream'] 
        ]
        
        self.tree_view = QTreeView(self)
        self.tree_view.setRootIsDecorated(False)
        self.tree_view.header().hide()
        #self.tree_view.header().hideSection(0)
        #self.tree_view.setColumnHidden(0, True)

        self.combo_box = QComboBox(self)

        self.combo_box.setView(self.tree_view)
        self.combo_box.setModel(self.standard_item_model)

        self.push_button = QPushButton(self)
        self.push_button.setText("Populate in TreeView")
        
        layout = QVBoxLayout(self)
        layout.addWidget(self.combo_box)
        layout.addWidget(self.push_button)
        self.setLayout(layout)

        self.selection_changed()
        self.init_signal_clicked_push_button()

    def selection_changed(self):
        self.tree_view.selectionModel().selectionChanged.connect(lambda new_index: 
                    self.get_id_tree_view(new_index=new_index, tree_view_object=self.tree_view))


    def generator_header_data(self, list_header):

        for header_data in list_header:
            yield header_data
            
    def set_header_data(self, list_header_data=None):

        count_column = 0

        for header_data in self.generator_header_data(list_header_data):
            self.standard_item_model.setHeaderData(count_column, Qt.Horizontal, header_data)

            count_column +=1

        return
        
    def populate_data_item(self, tuple_items):
       
        count_items = len(tuple_items)

        if count_items == 2:

            item_first, item_second = tuple_items

        two_columns_item = [QStandardItem(str(item_first)), QStandardItem(item_second)]

        self.standard_item_model.appendRow(two_columns_item)

        return      

    def get_id_tree_view(self, new_index=None,
                         tree_view_object=None,):

        try:
            if not new_index is None:

                index = new_index.indexes()[0].data()#.toPyObject()

                if isinstance(index, QVariant):
                    print "index", index.toString()
                    
        except IndexError as InErr:
            print "InErr", InErr

    def populate(self):
        for i in self.items:
            self.populate_data_item(i)

        

    def init_signal_clicked_push_button(self):
        
        self.push_button.clicked.connect(self.populate)
        
           
def main():
    app = QApplication(sys.argv)
    window = MyCustomDialog()
    window.resize(300, 50)
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ein weiteres Update: Mich lässt die ganze Sache keine Ruhe. Also habe ich weiter nachgeforscht. In Zeile 30 und 31 habe ich die view()-Methode auf die QComboBox() angewendet, um somit durch Umwege auf die QTreeView() zuzugreifen und die erste Spalte zu verstecken. Also, das Verstecken der QHeaderView() klappt über diesem Wege nach wie vor, nur das mit der Spalte will nicht klappen. Irgendwie drehe ich mich wohl im Kreise.

Code: Alles auswählen

import sys
from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtGui import QApplication, QStandardItemModel, QStandardItem, QTreeView, QComboBox, QDialog, \
                         QVBoxLayout, QPushButton

class MyCustomDialog(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        self.standard_item_model = QStandardItemModel(0,2, self)
        self.set_header_data(list_header_data = ["ID", "Data"])

        self.items = [
            ["0", 'Cookie dough'], 
            ["1", 'Hummus'], 
            ["2", 'Spaghetti'], 
            ["3", 'Dal makhani'], 
            ["4", 'Chocolate whipped cream'] 
        ]
        
        self.tree_view = QTreeView(self)
        self.tree_view.setRootIsDecorated(False)
        #self.tree_view.header().hide()
        #self.tree_view.header().hideSection(0)
        #self.tree_view.setColumnHidden(0, True)

        self.combo_box = QComboBox(self)
        self.combo_box.setView(self.tree_view)       
        self.combo_box.setModel(self.standard_item_model)
        self.combo_box.view().setHeaderHidden(True)
        self.combo_box.view().header().hideSection(0)

        self.push_button_load = QPushButton(self)
        self.push_button_load.setText("Populate in TreeView")

        self.push_button_hide_column = QPushButton(self)
        self.push_button_hide_column.setText("Hide first column")
        
        layout = QVBoxLayout(self)
        layout.addWidget(self.combo_box)
        layout.addWidget(self.push_button_load)
        layout.addWidget(self.push_button_hide_column)
        self.setLayout(layout)

        self.selection_changed()
        self.init_signal_clicked_push_button()

    def selection_changed(self):
        self.tree_view.selectionModel().selectionChanged.connect(lambda new_index: 
                    self.get_id_tree_view(new_index=new_index, tree_view_object=self.tree_view))


    def generator_header_data(self, list_header):

        for header_data in list_header:
            yield header_data
            
    def set_header_data(self, list_header_data=None):

        count_column = 0

        for header_data in self.generator_header_data(list_header_data):
            self.standard_item_model.setHeaderData(count_column, Qt.Horizontal, header_data)

            count_column +=1

        return
        
    def populate_data_item(self, tuple_items):
       
        count_items = len(tuple_items)

        if count_items == 2:

            item_first, item_second = tuple_items

        two_columns_item = [QStandardItem(str(item_first)), QStandardItem(item_second)]

        self.standard_item_model.appendRow(two_columns_item)

        return      

    def get_id_tree_view(self, new_index=None,
                         tree_view_object=None,):

        try:
            if not new_index is None:

                index = new_index.indexes()[0].data()#.toPyObject()

                if isinstance(index, QVariant):
                    print "index", index.toString()
                    
        except IndexError as InErr:
            print "InErr", InErr

    def populate(self):
        for i in self.items:
            self.populate_data_item(i)

        

    def init_signal_clicked_push_button(self):
        
        self.push_button_load.clicked.connect(self.populate)

        self.push_button_hide_column.clicked.connect(self.hide_column)
        
           
def main():
    app = QApplication(sys.argv)
    window = MyCustomDialog()
    window.resize(300, 50)
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ich habe echt den Kopf zerbrochen, und bin auf eine, aus meiner Sicht, recht unzufriedene Lösung gekommen. Ich habe den nachfolgenden Quelltext noch einmal neu strukturiert. Aber der eigentliche Kern ist, dass ich durch einen blöden, aber wirklich blöden Zufall auf die setMinimumSize()-Methode gestoßen bin, und diese dann auf QTreeView() anwende. Würde ich diese besagte Methode beiseite lassen, würde sich die QTreeView() gar nicht erst per Drop-Down in der QTreeViewQComboBox() anzeigen lassen. Irgendwie erscheint mir meine Lösung nicht ganz Spruchreif. Vielleicht hat jemand eine bessere Idee? Ich meine, es kann ja nicht sein, dass es tatsächlich von der setMinimumSize() -Methode abhängt? Des Weiteren musste ich die resizeEvent()-Methode implementieren, damit ich die Breite der ComboBox bekomme, um die QTreeView durch die setMinimumSize()-Methode anpassen zu können, und es wird auch gleich die QTreeView() in der implementierten resizeEvent()-Methode angepasst.

Code: Alles auswählen

import sys
from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtGui import QApplication, QStandardItemModel, QStandardItem, QTreeView, QComboBox, QDialog, \
                         QVBoxLayout, QPushButton
 
class MyCustomDialog(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
 
        # First of all, it will be vreated a attribute
        combo_width = 0

        # Second, we need our QTreeView() and
        # the settings
        self.tree_view = QTreeView(self)
        self.tree_view.setMinimumSize(combo_width, 120)
        self.tree_view.header().hide()
 
        # Create an empty model for the TreeViews' data
        _standard_item_model = QStandardItemModel(0,2)

       # Add some textual items
        self.food_list = [
                    ["0", 'Cookie dough'], 
                    ["1", 'Hummus'], 
                    ["2", 'Spaghetti'], 
                    ["3", 'Dal makhani'], 
                    ["4", 'Chocolate whipped cream'] 
                ]
        # Now its time to populate data
        self.populate(model=_standard_item_model)

        # Well we also want to set the header
        self.set_header_data(list_header_data = ["ID", "Genre"], model=_standard_item_model)

        # QComboBox() will be created 
        self.combo_box = QComboBox(self)

        # we have to initialize the QComboBox() and apply the model
        self.init_combo(model=_standard_item_model)

        # set the selectionChanged to QTreeView()
        self.selection_changed()

        # layout is a defined QVBoxLayout()
        layout = QVBoxLayout(self)
        layout.addWidget(self.combo_box)
        self.setLayout(layout)

    def init_combo(self, model=None):

        self.combo_box.setView(self.tree_view)        
        self.combo_box.setModel(model)
 
    def selection_changed(self):
        
        self.tree_view.selectionModel().selectionChanged.connect(lambda new_index:
                    self.get_id_tree_view(new_index=new_index, tree_view_object=self.tree_view))
 
 
    def generator_header_data(self, list_header):
 
        for header_data in list_header:
            yield header_data
           
    def set_header_data(self, list_header_data=None, model=None):
 
        count_column = 0
 
        for header_data in self.generator_header_data(list_header_data):
            model.setHeaderData(count_column, Qt.Horizontal, header_data)
 
            count_column +=1 
 
    def get_id_tree_view(self, new_index=None,
                         tree_view_object=None,):
 
        try:
            if not new_index is None:
 
                index = new_index.indexes()[1].data()#.toPyObject()
 
                if isinstance(index, QVariant):
                    print "index", index.toString()
                   
        except IndexError as InErr:
            print "InErr", InErr

    def populate_data_item(self, item_list=None, model=None):
        
        count_items = len(item_list)
 
        if count_items == 2:
 
            item_first, item_second = item_list
 
        two_columns_item = [QStandardItem(item_second), QStandardItem(str(item_first))]
 
        model.appendRow(two_columns_item)
 
    def populate(self, model=None):
        
        for single_list in self.food_list:
            self.populate_data_item(item_list=single_list, model=model)

    def resizeEvent(self, evt=None):
        '''
            We need to re-implement the resizeEvent()-method
            to get width of the QComboBox() and to set the
            QTreeView()
        '''
        combo_width = self.combo_box.width()
        self.tree_view.resizeColumnToContents(0)
        self.tree_view.setColumnHidden(1, True)
           
def main():
    app = QApplication(sys.argv)
    window = MyCustomDialog()
    window.resize(300, 50)
    window.show()
    sys.exit(app.exec_())
 
if __name__ == "__main__":
    main()
BlackJack

@Sophus: Nur geraten, aber vielleicht würde es auch einfach helfen wenn man den `QTreeView` zu einem Layout hinzufügt damit Qt weiss wo und wie gross es angezeigt werden kann/soll‽
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Auf die Idee bin ich ganz zu Anfang gekommen, als ich QTreeView() unabsichtlich zum Layout hinzugefügt habe. Aber dann hält das Layout QTreeView() tatsächlich fest, und das Objekt erscheint dann nicht mehr in der QComboBox(). Die setView()-Methode greift hier dann nicht mehr, da QTreeView() vom Layout festgekrallt wird.
BlackJack

@Sophus: Okay, es war wie gesagt nur geraten, ich wusste nicht das Du den TreeView *in* der Combobox anzeigen wolltest.

Wobei mir da gerade auffällt das bei `Combobox.setView()` eine Anmerkung in der Dokumentation steht die Du nicht beachtest: Das Model soll *vor* dem Aufruf dieser Methode der Combobox hinzugefügt werden. Keine Ahnung ob das irgendwelche Auswirkungen auf Dein Problem hat.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Ich habe deine Anmerkung berücksichtigt, und die setModel()-Methode zuerst auf die QComboBox() angewendet, ehe ich dann im Anschluss die setView()-Methode auf die QComboBox() anwende. Soweit macht das Programm, was es soll. Eine Kleinigkeit fehlt mir jedoch. Diese findet ihr unter Meine Wunschliste - Verbesserungswusch.

Änderungen im Überblick:

- In Zeile 13 habe ich die setMinimumSize()-Methode durch die setRootIsDecorated(False)()-Methode ersetzt. Bei dem Boolean-Wert ist es gleich ob True oder False. Ich habe es auf False gesetzt, da ich in meinem Fall nicht mit der Hierarchie arbeiten will, sondern nur mit Zeilen und Spalten.

- Ich konnte die resizeEvent()-Methode, die ich zuvor re-implementiert habe, wieder entfernen. Die Breite und Höhe der QTreeView() wird automatisch berechnet.

- Mit einem Blick in die populate_data_item()-Methode (Zeile 112) habe ich die Anordnung der Daten-Elemente (in Zeile 120) geändert. Zuvor habe ich erst die IDs, und dann die Daten ausgeben lassen. Da ich aber vordergründig die Daten ausgeben will, und nur im Hintergrund mit den IDs arbeiten will, habe ich die Reihenfolge getauscht. Erst die Daten, dann die IDs. Die IDs sind jetzt in der zweiten Spalte.

Meine Wunschliste - Verbesserungswusch:

- In Zeile 69 ist die on_combo_box_changed()-Methode zu sehen. Man kennt ja die Eigentümlichkeiten bei ComboBoxen. Wenn dieser aktiviert wurde, kann man mit den Cursor-Tasen (nach oben und nach unten) durch die Daten in der ComboBox navigieren. Bei einer einfachen Verwendung führt man entweder die currentIndexChanged ()- und/oder activated[str]()-Signale auf der ComboBox aus, um die entsprechenden Eintragenen ausgeben zu können, damit man weiß, welches Element der Benutzer nun in der ComboBox ausgewählt hat. Leider klappt das in meinem Fall nicht. Wenn ich die ComboBox (zum Beispiel durch die TAB-Taste) aktiviere und mit den Cursor-Tasten navigiere, bekomme ich nicht das aktuelle Daten-Element. Und dann will ich an die IDs kommen, die in der zweiten Spalte sind.

Hat jemand eine Idee?

Code: Alles auswählen

import sys
from PyQt4.QtCore import Qt, QVariant
from PyQt4.QtGui import QApplication, QStandardItemModel, QStandardItem, QTreeView, QComboBox, QDialog, \
                         QVBoxLayout, QPushButton
 
class MyCustomDialog(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        # Second, we need our QTreeView() and
        # the settings
        self.tree_view = QTreeView(self)
        self.tree_view.setRootIsDecorated(False)
        self.tree_view.setWordWrap(True)
        self.tree_view.header().hide()
 
        # Create an empty model for the TreeViews' data
        _standard_item_model = QStandardItemModel(0,2)

       # Add some textual items
        self.food_list = [
                    ["0", 'Cookie dough'], 
                    ["1", 'Hummus'], 
                    ["2", 'Spaghetti'], 
                    ["3", 'Dal makhani'],
                    ["666", 'Hummus'], 
                    ["4", 'Spaghetti'], 
                    ["3", 'Dal makhani'], 
                    ["4", 'Chocolate whipped cream'] 
                ]
        # Now its time to populate data
        self.populate(model=_standard_item_model)

        # Well we also want to set the header
        self.set_header_data(list_header_data = ["ID", "Genre"], model=_standard_item_model)

        # Apply the model to the list view
        self.set_tree_view_model(_standard_item_model)

        # QComboBox() will be created 
        self.combo_box = QComboBox(self)

        # we have to initialize the QComboBox()
        self.init_combo(model=_standard_item_model)

        # set the selectionChanged to QTreeView()
        self.selection_changed()

        # layout is a defined QVBoxLayout()
        layout = QVBoxLayout(self)
        layout.addWidget(self.combo_box)
        self.setLayout(layout)

    def set_tree_view_model(self, model):
        
        self.tree_view.setModel(model)

    def init_combo(self, model=None):
        
        self.combo_box.setModel(model)
        self.combo_box.setView(self.tree_view)

        self.combo_box.currentIndexChanged.connect(lambda:
                                              self.on_combo_box_changed(model_index=self.combo_box.view().currentIndex()))

        self.tree_view.resizeColumnToContents(0)
        self.tree_view.setColumnHidden(1, True)

    def on_combo_box_changed(self, model_index=None):
        '''display text of selected item '''

        # Get the index first,

        new_indx = model_index.data(0).toString() # data method of QModelIndex is a
                                                 # convenient method for getting the d
                                                 # isplay text for that particular index

        print "index", new_indx
 
    def selection_changed(self):
        
        self.tree_view.selectionModel().selectionChanged.connect(lambda new_index:
                    self.get_id_tree_view(new_index=new_index))        
 
    def generator_header_data(self, list_header):
 
        for header_data in list_header:
            yield header_data
           
    def set_header_data(self, list_header_data=None, model=None):
 
        count_column = 0
 
        for header_data in self.generator_header_data(list_header_data):
            model.setHeaderData(count_column, Qt.Horizontal, header_data)
 
            count_column +=1 
 
    def get_id_tree_view(self, new_index=None):
 
        try:
            if not new_index is None:
 
                index = new_index.indexes()[1].data()#.toPyObject()
 
                if isinstance(index, QVariant):
                    print "index", index.toString()
                   
        except IndexError as InErr:
            pass#print "InErr", InErr

    def populate_data_item(self, item_list=None, model=None):
        
        count_items = len(item_list)
 
        if count_items == 2:
 
            item_first, item_second = item_list
 
        two_columns_item = [QStandardItem(item_second), QStandardItem(str(item_first))]
 
        model.appendRow(two_columns_item)
 
    def populate(self, model=None):
        
        for single_list in self.food_list:
            self.populate_data_item(item_list=single_list, model=model)

           
def main():
    app = QApplication(sys.argv)
    window = MyCustomDialog()
    window.resize(300, 50)
    window.show()
    sys.exit(app.exec_())
 
if __name__ == "__main__":
    main()
Antworten