I need support to my little software with Qwidget and QTreeWidgets. see below figure.
This is how my Qwidget looks like. I want to restore Qtreewidget items and widget when I close the window and restore it with previous selection. as Marked on the figure below.As you see in my scripts below, I have used Qsettings, I have tried to fix it with pickle but does not work.
Any improvement in coding is welcoming.
from PyQt5 import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import sys
import pickle
iconroot = QFileInfo(__file__).absolutePath()
ORGANIZATION_NAME = 'Circularcolumn App'
ORGANIZATION_DOMAIN = 'Circular shape'
APPLICATION_NAME = 'QSettings program'
SETTINGS_TRAY = 'settings/tray'
class usedcircularshape(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Frequently used shape")
self.setWindowIcon(QIcon(iconroot+"/images/circularcolumnnorebar.png"))
#self.setStyleSheet("background-color:#f2f2f2")
self.addbutton = QPushButton("Add")
self.addbutton.clicked.connect(self.add)
self.deletebutton = QPushButton("Delete")
self.deletebutton.clicked.connect(self.delete)
self.okbutton = QPushButton("Ok")
self.okbutton.setCursor(Qt.PointingHandCursor)
#self.okbutton.clicked.connect(self.hidethiswindow)
self.okbutton.clicked.connect(self.savesetting)
self.cancelbutton = QPushButton("Cancel")
self.cancelbutton.setCursor(Qt.PointingHandCursor)
self.cancelbutton.clicked.connect(self.loadsetting)
#self.cancelbutton.clicked.connect(self.close)
self.addimage()
self.qlabeltodefinesection()
self.treewidget()
self.sectionnamecircular = QLabel('Section name: ')
self.sectionnamecircularindata = QLineEdit('Define en name to section')
self.sectionnamecircularindata.setObjectName("sectionnamecircularindata")
self.sectionnamecircular.setBuddy(self.sectionnamecircularindata)
self.sectionnamecircular.setFocus()
self.grid_sectionname = QHBoxLayout()
self.grid_sectionname.addWidget(self.sectionnamecircular)
self.grid_sectionname.addWidget(self.sectionnamecircularindata)
self.boxlayout = QGridLayout()
self.boxlayout.addLayout(self.grid_sectionname,0,0,1,2)
self.boxlayout.addWidget(self.treewidget,1,0,5,2)
self.boxlayout.addWidget(self.addbutton,2,2)
self.boxlayout.addWidget(self.deletebutton,3,2)
self.boxlayout.addWidget(self.imagelabel,6,0)
self.boxlayout.addLayout(self.qlabelhboxgrid ,6,1)
self.boxlayout.addWidget(self.okbutton,8,1)
self.boxlayout.addWidget(self.cancelbutton,8,2)
self.setLayout(self.boxlayout)
try:
self.loadsetting()
except ( ValueError, TypeError):
pass
def treewidget(self):
self.treewidget = QTreeWidget(self)
self.treewidget.setColumnCount(1)
self.treewidget.setColumnWidth(1,20)
self.treewidget.setHeaderItem(QTreeWidgetItem(['Standard Section Library']))
#self.treewidget.addTopLevelItem(QTreeWidgetItem(['Standard Sectiontype']))
self.treewidget.setRootIsDecorated(True)
self.firstparentitem = QTreeWidgetItem(self.treewidget)
self.firstparentitem.setText(0,'Circular shapes')
self.firstparentitem.setIcon(0,QIcon(iconroot+"/images/circularcolumnnorebar.png"))
standardsectionlist = ["D100","D150","D200","D250","D300","D350","D400","D450","D500","D550","D600","D650"
,"D700","D750","D800","D850","D900","D950","D1000"]
for i in standardsectionlist:
self.firstparentitem.addChild(QTreeWidgetItem(["%s"%i]))
self.secondparentitem = QTreeWidgetItem(self.treewidget)
self.secondparentitem.setText(0,'Customized')
self.secondparentitem.setIcon(0,QIcon(iconroot+"/images/circularcolumnnorebar.png"))
self.secondchilditem = QTreeWidgetItem(["D235"])
self.secondparentitem.insertChild(0,self.secondchilditem)
self.secondchilditem.setChildIndicatorPolicy(QTreeWidgetItem.DontShowIndicator)
self.treewidget.move(15,15)
self.treewidget.setGeometry(15,15,200,600)
self.treewidget.setAlternatingRowColors(True)
self.treewidget.expandItem ( self.firstparentitem )
self.show()
print(self.treewidget.headerItem().text(0))
print(self.treewidget.columnCount())
print(self.treewidget.currentColumn())
print(self.treewidget.indexFromItem(self.firstparentitem).row())
print(self.firstparentitem.childCount())
print(self.firstparentitem.child(1).text(0))
print(self.firstparentitem.text(0))
print(self.treewidget.headerItem().text(0))
print(self.treewidget.topLevelItem(0).text(0))
print(self.firstparentitem.isSelected())
print(self.treewidget.selectedItems())
print(self.secondchilditem.text(1))
branchstyle = '''QTreeWidget {border:none;}
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(images/vline.png) 0;}
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(images/branch-more.png) 0;}
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(images/branch-end.png) 0;}
QTreeView::branch:has-children:!has-siblings:closed,
QTreeView::branch:closed:has-children:has-siblings {
border-image: none;
image: url(images/branch-closed.png);}
QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(images/branch-open.png);}'''
self.treewidget.setStyleSheet(branchstyle)
self.treewidget.itemClicked.connect(self.currentitem)
self.treewidget.currentItemChanged.connect(self.current_item_changed)
#@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, QtWidgets.QTreeWidgetItem)
def current_item_changed(self, current, previous):
#print('\ncurrent: {}, \nprevious: {}'.format(current, previous))
print(current.text(0),previous)
def add(self):
text, ok = QInputDialog.getText(self, "Add custom section", "Enter section geometry f.ex as D325 or just 325 in mm: ")
if ok:
self.secondchilditem = QTreeWidgetItem(["%s"% text])
self.secondparentitem.insertChild(0,self.secondchilditem)
self.treewidget.expandItem ( self.secondparentitem )
self.gettext = text
print(self.secondparentitem.child(0), self.gettext)
def delete(self):
self.secondparentitem.takeChild(0)
def currentitem(self):
print(self.treewidget.currentItem().text(0),self.treewidget.selectedItems())
self.itemtext = self.treewidget.currentItem().text(0)
if self.itemtext == self.firstparentitem.text(0) or self.itemtext == self.secondparentitem.text(0):
return None
elif self.itemtext == self.treewidget.topLevelItem(0).text(0):
return None
elif self.itemtext == None:
return None
else:
self.select_circular_section = int(self.itemtext.translate({ord('D'):None}))
print(self.itemtext, self.treewidget.selectedItems, self.select_circular_section)
area = str(format(3.1416/4*(self.select_circular_section)**2,'0.2E'))
inerti = str(format(3.1416/64*pow(self.select_circular_section,4),'0.2E'))
self.qlabelcirculardiameterselected = QLabel('')
qlabelcircularareaselected = QLabel('')
qlabelcircularinertimomentselected = QLabel("")
emptylabel1 = QLabel(' ')
self.qlabelcirculardiameterselected.setText('%s mm '% self.select_circular_section)
qlabelcircularareaselected.setText('{} mm2 ' .format(area))
qlabelcircularinertimomentselected.setText("%s mm4 " %(inerti))
qlabelhboxgridselected = QGridLayout()
qlabelhboxgridselected.addWidget(emptylabel1,0,0)
qlabelhboxgridselected.addWidget(self.qlabelcirculardiameterselected,1,0)
qlabelhboxgridselected.addWidget(qlabelcircularareaselected,2,0)
qlabelhboxgridselected.addWidget(qlabelcircularinertimomentselected,3,0)
qlabelhboxgridselected.addWidget(emptylabel1,4,0,5,0)
return print(self.itemtext, self.treewidget.selectedItems, self.select_circular_section), self.boxlayout.addLayout(qlabelhboxgridselected ,6,2),self.qlabelcirculardiameterselected
def addimage(self):
self.imagelabel = QLabel()
self.circularimage = QPixmap(iconroot+"/images/circularcolumnnorebard.png").scaled(230,230,Qt.KeepAspectRatio)
self.imagelabel.setPixmap(self.circularimage)
self.imagelabel.setGeometry(15,15,15,15)
def hidethiswindow(self):
if self.itemtext == self.firstparentitem.text(0) or self.itemtext == self.secondparentitem.text(0):
QMessageBox.about(self,'Error selection','Please, select a section not a text')
elif self.itemtext == self.treewidget.topLevelItem(0).text(0):
QMessageBox.about(self,'Error selection','Please, select a section not a text')
elif self.itemtext == None:
QMessageBox.about(self,'Error selection','Please, select a section not a text')
else:
self.savesetting()
self.hide()
def qlabeltodefinesection(self):
self.qlabelcirculardiameter = QLabel(' D = ')
self.qlabelcirculararea = QLabel(' A = ')
self.qlabelcircularinertimoment = QLabel(" I = ")
self.emptylabel = QLabel(' ')
self.qlabelhboxgrid = QGridLayout()
self.qlabelhboxgrid.addWidget(self.emptylabel,0,0)
self.qlabelhboxgrid.addWidget(self.qlabelcirculardiameter,1,0)
self.qlabelhboxgrid.addWidget(self.qlabelcirculararea,2,0)
self.qlabelhboxgrid.addWidget(self.qlabelcircularinertimoment,3,0)
self.qlabelhboxgrid.addWidget(self.emptylabel,4,0,5,0)
def savesetting(self):
settings = QSettings(ORGANIZATION_NAME,APPLICATION_NAME)
#settings = QSettings('config.ini',QSettings.IniFormat)
settings.beginGroup('D')
settings.setValue(SETTINGS_TRAY,self.geometry())
settings.setValue("LineEdit",self.sectionnamecircularindata.text())
settings.setValue("Selectitem",self.treewidget.currentItem())
settings.setValue("Label",self.qlabelcirculardiameterselected)
settings.endGroup()
print('Saved', )
#self.hide()
def loadsetting(self):
settings = QSettings(ORGANIZATION_NAME,APPLICATION_NAME)
#settings = QSettings('config.ini',QSettings.IniFormat)
settings.beginGroup('D')
myrect = settings.value(SETTINGS_TRAY)
restorelineEdit = settings.value("LineEdit",'')
restoreselectsection = settings.value("Selectitem",)
restoreqlabel = settings.value("Label",'')
self.setGeometry(myrect)
self.sectionnamecircularindata.setText(restorelineEdit)
self.treewidget.setCurrentItem(restoreselectsection)
settings.endGroup()
if __name__ == "__main__":
QCoreApplication.setApplicationName(ORGANIZATION_NAME)
QCoreApplication.setOrganizationDomain(ORGANIZATION_DOMAIN)
QCoreApplication.setApplicationName(APPLICATION_NAME)
app = QApplication(sys.argv)
subwindow=usedcircularshape()
subwindow.show()
app.exec()
What does the code do?
This is engineering software, to concrete columns, the idea is a user should be able to pick a standard section e.g. D350, D means diameter and 350 is diameter of circular concrete column in unit mm. User has ability to add custom shapes of circular columns. And when user click and select in from Qtreewidget a section, this section should remain and be global available for further calculation, which is not shown her. This is a widget to geometry defining within big concrete software. I would below further explain the code. First I create Qdialog and create Qtreewidget, an image for clarification and Qlabel based an item selection. Within Qtreewidget I first create a parentitem and give it an name “ Circular shapes”
self.firstparentitem = QTreeWidgetItem(self.treewidget)
self.firstparentitem.setText(0,'Circular shapes')
Then parentitem has children, to define and add those, first I create a list with standard sections
standardsectionlist = ["D100","D150","D200","D250","D300","D350","D400","D450","D500","D550","D600","D650"
,"D700","D750","D800","D850","D900","D950","D1000"]
And do for condition to add children to parentitem.
for i in standardsectionlist:
self.firstparentitem.addChild(QTreeWidgetItem(["%s"%i]))
later I define second parentitem and give a name “Customized” and define a list then later add item of list as child to second parentitem. For user should be able to add and remove childitem from second parentitem I create 2 buttons
self.addbutton = QPushButton("Add")
self.deletebutton = QPushButton("Delete")
Addbutton has a function to add children item to second parentitem. Deletebutton has a function to remove first childitem from second parentitem.
def currentitem(self):
currentitem function has a function when user click and select an item within Qtreewidget, it takes currenitem text and remove D letter from it and convert it into int, then display it as D = Diameter, A = Area ect…
def hidethiswindow(self)
hidethiswindow function this one guide the user either to select an item or to cancel the widget, in case user by mistake has selected headeritem, a error message will arise and tell to select child item. Powerfull is’t it!
The rest code is to display and Qsettings. Hope this explains the code.
What you have to do is save the data of each item for this I have created the itemToTuple method that returns a tuple of the data to be saved, also given that saved tuple you must set those properties and for that you use the tupleToItem function (if you want add more information of the items you only have to modify those methods). But to save all the items of QTreeWidget, the whole tree must be traversed. For this, the dataFromChild method that returns the data of the item and its children is used. The inverse process, that is, given the data must be established in all the items, dataToChild must be used, which establishes the respective data to the item and its children.
Also I have divided the application into several classes to have it sorted, if you want the information of a widget to be saved you must create a method called writeSettings(...) with a format similar to the ones I show then the application will call in the closeEvent(...) method. Similarly, you can create the readSettings(...) method and call it in its constructor.
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
iconroot = os.path.dirname(__file__)
ORGANIZATION_NAME = 'Circularcolumn App'
ORGANIZATION_DOMAIN = 'Circular shape'
APPLICATION_NAME = 'QSettings program'
SETTINGS_TRAY = 'settings/tray'
QSS = """
QTreeWidget{
border:none;
}
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(images/vline.png) 0;
}
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(images/branch-more.png) 0;
}
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(images/branch-end.png) 0;
}
QTreeView::branch:has-children:!has-siblings:closed,
QTreeView::branch:closed:has-children:has-siblings {
border-image: none;
image: url(images/branch-closed.png);
}
QTreeView::branch:open:has-children:!has-siblings,
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(images/branch-open.png);
}
"""
class TreeWidget(QtWidgets.QTreeWidget):
currentTextChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(TreeWidget, self).__init__(parent)
self.currentItemChanged.connect(self.onCurrentItemChanged)
self.setHeaderLabel('Standard Section Library')
self.setRootIsDecorated(True)
self.setAlternatingRowColors(True)
self.readSettings()
self.expandAll()
def onCurrentItemChanged(self, current, previous):
if current not in [self.topLevelItem(ix) for ix in range(self.topLevelItemCount())]:
self.currentTextChanged.emit(current.text(0))
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("TreeWidget")
values = settings.value("items")
if values is None:
self.loadDefault()
else:
TreeWidget.dataToChild(values, self.invisibleRootItem())
self.customized_item = None
for ix in range(self.topLevelItemCount()):
tlevel_item = self.topLevelItem(ix)
if tlevel_item.text(0) == "Customized":
self.customized_item = tlevel_item
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("TreeWidget")
settings.setValue("items", TreeWidget.dataFromChild(self.invisibleRootItem()))
settings.endGroup()
def loadDefault(self):
standardsectionlist = ["D100","D150","D200","D250","D300","D350","D400","D450","D500",
"D550","D600","D650","D700","D750","D800","D850","D900","D950","D1000"]
rootItem = QtWidgets.QTreeWidgetItem(self, ['Circular shapes'])
rootItem.setIcon(0, QtGui.QIcon(os.path.join(iconroot,"images/circularcolumnnorebar.png")))
for element in standardsectionlist:
rootItem.addChild(QtWidgets.QTreeWidgetItem([element]))
self.customized_item = QtWidgets.QTreeWidgetItem(self, ["Customized"])
self.customized_item.setIcon(0, QtGui.QIcon(os.path.join(iconroot,"images/circularcolumnnorebar.png")))
@staticmethod
def dataToChild(info, item):
TreeWidget.tupleToItem(info["data"], item)
for val in info["childrens"]:
child = QtWidgets.QTreeWidgetItem()
item.addChild(child)
TreeWidget.dataToChild(val, child)
@staticmethod
def tupleToItem(t, item):
# set values to item
ba, isSelected = t
ds = QtCore.QDataStream(ba)
ds >> item
item.setSelected(isSelected)
@staticmethod
def dataFromChild(item):
l = []
for i in range(item.childCount()):
child = item.child(i)
l.append(TreeWidget.dataFromChild(child))
return {"childrens": l, "data": TreeWidget.itemToTuple(item)}
@staticmethod
def itemToTuple(item):
# return values from item
ba = QtCore.QByteArray()
ds = QtCore.QDataStream(ba, QtCore.QIODevice.WriteOnly)
ds << item
return ba, item.isSelected()
class InfoWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(InfoWidget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout(self)
plabel = QtWidgets.QLabel()
pixmap = QtGui.QPixmap(os.path.join(iconroot, "images/circularcolumnnorebard.png"))\
.scaled(230, 230, QtCore.Qt.KeepAspectRatio)
plabel.setPixmap(pixmap)
hlay.addWidget(plabel)
self.ilabel = QtWidgets.QLabel()
hlay.addWidget(self.ilabel)
hlay.addStretch()
self.readSettings()
@QtCore.pyqtSlot(str)
def setData(self, text):
try:
circular_section = int(text.translate({ord('D'): ""}))
area = (3.1416/4)*(circular_section**2)
inertia = (3.1416/64)*circular_section**4
fmt = "D = {}mm\nA = {:0.2E}mm2\n I = {:0.2E}mm4"
self.ilabel.setText(fmt.format(circular_section, area, inertia))
except ValueError:
pass
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("InfoWidget")
self.ilabel.setText(settings.value("text", ""))
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("InfoWidget")
settings.setValue("text", self.ilabel.text())
settings.endGroup()
class CircularDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(CircularDialog, self).__init__(parent)
grid = QtWidgets.QGridLayout(self)
self.tree = TreeWidget()
self.infoWidget = InfoWidget()
section_lay = QtWidgets.QHBoxLayout()
section_label = QtWidgets.QLabel("Section name: ")
section_edit = QtWidgets.QLineEdit('Define en name to section')
section_lay.addWidget(section_label)
section_lay.addWidget(section_edit)
self.tree.currentTextChanged.connect(self.infoWidget.setData)
button_layout = QtWidgets.QVBoxLayout()
add_button = QtWidgets.QPushButton("Add")
add_button.clicked.connect(self.addItem)
delete_button = QtWidgets.QPushButton("Delete")
delete_button.clicked.connect(self.removeItem)
button_layout.addWidget(add_button, alignment=QtCore.Qt.AlignBottom)
button_layout.addWidget(delete_button, alignment=QtCore.Qt.AlignTop)
buttonBox = QtWidgets.QDialogButtonBox()
buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
# for windows, posible bug
self.accepted.connect(self.write_all_data)
self.rejected.connect(self.write_all_data)
grid.addLayout(section_lay, 0, 0)
grid.addWidget(self.tree, 1, 0)
grid.addLayout(button_layout, 1, 1)
grid.addWidget(self.infoWidget, 2, 0, 1, 2)
grid.addWidget(buttonBox, 3, 0, 1, 2)
self.readSettings()
def readSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("CircularDialog")
self.setGeometry(settings.value("geometry", QtCore.QRect(300, 300, 400, 600)))
settings.endGroup()
def writeSettings(self):
settings = QtCore.QSettings()
settings.beginGroup("CircularDialog")
settings.setValue("geometry", self.geometry())
settings.endGroup()
def closeEvent(self, event):
self.write_all_data()
super(CircularDialog, self).closeEvent(event)
def write_all_data(self):
for children in self.findChildren(QtWidgets.QWidget) + [self]:
if hasattr(children, "writeSettings"):
children.writeSettings()
def addItem(self):
text, ok = QtWidgets.QInputDialog.getText(self, "Add custom section",
"Enter section geometry f.ex as D325 or just 325 in mm: ")
if ok:
it = QtWidgets.QTreeWidgetItem([text])
self.tree.customized_item.addChild(it)
def removeItem(self):
it = self.tree.customized_item.takeChild(0)
del it
if __name__ == '__main__':
QtCore.QCoreApplication.setApplicationName(ORGANIZATION_NAME)
QtCore.QCoreApplication.setOrganizationDomain(ORGANIZATION_DOMAIN)
QtCore.QCoreApplication.setApplicationName(APPLICATION_NAME)
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(QSS)
w = CircularDialog()
w.show()
sys.exit(app.exec_())