Nmaster88
Nmaster88

Reputation: 1585

How to export information of fields in qml

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:

enter image description here

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

Answers (2)

air-dex
air-dex

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

Nmaster88
Nmaster88

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

Related Questions