Search code examples
pythonpysideqtreeviewqtreewidgetqabstractitemmodel

Comparing QTreeView/QAbstractItemModel to QTreeWidget


I'm building a tool in PySide for Maya (3D software) that auto versions and saves files in a directory.

I've spent the past few days trying to convert my initial QTreeWidget code into a model/view pattern with QTreeView and QAbstractItemModel to get tool tips and other functions, but I'm finding it harder than anticipated. When I'm learning a new coding language/technique I like to find two scripts that do the same thing- one with the new technique and one with the old technique. This way I can compare and breakdown what the new code is doing.

My big obstacle is that I'm having trouble finding a QTreeView sample that does what my QTreeWidget sample does. Also, most examples manually populate the QTreeView which doesn't help me much either. It would be great if someone could modify the QTreeView code so it does what my QTreeWidget code does. Comments on best practices for QTreeView would be great as well!

My QTreeWidget code currently...

1) gets a list of strings from a source and lists them in the first column

2) gets the date from each string and places it in the second column

QTreeView code:

from PySide import QtCore, QtGui
from shiboken import wrapInstance
import maya.OpenMayaUI as mui
import sys, os

def get_parent():
    ptr = mui.MQtUtil.mainWindow()
    return wrapInstance( long( ptr ), QtGui.QWidget )   

################################################################################
class MyTree(QtGui.QMainWindow):
    def __init__(self, parent=get_parent() ):
        super(MyTree, self).__init__(parent)

        data = MyData.init()
        frame = QtGui.QFrame();
        frame.setLayout( QtGui.QHBoxLayout() );

        treeViewModel = TreeViewModel(data)
        treeView = Widget_TreeView(treeViewModel)
        frame.layout().addWidget( treeView );

        self.setCentralWidget(frame)

################################################################################
class MyData():
    def __init__(self, txt, parent=None):
        self.txt = txt
        self.tooltip = None
        self.parent = parent
        self.child = []
        self.icon = []
        self.index = None
        self.widget = None

    #---------------------------------------------------------------------------
    # test initialization
    @staticmethod
    def init():
        root = MyData("root")
        root.tooltip = "root tooltip"
        for i in range(0, 2):
            child1 = MyData("child %i" % (i), root)
            child1.tooltip = "child1 tooltip"
            root.child.append(child1)
            for x in range(0, 2):
                child2 = MyData("child %i %i" % (i, x), child1)
                child2.tooltip = "child2 tooltip"
                child1.child.append(child2)
        return root

        # my failed attempt at adding my own data.  
        '''
        path = "C:\Program Files"
        contents = os.listdir( path )
        data_list = []
        for item in contents:
            _data = MyData(item)
            _data.tooltip = "_data tooltip"
            data_list.append(_data)
        return data_list # [0] adding this adds the first item to the UI, 
                         # but i need every item from the directory
        '''

    ################################################################################
class TreeViewModel(QtCore.QAbstractItemModel):
    #---------------------------------------------------------------------------
    def __init__(self, tree):
        super(TreeViewModel, self).__init__()
        self.__tree = tree
        self.__view = None

    #---------------------------------------------------------------------------
    def flags(self, index):
        flag = QtCore.Qt.ItemIsEnabled
        if index.isValid():
            flag |= QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable 
        return flag

    #---------------------------------------------------------------------------
    def index(self, row, column, parent=QtCore.QModelIndex()):
        node = QtCore.QModelIndex()
        if parent.isValid():
            nodeS = parent.internalPointer()
            nodeX = nodeS.child[row]
            node = self.__createIndex(row, column, nodeX)
        else:
            node = self.__createIndex(row, column, self.__tree)
        return node

    #---------------------------------------------------------------------------
    def parent(self, index):
        return QtCore.QModelIndex()
    #---------------------------------------------------------------------------
    def rowCount(self, index=QtCore.QModelIndex()):
        count = 1
        node = index.internalPointer()
        if node is not None:
            count = len(node.child)
        return count

    #---------------------------------------------------------------------------
    def columnCount(self, index=QtCore.QModelIndex()):
        return 2

    #---------------------------------------------------------------------------
    def data(self, index, role=QtCore.Qt.DisplayRole):
        data = None
        return data

    #---------------------------------------------------------------------------
    def setView(self, view):
        self.__view = view

    #---------------------------------------------------------------------------
    def __createIndex(self, row, column, node):
        if node.index == None:
            index = self.createIndex(row, column, node)
            node.index = index
        if node.widget is None:
            node.widget = Widget_Tooltip(node)
            self.__view.setIndexWidget(index, node.widget)
        return node.index

################################################################################
class Widget_TreeView(QtGui.QTreeView):
    #---------------------------------------------------------------------------
    def __init__(self, model, parent=None):
        super(Widget_TreeView, self).__init__(parent)
        self.setModel(model)
        #self.setIndentation(0)
        model.setView(self)
        root = model.index(0,0)

################################################################################
class Widget_Tooltip(QtGui.QWidget):
    #---------------------------------------------------------------------------
    def __init__(self, node):
        super(Widget_Tooltip, self).__init__()

        # Vars
        self.node = node
        self.txt = None
        # Commands
        self.create_tooltip(self.node)

    ############################################
    def create_tooltip(self, node):
        layout = QtGui.QHBoxLayout()
        self.txt = QtGui.QLabel( node.txt)
        self.txt.setToolTip("Text tooltip %s %s" % (node.txt, node.tooltip))
        layout.addWidget(self.txt, 1)
        self.setLayout(layout)

################################################################################
if __name__ == '__main__':
    try:
        form_ui.close()
        form_ui.deleteLater()
    except:
        pass

    form_ui = MyTree()
    form_ui.show()

    try:
        form_ui.show()
    except:
        form_ui.close()
        form_ui.deleteLater()

QTreeWidget code:

import sys, os, time
from PySide import QtCore, QtGui
from shiboken import wrapInstance
import maya.OpenMayaUI as mui

def get_parent():
    ptr = mui.MQtUtil.mainWindow()
    return wrapInstance( long( ptr ), QtGui.QWidget )   

class Main_Window(QtGui.QDialog):
    def __init__(self, parent = get_parent()):
        super(Main_Window, self).__init__(parent)



        # Commands
        self.create_gui()
        self.create_layout()
        self.get_contents( None )

    def create_gui( self ):
        self.tw_file_list = File_List( self )
        self.parent = self.tw_file_list.invisibleRootItem()

    def create_layout( self ):
        self.layout = QtGui.QHBoxLayout( self )
        self.layout.addWidget(self.tw_file_list)
        self.setLayout( self.layout )

    def get_contents( self, path ):
        self.tw_file_list.clear()
        path = "C:\Program Files"
        contents = os.listdir( path )

        for item in contents:
            print item
            parent = self.tw_file_list.invisibleRootItem()
            date = self.get_date( item, path)
            self.add_item(item, date, parent)

    def add_item(self, name, date, parent):
        item = QtGui.QTreeWidgetItem(parent)
        item.setText(0, name)
        item.setText(1, date)
        item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled )
        return item

    def get_date( self, item, path):
        path = "C:\Program Files"
        file = str(path + "/" + item)
        date = time.localtime(os.path.getmtime(file))
        clean_date = "{0}_{1}_{2}  {3}:{4}".format( date[0], date[1], date[2], date[3], str(date[4]).zfill(2) )
        return clean_date    

############################################
class File_List( QtGui.QTreeWidget ):
    ''' Create the file filters '''
    def __init__( self, parent=get_parent() ):
        super( File_List, self ).__init__( parent )

        # Setup UI
        self.setColumnCount(2)
        self.setHeaderLabels(["name","date"])
        self.parent = self.invisibleRootItem()

############################################
if __name__ == "__main__":
    # Workaround hack for a PySide bug within maya
    try:
        main_ui.close()
        main_ui.deleteLater()
    except:
        pass

    # Show stuff
    main_ui = Main_Window()
    main_ui.show()

    try:
        main_ui.show()
    except:
        main_ui.close()
        main_ui.deleteLater()

Solution

  • Here's your QTreeWidget example, simplified:

    import sys
    from PySide import QtCore, QtGui
    class File_List( QtGui.QTreeWidget ):
        def __init__( self, parent=None):
            super( File_List, self ).__init__( parent )
            self.setColumnCount(2)
            self.setHeaderLabels(["name","date"])
            self.get_contents()
    
        def get_contents( self):
            self.clear()
            contents = ["path1","path2"]
            for path in contents:
                date = self.get_date(path)
                self.add_item(path,date)
    
        def add_item(self, name, date):
            item = QtGui.QTreeWidgetItem(self)
            item.setText(0, name)
            item.setText(1, date)
            item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled )
            return item
    
        def get_date(self, path):
            return "a date"
    
    if __name__ == "__main__":  
        app = QtGui.QApplication(sys.argv)
        win= File_List()
        win.show() 
        sys.exit(app.exec_())
    

    And here's the same thing with a QTreeView and a QStandardItemModel (+ how to add children):

    import sys
    from PySide import QtCore, QtGui
    
    class MyModel(QtGui.QStandardItemModel):
        def __init__(self, parent=None):
            super(MyModel, self).__init__(parent)
            self.get_contents()
    
        def get_contents(self):
            self.clear()
            contents=["path1","path2"]
            for path in contents:
                date = self.get_date(path)
                self.add_item(path,date)
    
        def add_item(self,name,date):
            item1 = QtGui.QStandardItem(name)
            item2 = QtGui.QStandardItem(date)
            self.appendRow([item1, item2])
            #to append child items
            childItem=QtGui.QStandardItem("child")
            item1.appendRow(childItem)
    
        def get_date(self, path):
            return "a date"
    
    
    if __name__ == "__main__":  
        app = QtGui.QApplication(sys.argv)
        model=MyModel()
        treeView=QtGui.QTreeView()
        treeView.setModel(model)
        model.setHorizontalHeaderLabels(["name","date"])
        treeView.show()
        sys.exit(app.exec_())