Reputation: 1585
What i want to do is a generic function that gets info about all input fields (TextField, SpinBox, ComboBox, maybe some i'm forgeting)
Let's imagine this form:
i want to get an object that gets what was filled there. It has to check if it is a TextField
or a ComboBox
because the way to get the info is different.
So i want something like this "meta" code:
Object = {}
for(input in inputs) {
if(input.type=="TextField") {
Object.push(input.text)
} else if (input.type == "ComboBox") {
Object.push(input.currentText)
}
}
I will implement a button or something that will trigger the function.
To make it a bit more difficult not all elements of the form will be on the same level, some will be childs of items for example.
Below i provide some code of what i want to do:
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
title: "Stack"
visible: true
width: 1400
Page {
id: page
anchors.fill: parent
property int responsiveWidth: 1000
property int maximumWidth: 900
ScrollView {
anchors.fill: parent
GridLayout {
columns: 2
width: page.width > page.responsiveWidth ? page.maximumWidth : page.width
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: page.width > page.responsiveWidth ? (page.width - childrenRect.width)/2 : 10
anchors.rightMargin: page.width > page.responsiveWidth ? 0 : 10
Button {
Layout.fillWidth: true
Layout.columnSpan: 2
text: "export"
onClicked: {
console.log("here i want to get an object with info related with the fields of these form")
}
}
Label {
text: "Company Name"
color: "red"
Layout.fillWidth: true
}
TextField {
objectName: "company_name"
font.bold: true
Layout.fillWidth: true
Layout.rightMargin: 10
}
Label {
text: "choices"
color: "red"
Layout.fillWidth: true
}
ComboBox {
Layout.fillWidth: true
model: [ "Banana", "Apple", "Coconut" ]
}
Item {
Layout.fillWidth: true
implicitHeight: 100
Layout.columnSpan: 2
Label {
anchors.left: parent.left
anchors.top: parent.top
text: "label"
color: "red"
width: parent.width
}
TextField {
anchors.left: parent.left
anchors.bottom: parent.bottom
objectName: "company_name"
font.bold: true
width: parent.width
//Layout.rightMargin: 10
}
}
Label {
text: "number"
color: "red"
Layout.fillWidth: true
}
SpinBox {
id: spinBox1
height: 30
stepSize: 1
editable: true
Layout.fillWidth: true
Layout.rightMargin: 10
}
}
}
}
}
There is a way to check the type of a component but we need to pass it's id
:
function isTextField(item) {
return item instanceof TextField
}
Is there a change to use instaceof
using other reference that is not the id
of a component?
I was think of getting the children
of a component
like this
var objects = configgrid.children
Upvotes: 0
Views: 815
Reputation: 4180
You should use a TableView
for your QML form and a custom table model containing form labels and inputs:
NmForm.qml:
import QtQuick 2.9
import QtQuick.Controls 2.2
import Nmaster 13.37
TableView {
id: form_table
model: TableModel {
fields: [
{
f_label: "Company Name",
f_input: ""
},
{
f_label: "Choices",
f_input: ""
},
// And so on
]
}
delegate: Item {
Label {
color: "red"
anchors.fill: parent
visible: column === 0
text: f_label // fieldInfos[row].label on the C++ side, displayed with the "f_label" role defined in roleNames().
}
TextField {
anchors.fill: parent
visible: column === 1 && (row ===1 || row === 4 || ... || row === /* another row index where you use a text field */)
text: f_input // fieldInfos[row].input on the C++ side, displayed with the "f_input" role defined in roleNames().
}
// And so on for other input components.
}
function getAllFields() {
return model.getAllFieldValues()
}
}
main.qml:
import QtQuick 2.9
import QtQuick.Controls 2.2
ApplicationWindow {
id: window
//...
NmForm { id: form }
//...
Button {
id: fields_button
text: qsTr("Get all fields")
onclicked: {
var fields = form.getAllFields()
for (var i = 0; i < fields.length; i++) {
console.log(qsTr("Field n°%L1 = %2").arg(i).arg(fields[i]))
}
}
}
//...
}
tablemodel.hpp:
#ifndef TABLEMODEL_HPP
#define TABLEMODEL_HPP
#include <QAbstractTableModel>
#include <QJsonArray>
class TableModel: public QAbstractTableModel
{
Q_OBJECT
public:
TableModel(QObject * parent = nullptr);
static void registerQML();
/// @brief Your "Object = {}; for(input in inputs) { Object.push(/* ... */) }" thing.
Q_INVOKABLE QStringList getAllFieldValues() const;
// Implementing QAbstractTableModel
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
typedef enum {
LABEL_ROLE = Qt::UserRole +1,
INPUT_ROLE = Qt::UserRole +2
} TableRoles;
protected:
// Backend datas: one element per row, each element containing input label text and input data
typedef struct {
QString label = "";
QString input = "";
} FieldInfo;
Q_PROPERTY(QJsonArray fields READ getFields WRITE setFields NOTIFY fieldsChanged)
QList<FieldInfo> fieldInfos;
QJsonArray getFields() const;
void setFields(QJsonArray value);
signals:
void fieldsChanged();
};
#endif // TABLEMODEL_HPP
tablemodel.cpp:
#include "tablemodel.hpp"
#include <QJsonObject>
#include <QQmlEngine>
TableModel::TableModel(QObject * parent = nullptr) :
QAbstractTableModel(parent),
fieldInfos()
{}
void TableModel::registerQML() {
qmlRegisterType<TableModel>("Nmaster", 13, 37, "TableModel");
}
QVariant TableModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!index.isValid()) {
return QVariant();
}
int row = index.row();
int column = index.column();
if (column == 0 && role == TableRoles::LABEL_ROLE) {
return fieldInfos[row].label;
}
else if (column == 1 && role == TableRoles::INPUT_ROLE) {
return fieldInfos[row].input;
}
else {
return QVariant();
}
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
if (!index.isValid()) {
return false;
}
bool res = true;
int row = index.row();
int column = index.column();
QVector<int> rolesChanged;
if (column == 0 && role == TableRoles::LABEL_ROLE) {
fieldInfos[row].label = value.toString();
rolesChanged << TableRoles::LABEL_ROLE;
}
else if (column == 1 && role == TableRoles::INPUT_ROLE) {
fieldInfos[row].input = value.toString();
rolesChanged << TableRoles::INPUT_ROLE;
}
else {
res = false;
}
emit dataChaged(index, index, rolesChanged);
return res;
}
int TableModel::rowCount(const QModelIndex &parent = QModelIndex()) const override {
return fieldInfos.count();
}
// One column for labels, another one for inputs.
int TableModel::columnCount(const QModelIndex &parent = QModelIndex()) const override {
return 2;
}
QHash<int, QByteArray> TableModel::roleNames() const override {
QHash<int, QByteArray> roles;
roles["f_label"] = TableRoles::LABEL_ROLE;
roles["f_input"] = TableRoles::INPUT_ROLE;
return roles;
}
QJsonArray TableModel::getFields() const {
QJsonArray res;
for (auto it = fieldInfos.begin(); it != fieldInfos.end(); ++it) {
QJsonObject obj;
obj["f_label"] = it->label;
obj["f_input"] = it->input;
res << obj;
}
return res;
}
void TableModel::setFields(QJsonArray value) {
fieldInfos.clear();
for (auto it = value.begin(); it != value.end(); ++it) {
QJsonObject o = it->toObject();
FieldInfo fi;
fi.label = o["f_label"];
fi.input = o["f_input"];
fieldInfos << fi;
}
emit fieldsChanged();
}
QStringList TableModel::getAllFieldValues() const {
QStringList res;
for (auto it = fieldInfos.begin(); it != fieldInfos.end(); ++it) {
res << it->input;
}
return res;
}
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
#include "tablemodel.hpp"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// ...
TableModel::registerQML();
// ...
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
int res = engine.rootObjects().isEmpty() ? -1 : app.exec();
// ...
return res;
}
Upvotes: 1
Reputation: 1585
Thanks a lot @air-dex for the answer. That seems a possible solution, however i solved it in another way.
The function for export is:
Button {
text: "export"
onClicked: {
var info
var viewData = []
var listObjects = configgrid.children
for(var i=0; i<listObjects.length; i++) {
if(listObjects[i].getInfo !== undefined) {
info = listObjects[i].getInfo()
if(info.length > 0) {
for(var j=0; j<info.length; j++) {
viewData.push([info[j]['label'],info[j]['value']])
}
} else{
viewData.push([info['label'],info['value']])
}
}
}
dl.exportTest(JSON.stringify(viewData))
}
Component.onCompleted: {
}
}
So the parent of the form in my case is a GridLayout
. That is where i get the children.
Then i check if the components in GridLayout
have implemented the function getInfo()
, if they have, then they matter to the export.
I give an example for a TextField
:
PanelTextField {
id: companyNameText
labelExport: companyNameLabel.text
function getInfo() {
var dict = {
label: this.labelExport,
value: this.text
}
return dict
}
KeyNavigation.tab: languagesComboBox
objectName: "company_name"
Layout.fillWidth: true
maximumLength: Globals.COMPANY_NAME_LEN
selectByMouse: true
Layout.columnSpan: page.width > page.responsiveWidth ? 1 : 2
}
In the case i have a RowLayout
i do this:
RowLayout {
function getInfo() {
var selfList = this.children
var info = []
for(var i=0; i<selfList.length; i++) {
if(selfList[i].getInfo !== undefined) {
info.push(selfList[i].getInfo())
}
}
return info
}
}
This seems to work very well.
What are your opinions?
Upvotes: 0