0
votes

I am developing a GUI of an application for a signal generation device that will support a number of channels, presented similar to a console mixer's channel style. In the menu bar the user shall be able to configure the channels' use between 3 types of signals. Each type will contain the sum of the respective channels in a different tab in the main window. "Configuration.qml" contains the signal handler and "TabArea.qml" contains the javascript function create_PWM(countPWM).

In brief, my mainWindow consists of the components Devices , MenuBar , Tools and TabArea . Inside MenuBar I create dynamically a dialog component under the name Configuration. In there exists a table retrieving default information from the tableModel. The user can modify the model and when the Save button is clicked, I want the channels to be depicted on the respective tab considering the model data.

\\MainWindow.qml
Item {
id: mainWindow
width: 1200
height: 800

Rectangle {
    id: background
    color: "#d7dfda"
    anchors.fill: parent

    MenuBar {
        id: menuBar
        anchors {
            left: parent.left
            top: parent.top
        }
        height: 40
    }
    Tools {
        id: toolBar
        y: menuBar.height
        height: implicitHeight
        anchors {
            left: parent.left
            right: parent.right
            top: menuBar.bottom
            bottom: devices.top
        }
        channelsPWM: tabs.channelsPWM
        channelsFrequency: tabs.channelsFrequency
    }

    Devices {
        id: devices
        anchors {
            //                top: menuBar.down
            top: toolBar.bottom
            left: parent.left
            right: tabs.left
        }
        height: 3 * parent.height / 8
        //            y: menuBar.height
        y: toolBar.height
    }

    TabArea {
        id: tabs
        //            y: menuBar.height
        y: toolBar.height
        x: parent.width / 8
        anchors {
            //                top: menuBar.bottom
            top: toolBar.bottom
        }
        width: 3 * parent.width / 4
        height: 3 * parent.height / 4
    }
}
}


\\MenuBar.qml
Item {
id: menuToolBar
Button {
    id: fileButton
    text: "File"
    anchors.left: parent.left
    anchors.top: parent.top
    height: 40
    onClicked: fileMenu.open()
    onHoveredChanged: hovered? fileButton.font.bold = true : fileButton.font.bold = false
    Menu {
        id: fileMenu
        y: parent.height
        MenuItem {
            text: "New Project      Ctrl+N"
            onTriggered: newDialog.open()

            }
        MenuItem {
            text: "Open Project     Ctrl+O"
            onTriggered: openDialog.open()
        }
    }
}

Button {
    id: editButton
    y: 0
    text: "Edit"
    anchors.left: fileButton.right
    anchors.leftMargin: 0
    anchors.top: parent.top
    height: fileButton.height
    onHoveredChanged: hovered? editButton.font.bold = true : editButton.font.bold = false
    onClicked: editMenu.open()
    Menu {
        id: editMenu
        y: parent.height
        MenuItem {
            text: "Undo             Ctrl+Z"
        }
        MenuItem {
            text: "Cut              Ctrl+X"
        }
        MenuItem {
            text: "Copy             Ctrl+C"
        }
        MenuItem {
            text: "Paste            Ctrl+V"
        }
    }
}

Button {
    id: configurationButton
    text: "Configuration"
    anchors.left: editButton.right
    anchors.top: parent.top
    height: editButton.height
    onHoveredChanged: hovered? configurationButton.font.bold = true : configurationButton.font.bold = false
    onClicked: {
        var component = Qt.createComponent("Configuration.qml");
        if( component.status !== Component.Ready )
        {
            if( component.status === Component.Error )
                console.debug("Error:"+ component.errorString() );
            return; // or maybe throw
        }
        var dialog =component.createObject(configurationButton);
        dialog.channelConfigDialog.open();
    }
}

Button {
    id: helpButton
    y: 0
    text: "Help"
    anchors.left: configurationButton.right
    anchors.leftMargin: 0
    anchors.top: parent.top
    height: fileButton.height
    onHoveredChanged: hovered? helpButton.font.bold = true : helpButton.font.bold = false
    onClicked: helpMenu.open()
    Menu {
        id: helpMenu
        y: parent.height
        MenuItem {
            text: "Contents"
        }
        MenuItem {
            text: "Index"
        }
        MenuItem {
            text: "Context Help             F1"
        }

    }
}

/**
  More Buttons for Menu implementation can be added here
  **/

FileDialog {
    id: openDialog
    title: "Please choose a Project."
    /**
      The backend behavior needs to be added here in order to determine
      what the application's actions will be for the selected files etc.
      e.g. onAccepted: {}
            onRejected: {}
      **/

}}
\\Configuration.qml
Item{
property alias channelConfigDialog: channelConfigDialog
Dialog {
    id: channelConfigDialog
    modality: Qt.WindowModal
    title: "Channel Configuration."
    height: 500
    width: 500
    standardButtons: Dialog.Save | Dialog.Cancel
    property int totalChannels: 30

    ListModel {
        id: tableModel
        Component.onCompleted: {
            for (var i=0;i<channelConfigDialog.totalChannels;i++){
                append({"name": "Channel"+(i+1), "use": "PWM", "data": "Raw", "conversion": ""});
            }
        }
    }

    Component {
        id: usageComboDel
        Item{
            anchors.fill: parent
            ComboBox {
                id: usageCombo
                model:
                    ListModel{
                        id: usageModel
                        ListElement {
                            text: "PWM"
                        }
                        ListElement {
                            text: "Frequency"
                        }
                        ListElement {
                            text: "BLDC"
                        }
                        }
                currentIndex: 0
                height: 16
                anchors.fill: parent
                onCurrentTextChanged: {
                    tableModel.setProperty(styleData.row,"use",currentText);

                }

    }


    Component{
        id: dataTypeComboDel
        Item{
            id: itemDataTypeComboDel
            anchors.fill: parent
            ComboBox {
                id: dataTypeCombo
                model: ["Raw", "Phys"]
                currentIndex: 0
                height: 16
                anchors.fill: parent
                onCurrentTextChanged: {
                    tableModel.setProperty(styleData.row,"data",currentText);
                    sample()
                }
                }
            function sample(){
                for (var i=0;i<tableConfig.rowCount;i++){
                    var temp = tableModel.get(i).name;
                    console.log(temp);
                    console.log(tableModel.get(i).use + ", " + tableModel.get(i).data);
                    }
            }
                }
    }
    Component{
        id: conversionRuleComboDel
        Item{
            id: itemConversionRuleComboDel
            anchors.fill: parent
            ComboBox {
                id: conversionRuleCombo
                model: ["","Linear", "Ranges", "Quadtratic", "Logarithmic", "Mathematical function"]
                currentIndex: -1
                height: 16
                anchors.fill: parent
                onCurrentTextChanged: {
                    tableModel.setProperty(styleData.row,"conversion",currentText);
                }
                }

        }

    }


    TableView {
        id: tableConfig
        model: tableModel
        anchors.fill: parent


        TableViewColumn{
            role: "name"
            title: "Channels"
            width: tableConfig.width/ tableConfig.columnCount

        }
        TableViewColumn{
            id: usageCol
            property alias delagata: usageComboDel
            title: "Usage"
            delegate: usageComboDel
            width: tableConfig.width/tableConfig.columnCount
        }
        TableViewColumn{
            title: "Data Type"
            delegate: dataTypeComboDel
            width: tableConfig.width/tableConfig.columnCount
        }
        TableViewColumn{
            id: conversionRuleClmn
            title: "Coversion Rule"
            delegate: conversionRuleComboDel
            width: tableConfig.width/tableConfig.columnCount
        }
    }

    onAccepted: {
        var countPWM = 0;
        var countFrequency = 0;
        for (var i=0; i<tableModel.count; i++){
            if ( tableModel.get(i).use === "PWM" ){
                countPWM++;
            }
            else if (tableModel.get(i).use === "Frequency"){
                countFrequency++;
            }
        }
        TabArea.channelsPWM.create_PWM(countPWM);

        }
}
}
\\TabArea.qml
Item{
id: tabAreaRoot
property alias channelsPWM: channelsPWM
property alias channelsFrequency: channelsFrequency

TabBar {
    id: tabBar
    TabButton {
        text: qsTr("PWM Output")
        width: implicitWidth
    }
    TabButton {
        text: qsTr("Frequency Output")
        width: implicitWidth
    }
    TabButton {
        text: qsTr("BLDC Emulation")
        width: implicitWidth
    }
}

StackLayout {
    id: tabLayout
    anchors.top: tabBar.bottom
    currentIndex: tabBar.currentIndex
    width: parent.width
    height: parent.height
    Rectangle {
        color: background.color
        border.width: 2
        ScrollView{
            id: scrollPWM
            anchors.fill: parent
            contentItem: channelsPWM.children
            horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOn
            RowLayout{
                id: channelsPWM
                spacing: 0
                Layout.fillHeight: true
                Layout.fillWidth: true


                function create_PWM(countPWM){
                    for (var i=0;i<countPWM;i++){
                        var component = Qt.createComponent("PWM.qml");
                        if( component.status !== Component.Ready )
                        {
                            if( component.status === Component.Error )
                                console.debug("Error:"+ component.errorString() );
                            return; // or maybe throw
                        }
                        var channels =component.createObject(channelsPWM, { "id": "channelPWM"+(i+1), "channelText.text": "Channel"+(i+1)});
                    }
                    }
            }
        }

    }/* Each tab will be a row layout containing column positioners for each channel */
    Rectangle {
        color: background.color
        border.width: 2
        ScrollView{
            id: scrollFrequency
            anchors.fill: parent
            contentItem: channelsFrequency.children
            horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOn
            RowLayout{
                id: channelsFrequency
                spacing: 0
                Layout.fillHeight: true
                Layout.fillWidth: true
                function create_Frequency(countFrequency){
                    for (var i=0;i<countFrequency;i++){
                        var component = Qt.createComponent("Frequency.qml");
                        if( component.status !== Component.Ready )
                        {
                            if( component.status === Component.Error )
                                console.debug("Error:"+ component.errorString() );
                            return; // or maybe throw
                        }
                        var channels =component.createObject(channelsFrequency, { "id": "channelFrequency"+(i+1), "channelText.text": "Channel"+(i+1)});
                    }
                    }
                }
        }

    }
    Rectangle {
        color: background.color
        border.width: 2


    }
}

}

Both qml files are declared in the same directory. Thus, the TabArea component is visible within the onAccepted handler. Also, the id: channelsPWM is declared as a property of the tabAreaRoot Item and therefore also callable outside the component.

The problem is, that when I try to call the create_PWM inside the onActivated handler I get the following :

qrc:/Configuration.qml:181: TypeError: Cannot call method 'create_PWM' of undefined

I understand that this is caused because the channelsPWM is not "visible" inside the signal handler. But why is that? As described above, even when I typed the TabArea.channelsPWM. etc. the QtCreator Editor shows me the available options, which means that this id is accpeted by the current scope.

I have also tried to "bypas" this issue by putting create_PWM in a separate js file and when the activated() signal is omitted, call it from there. In this case, I don't get the TypeError BUT the desired channels are not created in the desired position.

I have also checked that the object with id channelsPWM is not destroyed before the call inside the handler. (thought it would be good to check anyway)

Things might get a little more confusing because I want these channels to be dynamically created based on the user's configuration. So if I get this wright, the function that will create them needs to be placed in the same qml with the channels' RowLayout parent.

Thank you in advance.

1
you have to create a TabArea, you can not use it without creating an instance, for example: TabArea{id: t_area}, then use it t_area.channelsPWM.create_PWM(countPWM)eyllanesc
@eyllanesc I have already tried this and yes the channels are created BUT under the new parent e.g. t_area. And therefore placed in a totally different place than desired. I want them to be instantiated as children of parent tabAreaRoot so as to be put in the respective tab in the main window. Should I consider creating the entire tab section when the accepted() signal is omitted as a better option?K.Tsakalis
I can not help you because your code can not be reproduced and therefore I do not have an integral vision of your project, so I can no longer advise. I only said that because it was the easiest error to see. If you want us to help you, you must provide a minimal reproducible exampleeyllanesc
@eyllanesc Thank you for your answer and your prompt to correct example. I provided all the code necessary. Some parts could have been omitted but again they will give you a full idea of the problem.K.Tsakalis

1 Answers

1
votes

Problem Solved.

As it seems I cannot exchange data directly from the dynamic created object with any other qml file except for it's parent main qml file.

So, I had to create a method in the MenuBar.qml and connect it to a custom signal inside the Configuration.qml as described here. This way, I pass the countPWM argument to the parent Item configurationButton and then I can call the create_PWM(countPWM) from there, using the tabs object instantiated in the MainWindow.qml . Therefore, the newlly created PWM channels will be children of the existing object and placed in the correct tab.

\\MenuBar.qml
(rest of code)
function sendParams(counter){
        tabs.channelsPWM.create_PWM(counter);
    }


\\Configuration.qml
(rest of code)
signal saved(int counter)
onSaved: {
        console.log("I just sent the counter");
}
onAccepted: {
        var countPWM = 0;
        var countFrequency = 0;
        for (var i=0; i<tableModel.count; i++){
            if ( tableModel.get(i).use === "PWM" ){
                countPWM++;
            }
            else if (tableModel.get(i).use === "Frequency"){
                countFrequency++;
            }
        }
        saved.connect(parent.sendParams);
        saved(countPWM);
}