Search code examples
qtqmlsignals-slots

Trouble getting 2 QML files to communicate using Signals


I have 2 qml files. Toolbarbutton.qml creates a button and DockWidget.qml creates a ListView. I am trying to have the button in Toolbarbutton broadcast to DockWidget that the button has been clicked. I then want to add items to my listView.

I have been trying to use a signal to establish the communication. In the Toolbarbutton.qml, I have a Rectangle item with an ID of saveButton. Under this Rectangle Item I added a signal called addSaveHistory(). I have shortened the code so its easier to see what I am doing.

Rectangle {
 id: saveButton
 width: 50
 height: 30
 border.color: "white"
 color: buttonMouseArea.containsMouse ? "grey" : "black"

//Button text
Text{
    id: buttonLabel
    anchors.centerIn: parent
    text: "Save +"
    color: "white"
}
signal addSaveHistory(string url)

 MouseArea{
    id: buttonMouseArea
    anchors.fill: parent //anchor the mousearea to the rect area

    //onClicked handles valid mouse button clicks
    onClicked: {
       addSaveHistory("Hello?") //dispatch the event

    }
}
}

In the DockWidget.qml file I have an Item that is using the Connections component. I have shorten the code so its easier to see what I am doing.

Item{
  width: 100
  height: 100
  objectName: "Save + History"

 Rectangle {
   id: rect
   anchors.fill: parent
   color: "#323232"

 Connections {
     target: saveButton //id of rectangle in ToolbarButton.qml

     onAddSaveHistory: {

       // this is never called  
     }
 }
}

I can't seem to figure out why the onAddSaveHistory is never called. I have also tried using a Loader component to load the Dockwidget.qml file into Toolbarbutton.qml and then specifically call a function in Dockwidget, but that doesn't work as well.

Do I just have the wrong idea of how the signals should work? I would greatly appreciation any guidance.

Here is my main.qml file.

import QtQuick 2.2
import Painter 1.0

import "save.js" as Save

Plugin {

//executed at startup
Component.onCompleted:{

  //add toolbar
  alg.ui.addToolBarWidget("ToolbarButton.qml")//add a button to toolbar
  alg.ui.addDockWidget("DockWidget.qml")//add dock widget

  Save.log("Incremental Save Plugin has been created")

  }
}

Solution

  • First of all, realise that the signal is declared on the QML element at the root of the ToolbarButton.qml file, i.e. the Rectangle. Only the root item forms the interface for the QML element. All child elements are hidden to the outside world. So it's customary to declare the signal near the top of the file. A common ordering is:

    Item {
        id: root
        property real foo: 0.0
        property alias bar: innerItem.bar
        signal baz(url location)
    
        Rectangle {
            id: innerItem
            property color bar: "red"
        }
    }
    

    So in this case a simple ToolbarButton.qml that declares a signal and emits it when you click in a contained MouseArea would look like this:

    import QtQuick 2.3
    
    Rectangle {
        id: root
        width: buttonLabel.width + 20
        height: buttonLabel.height + 20
        color: "steelBlue"
    
        // Declare signal on the button object
        signal addSaveHistory(string url)
    
        Text {
            id: buttonLabel
            anchors.centerIn: parent
            text: "Save +"
        }
    
        MouseArea {
            id: buttonMouseArea
            anchors.fill: parent
            onClicked: {
                // emit the signal
                root.addSaveHistory("Hello?")
            }
        }
    }
    

    Then a simple consumer of this signal, here just a Text element but can be anything might look like this:

    import QtQuick 2.3
    
    Text {
        id: dockWidget
        text: "Waiting for a click..."
    }
    

    But wait, you say, how does that help. Well so far there is no connection. We make that when we instantiate our reciever item. So here, the main.qml file looks like this:

    import QtQuick 2.3
    import QtQuick.Window 2.2
    
    Window {
        visible: true
    
        // Instantiate the ToolbarButton that emits the signal when clicked
        ToolbarButton {
            id: toolbarButton
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.topMargin: 10
            anchors.leftMargin: 10
        }
    
        // Instantiate the DockWidget (just a Text element in this example)
        DockWidget {
            id: dockWidget
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 10
            anchors.left: parent.left
            anchors.leftMargin: 10
    
            // Complete the plumbing that connects the signal from item above
            // with id: toolbarButton.
            Connections {
                target: toolbarButton
                // When signal addSaveHistory is emitted,
                // replace binding above with a new one
                onAddSaveHistory: dockWidget.text = "Button was clicked"
            }
        }
    }
    

    In actual fact there is no need for the Connections element to be a child of the DockWidget. Think of the ToolbarButton and Dock widget as lego bricks that we are plumbing together at a location that has a higher level of knowledge about these items and how they should interact. If you want to though, you could wrap this up into its own more complicated custom QML component.

    Furthermore, if you only have one thing caring about the signal, you don't even need the Connections element at all:

    import QtQuick 2.3
    import QtQuick.Window 2.2
    
    Window {
        visible: true
    
        ToolbarButton {
            id: toolbarButton
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.topMargin: 10
            anchors.leftMargin: 10
    
            // Add signal handler directly to emitter
            onAddSaveHistory: dockWidget.text = "Button was clicked"
        }
    
        DockWidget {
            id: dockWidget
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 10
            anchors.left: parent.left
            anchors.leftMargin: 10
        }
    }
    

    Hope this helps.