Home > other >  Creating a Pyqt QtableWidget from a dictionary with list of values, and back to a dictionary
Creating a Pyqt QtableWidget from a dictionary with list of values, and back to a dictionary

Time:09-30

Following on from an earlier question I can now create a Pyqt QTreeWidget from a dictionary, edit the tree and save it to and edited dictionary. However when I comes to dictionary values which contain a list of dictionaries, it compiles the values under the same parent (see dictionary where the 'Property' key contains a list of dictionaries, and output to QTreeWidget below)

self.d = {'TestName': {'Ref': 'ABC/DEF', 'Property': [{'Number': '2', 'Zipcode': '0002234',
                                                           'KeyAvailable': 'Yes'}, {'Number': '3',
                                                                                    'Zipcode': '2342444'}]}}

QtreeWidget

For neatness it would probably be nicer to have another parent of 'Property' for the second list item (dictionary). The main problem I have is converting back into a dictionary, as at the moment the last entry overwrites the previous, with the additional problem if there's an extra key in one of the dictionaries (in this case 'KeyAvailable').

Here's the output converting back to a dictionary:

{'TestName': {'Ref': 'ABC/DEF', 'Property': {'Number': '3', 'Zipcode': '2342444', 'KeyAvailable': 'Yes'}}}

Is there a simple solution for handling these list instances in my 'tree_from_dict' and 'tree_2_dict' functions below?

import sys
from PyQt5.QtWidgets import QWidget, QApplication, QTreeWidget, QTreeWidgetItem, QPushButton, QMainWindow
from PyQt5.QtCore import *


class MyMainWindow(QMainWindow):

    def __init__(self, dialog):
        QMainWindow.__init__(self)

        self.d = {'TestName': {'Ref': 'ABC/DEF', 'Property': [{'Number': '2', 'Zipcode': '0002234',
                                                               'KeyAvailable': 'Yes'}, {'Number': '3',
                                                                                        'Zipcode': '2342444'}]}}

        self.setWindowTitle('Example')
        self.setGeometry(20, 20, 400, 400)

        central = QWidget(self)
        self.setCentralWidget(central)

        self.button = QPushButton('Save button', central)
        self.button.move(100, 350)

        self.tree = QTreeWidget(self.centralWidget())
        self.tree.setGeometry(QRect(30, 30, 300, 300))

        self.tree.setColumnCount(2)
        self.tree.setHeaderLabels(["XML Property", "Value"])

        self.tree.itemDoubleClicked.connect(self.editItem)
        self.button.clicked.connect(self.save_changes)

        self.tree_from_dict(data=self.d, parent=self.tree)

    def editItem(self, item, column):

        try:
            if column == 1:
              item.setFlags(item.flags() | Qt.ItemIsEditable)
            else:
                pass
        except Exception as e:
            print(e)

    def tree_from_dict(self, data=None, parent=None):
        for key, value in data.items():
            item = QTreeWidgetItem(parent)

            item.setText(0, key)

            if isinstance(value, dict):
                self.tree_from_dict(data=value, parent=item)
            elif isinstance(value, list):
                [self.tree_from_dict(i, parent=item) for idx, i in enumerate(value)]

            else:
                item.setText(1, value)

    def save_changes(self):
        d = self.tree_2_dict(self.tree.invisibleRootItem())
        print(d)

    def tree_2_dict(self, parent, d=None):
        if d is None:
            d = {}
        for index in range(parent.childCount()):
            child = parent.child(index)
            if child.childCount():
                self.tree_2_dict(child, d.setdefault(child.text(0), {}))
            else:
                d[child.text(0)] = child.text(1)
        return d


if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = QMainWindow()
    foo = MyMainWindow(dialog)
    foo.show()
    sys.exit(app.exec_())
    

CodePudding user response:

I'm quite sure this is not the most elegant solution. The function now checks whether a list is passed as data and adds the property as many times as there are elements in that list.

def tree_from_dict(self, data=None, parent=None):
    for key, value in data.items():
        item = QTreeWidgetItem(parent)
        item.setText(0, key)

        if isinstance(value, dict):               
            self.tree_from_dict(data=value, parent=item)
        elif isinstance(value, list):               
            for idx, i in enumerate(value):
                if idx!=0:
                    item = QTreeWidgetItem(parent)
                    item.setText(0, key)
                self.tree_from_dict(i, parent=item)
        else:
            item.setText(1, value)

The other function now additionally checks whether a property is already a key of the dict. If so it converts the corresponding value to a list and appends the new value.

 def tree_2_dict(self, item, dic):
    # Variable for saving all occuring property names in that scope
    childrenNames=[]
    for index in range(item.childCount()):
        child = item.child(index)
        if child.text(0) not in childrenNames:
            childrenNames.append(child.text(0))
            if child.childCount():
                dic[child.text(0)]=self.tree_2_dict(child, {})
            else:
                dic[child.text(0)] = child.text(1)
        else:
            ele = dic[child.text(0)]
            
            if child.childCount():
                child_dic =self.tree_2_dict(child,  {})
                if isinstance(ele,list):
                    ele.append(child_dic)
                else:
                    ele=[ele,child_dic]
            else:
                if isinstance(ele,list):
                    ele.append(child.text(1))
                else:
                    ele=[ele,child.text(1)]
            dic[child.text(0)] = ele
    return dic
  • Related