Qt: Creating an Array of Widgets
One of the features I like in Visual Basic 6 is how easy it is to create an array of "controls", what your average person would call an "interface object" or "widget". By placing a control on the form and then copy-pasting the control into the same form, the IDE would prompt you to create an array for that control.
This was very useful if, for example, your interface required a lot of inputs and you wanted to easily loop through them to aggregate the data. If you wanted to take one hundred text boxes and put them into a record in your SQL database, you had basically two options:
Dim queryText as String queryText = "INSERT INTO foo (bar,...,baz) VALUES (" ' This: queryText = queryText + txtField1.text queryText = queryText + ... queryText = queryText + txtField100.text ' Or this: Dim i as Integer For i=0 to 100 queryText = queryText = txtFields(i).text Next i
(Quick side note: neither of those options are really ideal since they don't use SQL parameterization. To be honest, I'm not sure if VB6 even supports paramterizing queries- ignore this for now, and don't ever run queries like that!)
The latter option is a bit cleaner, but it does sacrifice one thing: each object in the array effectively has the same name. You gain the benefit of cleaner compact code at the cost of having to reference your objects as "txtFields(1)" rather than "txtFieldFirstName."
Homogeneous Widget Arrays in Qt
When it came time to implement the "Fire Call" form in the Fire Department Management System, which has more than one hundred fields to enter information, I began looking up how to create an object array in Qt. I certainly didn't want over one hundred `QSqlQuery::addBindValue(ui->myField1->text())` lines.
Unlike in VB6, you can't create an array of widgets in the interface editor, Qt Designer. However, you can still use Qt Designer to create your interface; you just need to put a common string in the name of all the objects you wish to be a part of your array. For example, for each object we want to be in the array, if we were going to call the object "X", let's now call it "myArrayObjsX".
The first method I found utilizes QObject::findChildren(const QRegExp & regExp):
for (int i = 0; i < myList.size(); ++i) { qDebug() << tmp->text(); }
With the above, `myList` will contain all of the widgets inside of the container widget (myQGroupBox in this example). This works well if you are only trying to build an array of widgets of the same type; the resulting list will be ordered by the tab order of each child. However, if the result contains different types of widgets, it firsts groups them by type, then orders them by tab order! This wasn't desirable because I couldn't predict with certainty the final ordering of the list unless I used widgets of all the same type. While I could have just stuck with a single widget type, the form I was designing had fields for dates, times, and check-boxes; since I wasn't stuck with old VB6 anymore, I looked around for another way...
Heterogeneous Widget Arrays in Qt
I did some searching and found that the tab order for the interface objects was implemented linked-list style. Looking through the QWidget reference, I found the function I needed: nextInFocusChain(). This would give me the predictable order I needed. The next step was to iterate over the linked-list and only select the widgets I wanted:
// Get the first widget inside the parent widget from the tab ordering // Only iterate until we come back to the parent while(tmpw!=ui->myQGroupBox){ // Filter for the widgets we want if(tmpw->objectName().startsWith("myArrayObjs")){ edits+=tmpw; } // Keep on iterating tmpw=tmpw->nextInFocusChain(); }
Great, now we have a list containing our selected heterogeneous widgets. Now it comes down to pulling the actual data out of the list, which we use a little bit of type casting for:
// Loop through our list for (int i = 0; i < edits.size(); ++i) { // Get the current widget // Typecast to the types of widgets we know might be in the list // For each one, use the appropriate function to retrieve data if(tmplineedit){ qDebug()<<tmplineedit->text(); } else if(tmpdatetime){ qDebug()<<tmpdatetime->dateTime().toString("yyyy-MM-dd hh:mm:00.000"); } else if(tmpcombo){ qDebug()<<tmpcombo->currentText(); } else if(tmpcheck){ qDebug()<<tmpcheck->objectName(); } }
This is like having your cake and eating it, too! Not only do we have an array of widgets to easily iterate over, but it contains widgets of different types, and we can still reference each widget in the array by a meaningful name! Instead of `txtFields(2)` you can use `myArrayObjsFirstName`!