Model/View mapping to dynamically-generated widgets?



  • Hello,

    The model/view concept is fairly new to me and I'm trying to understand it. Most of the examples I can find online involve the QListView, QTableView, QTreeView, etc., but for my application I would prefer to map to labels & buttons. The labels & buttons are dynamically generated so there is no fixed layout.

    From some initial research it looks like I need to work with QDataWidgetMapper, but I'm having trouble finding helpful examples.

    Below is an example of some of the convoluted steps I often take (without model/view approach). My impression is that a model/view approach will greatly simplify this.

    If someone could either a.) point me in the direction of some clear examples/tutorials for mapping model data to (non Q?View) widgets, and/or b.) give me some suggestions for re-writing the code below in a model/view paradigm, I wouild appreciate it.

    Thanks

    @from PySide.QtGui import *
    from PySide.QtCore import *
    from random import choice
    import sys

    class MainWindow(QMainWindow):
    def init(self, thingList):
    super(MainWindow, self).init()
    self.initUI(thingList)

    def initUI(self, thingList):
        cw = centralWidget(thingList)
        self.setCentralWidget(cw)
        self.setWindowTitle("How can I do the same with model/view (& QDataWidgetMapper?)")
        self.show()
    

    class centralWidget(QWidget):
    def init(self, thingList):
    super(centralWidget, self).init()
    self.initUI()
    self.thingList = thingList

    def initUI(self):
        self.vbox = QVBoxLayout()
        self.actionLabel = QLabel("No Buttons have been Clicked")
        self.vbox.addWidget(self.actionLabel)
    
        self.g = QGridLayout()
    
        col=0
        row=0
        columnBreak = len(thingList) / 2
        for index, thing in enumerate(thingList):
            newHBox = QHBoxLayout()
    
    
            newLabel = thing[4]
    
            newHBox.addWidget(newLabel)
            for button in thing[2]:
                newHBox.addWidget(button)
                button.clicked.connect(self.somethingChanged)
    
            newComboBox = thing[3]
            newComboBox.currentIndexChanged.connect(self.somethingChanged)
            newHBox.addWidget(newComboBox)
            newHBox.addStretch(1)
            self.g.addLayout(newHBox, row, col)
            row += 1
            if row >= columnBreak:
                row = 0
                col += 1
    
        self.setMinimumWidth(500)
        self.vbox.addLayout(self.g)
        self.setLayout(self.vbox)
        self.show()
        
    ###########################################
    # Needlessly complicated series of methods
    # used for translating user input into 
    # property changes of "thing."
    # I assume that model/view will simplify this?
    ############################################
    
    def changeValue(self, sender, thing, index):
        bools = thing[1]
        comboBox = thing[3]
        thingLabel = thing[4]
        originalThingName = thing[5]
    
        if comboBox.currentText() == "":
            thingName = originalThingName
        else:
            thingName = "{0}_{1}".format(thing[0], comboBox.currentText().replace(" ", ""))
    
        thingLabel.setText(thingName)
    
    
        if type(sender) == QPushButton:
            if sender.isChecked():
                bools[index] = True
            else:
                bools[index] = False
    
        message = "{0}: New Values: {1} - {2} - {3} - {4}.  New Name: {5}".format(thing[0], bools[0], bools[1], bools[2], bools[3], thingName)
        self.actionLabel.setText(message)
        newData = [thingName, [bools[0], bools[1], bools[2], bools[3]], originalThingName]
        self.setThingValues(newData)
    
    
    def findSenderThing(self, sender):
        for thing in self.thingList:
            if sender == thing[3]:
                index = None
                return thing, index
            if sender in thing[2]:
                index = thing[2].index(sender)
                return thing, index
    
    
    def setThingValues(self, newData):
        print("\n\nADJUSTED THINGLIST:\n")
        for thing in thingList:
            newThingName = newData[0]
            newBoolsList = newData[1]
            originalThingName = newData[2]
    
            if thing[5] == originalThingName:
                thing[0] = newThingName
                thing[1] = newBoolsList
    
            print("\t{0}\t{1}\t(Originally: {2})".format(thing[0], thing[1], thing[5]))
    
    
    def somethingChanged(self):
        sender = self.sender()
        thing, index = self.findSenderThing(sender)
        self.changeValue(sender, thing, index)
    

    def generateDummyData():
    rnd = choice(range(15, 40))
    thingList = []

    for x in range(rnd):
        thingName = "Thing {0}".format(str(x))
        originalThingName = thingName
    
        newLabel = QLabel(thingName)
        newLabel.setFixedWidth(80)
    
        boolList = []
        buttonList = []
        while len(boolList) < 4:
            randBool = choice([True, False])
            boolList.append(randBool)
            newButton = QPushButton(str(len(boolList)))
            newButton.setCheckable(True)
            newButton.setFixedSize(20,20)
            buttonList.append(newButton)
    
            newComboBox = QComboBox()
            newComboBox.addItems(['', 'ABC', 'DEF', 'GHI', ' OCD', 'MNO'])
    
        newThing = [thingName, boolList, buttonList, newComboBox, newLabel, originalThingName]
        for bool in boolList:
            if bool:
                buttonList[boolList.index(bool)].setChecked(True)
        thingList.append(newThing)
    return thingList
    

    if name == 'main':
    app = QApplication(sys.argv)
    thingList = generateDummyData()
    win = MainWindow(thingList)
    sys.exit(app.exec_())@



  • It is not completely clear on what is required here. Do you want to use your own widgets to display the data present in model ? If that is the case, please try to use the Delegates for this. You need to write custom delegates for this. I'm not familiar with pyqt. I have examples to share using Qt C++ for custom delegates.



  • Thanks for your response. To clarify my question:

    Basically I'm looking to make the jump into the model/view paradigm, but the only useful information I can find is on using QListView, QTableView, and QTreeView. None of which exactly fit my needs (I'm looking to create more of a button/combobox GUI).

    My research has pointed me in the direction of QDataWidgetMapper but any information I've found has been over my head, and I wondered if someone could explain, show some examples, or point me in the direction of some decent tutorials.

    The sample code I provided is an example of how I often find myself doing things (sans model/view) -- which, I believe, is convoluted and error-prone. I suspect that I could eliminate a lot of my "middle-man" methods/functions if I used a model/view approach. I figure someone who knows what they're doing might be able to suggest how I could accomplish the same (in fewer lines of code) .

    Thanks



  • ok. My suggestion is not get into ModelView directly. If your intent is to create simple UI with button and combo box, just create the UI using buttons/combobox and layouts. This will help you.



  • That is what I have been doing so far, but my code seems very inefficient. Notice all of the methods of my centralWidget class. (My actual application is much more complex than the example I posted).

    Is there any way to set "thingList" as a model and map each item's True/False values directly to the buttons?

    I think your first response (to use custom delegates) may be what I'm looking for but I don't understand it -- can you point me in the direction of some simple examples?


  • Lifetime Qt Champion

    Hi,

    Search for "Simple Widget Mapper Example" in Qt's documentation. It should give you the basics to start your code the right way.

    Hope it helps


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.