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.
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);
}