QTreeWidget для отражения словаря Python

Есть ли способ сделать QTreeWidget зеркальным отражением изменений, внесенных во внутреннюю структуру данных, такую ​​как словарь? Похоже, что они создали эту функциональность в API, потому что есть много программ, которые могут взаимодействовать с QTreeWidget из нескольких областей графического интерфейса, но основная цель, требуемая от QTreeWidget, — показать структуру данных в любой момент времени. Мне не так просто понять документацию для QtGui элементов, поскольку она обычно относится к документации C, и я не уверен, как она переносится на python.

Итак, по сути, я хотел бы, чтобы самый простой способ сделать QTreeWidget отображать вложенный словарь, где верхний уровень соответствует ключам, а подуровень соответствует значениям. Кроме того, если значения являются словарями, используйте ключи на этом уровне и создайте подуровни для значений и т. д.

Легко ли это выполнимо? Мне пока не удалось найти ничего, что могло бы сделать простое зеркалирование таких структур данных.


person chase    schedule 15.02.2014    source источник
comment
Этот вопрос выглядит похоже, но дает только общее процедура, к сожалению, нет кода для подражания. Но это может помочь вам   -  person M4rtini    schedule 16.02.2014
comment
Хотя вы можете сделать это с помощью QTreeWidget, вам действительно следует использовать QTreeView, поддерживаемый моделью. В противном случае вам придется постоянно заново заполнять все дерево, чтобы отражать изменения.   -  person anonymous    schedule 07.09.2017


Ответы (4)


Это простая реализация:

def fill_item(item, value):
  item.setExpanded(True)
  if type(value) is dict:
    for key, val in sorted(value.iteritems()):
      child = QTreeWidgetItem()
      child.setText(0, unicode(key))
      item.addChild(child)
      fill_item(child, val)
  elif type(value) is list:
    for val in value:
      child = QTreeWidgetItem()
      item.addChild(child)
      if type(val) is dict:      
        child.setText(0, '[dict]')
        fill_item(child, val)
      elif type(val) is list:
        child.setText(0, '[list]')
        fill_item(child, val)
      else:
        child.setText(0, unicode(val))              
      child.setExpanded(True)
  else:
    child = QTreeWidgetItem()
    child.setText(0, unicode(value))
    item.addChild(child)

def fill_widget(widget, value):
  widget.clear()
  fill_item(widget.invisibleRootItem(), value)

Я добавил поддержку списков на всякий случай, если это кому-то понадобится.

Применение:

d = { 'key1': 'value1', 
  'key2': 'value2',
  'key3': [1,2,3, { 1: 3, 7 : 9}],
  'key4': object(),
  'key5': { 'another key1' : 'another value1',
            'another key2' : 'another value2'} }

widget = QTreeWidget()
fill_widget(widget, d)
widget.show()

Результат:

скриншот

person Pavel Strakhov    schedule 16.02.2014
comment
Это абсолютно здорово! Большое спасибо. Это замечательно для вызова каждый раз, когда кнопка используется для вызова какой-либо функции, которая изменяет словарь. В итоге я вызывал treeWidget.clear() перед fill_item и treeWidget.show() при каждом вызове функции из кнопок, которые изменяют структуру данных. - person chase; 16.02.2014
comment
Я добавил функцию fill_widget в свой код для удобства. - person Pavel Strakhov; 16.02.2014

Просто потому, что мне недавно понадобилась эта реализация для Python3 и PyQt5, вот немного более короткий (и полный) порт данного примера:

from PyQt5.QtWidgets import  QApplication, QTreeWidget, QTreeWidgetItem

class ViewTree(QTreeWidget):
    def __init__(self, value):
        super().__init__()
        def fill_item(item, value):
            def new_item(parent, text, val=None):
                child = QTreeWidgetItem([text])
                fill_item(child, val)
                parent.addChild(child)
                child.setExpanded(True)
            if value is None: return
            elif isinstance(value, dict):
                for key, val in sorted(value.items()):
                    new_item(item, str(key), val)
            elif isinstance(value, (list, tuple)):
                for val in value:
                    text = (str(val) if not isinstance(val, (dict, list, tuple))
                            else '[%s]' % type(val).__name__)
                    new_item(item, text, val) 
            else:
                new_item(item, str(value))

        fill_item(self.invisibleRootItem(), value)

if __name__ == '__main__':
    app = QApplication([])
    window = ViewTree({ 'key1': 'value1', 'key3': [1,2,3, { 1: 3, 7 : 9}]})
    window.show()
    app.exec_()
person frans    schedule 07.09.2017

Я обнаружил, что рекурсия делает ее довольно медленной при создании древовидной структуры огромной структуры каталогов. Способ ниже может помочь.

  def update_right_dock(self, file_list):   
            obj_list = []
            maps = []
            level = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]         #Stores Maximum and Current Levels For The TreeWidget
            level_name = ["", "", "", "", "", "", "", "", "", "", "", "", "", ""]   #Stores Previous File Path To Compare before adding file or folder to tree
            tree.clear()
            prev = ""
            tot_len = 2

            p = 0
            for file in file_list:
                if(os.path.isdir(file)):
                    is_file = 0
                else:
                    is_file = 1

                tmp_map = []

                file = file[1:]
                abs_path = file.split('/')
                abs_path_len = len(abs_path)
                filename = abs_path[-1]

                if(prev == file[:tot_len - 1]):
                    #print("LOOOOOOOOOOOOP ------ 1")
                    while (i < abs_path_len - 1):
                        level[level_counter + 1] = QTreeWidgetItem(level[level_counter], [abs_path[i]])

                        tmp_map.append(level[level_counter + 1])
                        tmp_map.append(level_counter + 1)
                        tmp_map.append(1)
                        obj_list.append(tmp_map)
                        tmp_map = []

                        level[level_counter + 1].setCheckState(0, Qt.Checked)
                        tree.expandItem(level[level_counter + 1])
                        level_counter = level_counter + 1
                        level_name[i] = abs_path[i]
                        i = i + 1
                    level[level_counter + 1] = QTreeWidgetItem(level[level_counter], [abs_path[i]])

                    tmp_map.append(level[level_counter + 1])
                    tmp_map.append(level_counter + 1)
                    tmp_map.append(0)
                    obj_list.append(tmp_map)
                    tmp_map = []

                    level[level_counter + 1].setCheckState(0, Qt.Checked)
                    tree.expandItem(level[level_counter + 1])

                    file_len = len(filename)
                    tot_len = len(file) - file_len
                    prev = file[:tot_len - 1]
                    continue


                len2 = len(level_name)
                k = 0
                while k < abs_path_len and k < len2:
                    if (level_name[k] == abs_path[k]):
                        k  = k + 1
                        continue
                    break
                level_counter = k + 1
                i = level_counter - 1
                while k < abs_path_len:
                    level_name[k] = abs_path[k]
                    k = k + 1

                if level_counter > 1:
                    #print("LOOOOOOOOOOOOP ------ 2")
                    if(i == abs_path_len - 1):
                        level_counter = level_counter - 1
                    while i < abs_path_len - 1:
                        level[level_counter] = QTreeWidgetItem(level[level_counter - 1], [abs_path[i]])

                        tmp_map.append(level[level_counter])
                        tmp_map.append(level_counter)
                        tmp_map.append(1)
                        obj_list.append(tmp_map)
                        tmp_map = []

                        level[level_counter].setCheckState(0, Qt.Checked)
                        tree.expandItem(level[level_counter])
                        level_counter = level_counter + 1
                        level_name[i] = abs_path[i]

                        i = i + 1
                        if i == abs_path_len - 1:
                                level_counter = level_counter - 1

                    level[level_counter + 1] = QTreeWidgetItem(level[level_counter], [abs_path[i]])

                    tmp_map.append(level[level_counter + 1])
                    tmp_map.append(level_counter + 1)
                    tmp_map.append(0)
                    obj_list.append(tmp_map)
                    tmp_map = []

                    level[level_counter + 1].setCheckState(0, Qt.Checked)
                    tree.expandItem(level[level_counter + 1])

                    file_len = len(filename)
                    tot_len = len(file) - file_len
                    prev = file[:tot_len - 1]
                    continue

                if(abs_path_len == 1):
                    level[level_counter + 1] = QTreeWidgetItem(tree, [abs_path[i]])

                    tmp_map.append(level[level_counter + 1])
                    tmp_map.append(level_counter + 1)
                    tmp_map.append(0)
                    obj_list.append(tmp_map)
                    tmp_map = []

                    level[level_counter + 1].setCheckState(0, Qt.Checked)
                    tree.expandItem(level[level_counter + 1])
                    continue

                i = 1    
                #print("LOOOOOOOOOOOOP ------ 3")
                level[level_counter] = QTreeWidgetItem(tree, [abs_path[0]])

                tmp_map.append(level[level_counter])
                tmp_map.append(level_counter)
                tmp_map.append(1)
                obj_list.append(tmp_map)
                tmp_map = []

                level[level_counter].setCheckState(0, Qt.Checked)
                tree.expandItem(level[level_counter])
                level_name[level_counter - 1] = abs_path[0]       

                while i < abs_path_len - 1:
                    level[level_counter + 1] = QTreeWidgetItem(level[level_counter], [abs_path[i]])
                    tmp_map.append(level[level_counter + 1])
                    tmp_map.append(level_counter + 1)
                    tmp_map.append(1)
                    obj_list.append(tmp_map)
                    tmp_map = []

                    level[level_counter + 1].setCheckState(0, Qt.Checked)
                    tree.expandItem(level[level_counter + 1])
                    level_counter = level_counter + 1
                    level_name[i] = abs_path[i]
                    if i == abs_path_len - 1:
                            level_counter = level_counter - 1
                    i = i + 1

                level[level_counter + 1] = QTreeWidgetItem(level[level_counter], [abs_path[i]])
                tmp_map.append(level[level_counter + 1])
                tmp_map.append(level_counter + 1)
                tmp_map.append(0)
                obj_list.append(tmp_map)
                tmp_map = []

                level[level_counter + 1].setCheckState(0, Qt.Checked)
                tree.expandItem(level[level_counter + 1])
                level_name[i] = abs_path[i]

                file_len = len(filename)
                tot_len = len(file) - file_len
                prev = file[:tot_len - 1]
                p = p + 1
person Asit Rout    schedule 07.09.2017

Несколько дней назад я искал такую ​​тему, но все, что я мог найти - как заполнить дерево, а не как его извлечь. Поэтому я пишу некоторые. Может быть, кто-то еще найдет это полезным. Спасибо предыдущему автору, я использовал его код с некоторыми улучшениями. Это загрузит вашу коллекцию (это может быть список, словарь или кортеж со многими дочерними элементами)

def load_tree(self, d):
    self.fill_widget(self.tree, d)

def fill_widget(self, widget, value):
    widget.clear()
    self.fill_item(widget.invisibleRootItem(), value)

def fill_item(self, item, value):
    def new_item(parent, text):
        child = QTreeWidgetItem([str(text)])
        if text not in ("[dict]", "[list]", "[tuple]"):
            child.setFlags(child.flags() | Qt.ItemIsEditable)
        parent.addChild(child)
        child.setExpanded(True)

        return child

    if isinstance(value, dict):
        new_parent = new_item(item, f"[{value.__class__.__name__}]")
        for elem_k, elem_v in value.items():
            sub_parent = new_item(new_parent, elem_k)
            self.fill_item(sub_parent, elem_v)
    elif isinstance(value, (tuple, list)):
        new_parent = new_item(item, f"[{value.__class__.__name__}]")
        for val in value:
            self.fill_item(new_parent, val)
    else:
        new_item(item, f"{value}")

И извлеките код немного сложнее. Если кто-то может сделать его более причудливым - пожалуйста.

def get_dict(self):
    result = None

    def unpack(to_unpack, key, source=None):
        for child_index in range(to_unpack.childCount()):
            child = to_unpack.child(child_index)
            child_text = child.text(0)
            try:
                child_text = float(child_text)
            except ValueError:
                try:
                    child_text = int(child_text)
                except ValueError:
                    pass

            if source is None:
                core = result
            else:
                core = source

            if key == "[dict]":
                core.update({child_text: None})
                if child.childCount() > 0:
                    unpack(child, child_text, core)
            elif key == "[list]" or key == "[tuple]":
                if child_text == "[dict]":
                    core.append({})
                elif child_text == "[list]" or child_text == "[tuple]":
                    core.append([])
                else:
                    core.append(child_text)

                if child.childCount() > 0:
                    unpack(child, child_text, core[child_index])
            else:
                if child_text == "[dict]":
                    core.update({key: {}})
                elif child_text == "[list]" or child_text == "[tuple]":
                    core.update({key: []})
                else:
                    core.update({key: child_text})

                if child.childCount() > 0:
                    unpack(child, child_text, core[key])

    for index in range(self.tree.topLevelItemCount()):
        parent = self.tree.topLevelItem(index)
        element_text = parent.text(0)
        if element_text == "[dict]":
            result = {}
            unpack(parent, element_text)
        elif element_text == "[list]" or element_text == "[tuple]":
            result = []
            unpack(parent, element_text)
        else:
            result = element_text

    return result

Где self.tree - объект QTreeWidget в вашем окне.

person Windy    schedule 31.08.2018