Context:
I have a QTreeWidgetItem that I'd like to save as a JSON file. The QTreeWidgetItem is composed of three columns ["Name", "Type", "Value"]. name is a line edit, type is a comboBox, value is a spinBox. I'd like to retrieve value of each field and on each line to save them in the JSON
Tries:
There's no problem to retrieve the name, but I can't find how to retrieve the values from the comboBox and spinBox.
I've first tried to retrieve data in the simpliest form (node being a QTreeWidgetItem):
type = node.text(1)
value= node.text(2)
I've tried QTreeWidgetItem.data(int column, int role) but not quite understood the parameter "role" needed for this method.
Some code:
python file:
from qgis.PyQt import uic, QtCore, QtGui
import os, sys
import qgis.core
import logging
try:
from qgis.PyQt.QtGui import (QWidget,QDialog,QMainWindow,QApplication,QTreeWidgetItem,
QMenu,QToolButton,QSpinBox,QDoubleSpinBox,QHeaderView,QLineEdit,
QTextBrowser,QTreeWidgetItemIterator, QComboBox, QLabel, UserRole)
except ImportError:
from qgis.PyQt.QtWidgets import (QWidget,QDialog,QMainWindow,QApplication,QTreeWidgetItem,
QMenu,QToolButton,QSpinBox,QDoubleSpinBox,QHeaderView,QLineEdit,
QTextBrowser,QTreeWidgetItemIterator, QComboBox, QLabel)
import pprint
import json
class AMCWindow(QDialog):
def __init__(self, parent=None, dbase=None):
super(AMCWindow, self).__init__(parent=parent)
uipath = os.path.join(os.path.dirname(__file__), 'amcwindow1.ui')
uic.loadUi(uipath, self)
self.dict = {}
# treeWidget
self.treeWidget.setColumnCount(3)
self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.treeWidget.customContextMenuRequested.connect(self.openMenu)
self.maintreewdgitem = QTreeWidgetItem(self.treeWidget.invisibleRootItem(), ['note'])
headerlist = ["Name", "Type", "Value"]
self.treeWidget.setHeaderItem(QTreeWidgetItem(headerlist))
# Right click on criteria
self.menu = QMenu()
self.menu.addAction(self.tr("Add criteria"))
self.menu.addAction(self.tr("Remove criteria"))
self.menu.triggered.connect(self.menuAction)
# Click on save
self.buttonSave.clicked.connect(self.saveClicked)
self.menuitem = None
self.itemnameediting = None
def openMenu(self, position):
self.menuitem = self.treeWidget.currentItem()
self.menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
def menuAction(self, actionname):
if actionname.text() == self.tr("Add criteria"):
qtreewidgetitm = QTreeWidgetItem(self.menuitem, ["New criteria"])
# Type
qComboBox = QComboBox()
qComboBox.addItems(["Pondération", "Classe"])
self.treeWidget.setItemWidget(qtreewidgetitm, 1, qComboBox)
# qComboBox.setProperty("widgetitem", qtreewidgetitm)
# Value
spinbox = QDoubleSpinBox()
self.treeWidget.setItemWidget(qtreewidgetitm, 2, spinbox)
self.menuitem.setExpanded(True)
return qtreewidgetitm
elif actionname.text() == self.tr("Remove criteria"):
self.menuitem.parent().removeChild(self.menuitem)
def saveClicked(self):
print("> Save clicked")
self.dict.clear()
self.visitTree(self.maintreewdgitem, self.dict)
# Define directory
directory = "C://Users//jean.robertou//Desktop//file.json"
# Creates file and writes queryDict as json
with open(directory, "w") as file:
json.dump(self.dict, file)
def visitTree(self, node, dct):
"""
Iterate over the tree and build dct
:param node: QTreeWidgetItem, current node
:param dct: dict, contain elements from current node
:return:
"""
print("Enter visitTree")
print("> visitTree:", node.text(0))
# Get number of child
childNr = node.childCount()
# If node is leaf
if childNr == 0:
print(">", node.text(0), "has no child")
type = node.text(1) # <----- Need help here
value= node.text(2) # <----- Need help here
# Set default dict values
dct.setdefault("Type", type)
dct.setdefault("Value", value)
# If node has children
else:
print(">", node.text(0), "has children")
# Add children to dct
for childItem in range(childNr):
child = node.child(childItem)
dct.setdefault(child.text(0), {})
# Loop through children recursively
self.visitTree(child, dct[child.text(0)])
print("Exit visitTree")
GUI file (amcwindow1.ui):
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>941</width>
<height>518</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,10,1,1">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Nom</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QToolButton" name="buttonSave">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="columnCount">
<number>3</number>
</property>
<attribute name="headerDefaultSectionSize">
<number>50</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">3</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_sqlfinal"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Final SQL</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="toolButton_testsql">
<property name="text">
<string>Test SQL</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QTextBrowser" name="textBrowser_res">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>90</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
Expected ouput:
A single line QTreeWidgetItem, for instance: "nameExample" | Ponderation | 2
The JSON should look like:
{"nameExample": {"Type": "Ponderation", "Value": 2}
I have the following comments about your code:
I think you've had an error in the lines: self.treeWidget.setItemWidget(qtreewidgetitm, 2, qComboBox)
and self.treeWidget.setItemWidget(qtreewidgetitm, 3, spinbox)
since the columns are 2 and 3 but the indices in Qt start from 0 so they must be 1 and 2, respectively.
When you add a child, use a text different from the previous one because when you build the dictionary you will have problems because the duplicate elements will not be shown. It may not be necessary for the implementation of your project but when testing your current code, look at this problem.
If you want to obtain the data shown in the widget, you must first obtain the widget using the itemWidget()
method.
Considering the above, the solution is:
class AMCWindow(QDialog):
def __init__(self, parent=None, dbase=None):
super(AMCWindow, self).__init__(parent=parent)
uipath = os.path.join(os.path.dirname(__file__), "amcwindow1.ui")
uic.loadUi(uipath, self)
self.dict = {}
# treeWidget
self.treeWidget.setColumnCount(3)
self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.treeWidget.customContextMenuRequested.connect(self.openMenu)
self.maintreewdgitem = QTreeWidgetItem(
self.treeWidget.invisibleRootItem(), ["note"]
)
headerlist = ["Name", "Type", "Value"]
self.treeWidget.setHeaderItem(QTreeWidgetItem(headerlist))
# Right click on criteria
self.menu = QMenu()
self.menu.addAction(self.tr("Add criteria"))
self.menu.addAction(self.tr("Remove criteria"))
self.menu.triggered.connect(self.menuAction)
# Click on save
self.buttonSave.clicked.connect(self.saveClicked)
self.menuitem = None
self.itemnameediting = None
self.counter = 0
def openMenu(self, position):
self.menuitem = self.treeWidget.currentItem()
self.menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
def menuAction(self, actionname):
if actionname.text() == self.tr("Add criteria"):
qtreewidgetitm = QTreeWidgetItem(
self.menuitem, ["New criteria(%s)" % (self.counter)]
)
self.counter += 1
# Type
qComboBox = QComboBox()
qComboBox.addItems(["Pondération", "Classe"])
self.treeWidget.setItemWidget(qtreewidgetitm, 1, qComboBox)
# qComboBox.setProperty("widgetitem", qtreewidgetitm)
# Value
spinbox = QDoubleSpinBox()
self.treeWidget.setItemWidget(qtreewidgetitm, 2, spinbox)
self.menuitem.setExpanded(True)
return qtreewidgetitm
elif actionname.text() == self.tr("Remove criteria"):
self.menuitem.parent().removeChild(self.menuitem)
def saveClicked(self):
print("> Save clicked")
self.dict.clear()
self.visitTree(self.maintreewdgitem, self.dict)
# Define directory
directory = "C://Users//jean.robertou//Desktop//file.json"
# Creates file and writes queryDict as json
with open(directory, "w") as file:
json.dump(self.dict, file)
def visitTree(self, node, dct):
"""
Iterate over the tree and build dct
:param node: QTreeWidgetItem, current node
:param dct: dict, contain elements from current node
:return:
"""
print("Enter visitTree")
print("> visitTree:", node.text(0))
# Get number of child
childNr = node.childCount()
# If node is leaf
if childNr == 0:
print(">", node.text(0), "has no child")
qComboBox = self.treeWidget.itemWidget(node, 1)
type = qComboBox.currentText()
spinbox = self.treeWidget.itemWidget(node, 2)
value = spinbox.value()
dct.setdefault("Type", type)
dct.setdefault("Value", value)
# If node has children
else:
print(">", node.text(0), "has children")
# Add children to dct
for childItem in range(childNr):
child = node.child(childItem)
dct.setdefault(child.text(0), {})
# Loop through children recursively
self.visitTree(child, dct[child.text(0)])
print("Exit visitTree")