0
votes

I need support to my little software with Qwidget and QTreeWidgets. see below figure.

enter image description here

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.

enter image description here

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.

1
In SO you should ask a question by POST, so Ask Question says, do not say Ask Questions, please edit your question and remove the questions, those questions you should do in other POST. - eyllanesc
I edited the question to one by post. - Pavel.D
Could you explain what your code does, also those marks with yellow color in the image are relevant? - eyllanesc
I have explained what my code do or want to do!. Please do not hesitate to ask if you have more questions. - Pavel.D
I understand what you want to do, but I am confused by the meaning of 1 and 2 in yellow on the second image. Are they relevant? - eyllanesc

1 Answers

0
votes

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_())