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.
TabArea
, you can not use it without creating an instance, for example:TabArea{id: t_area}
, then use itt_area.channelsPWM.create_PWM(countPWM)
– eyllanesct_area
. And therefore placed in a totally different place than desired. I want them to be instantiated as children of parenttabAreaRoot
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