Changing button image loses pressed signal



  • Hi,

    I'm developing a small app that should allow the user to change the design (background and button colors).

    I define a standard button with a pre-defined background image plus hover effect like this:
    @// Button.qml

    import QtQuick 2.4

    Rectangle
    {
    id: button
    property alias image: image
    property alias mousearea: mousearea
    property alias text: text.text

    width: 90
    height: 30
    color: "transparent"

    BorderImage
    {
    id: image
    source: mousearea.pressed ? "qrc:/img/buttons/cyan_hover.png" : "qrc:/img/buttons/cyan.png"
    width: parent.width
    height: parent.height
    border { left: 25; top: 25; right: 25; bottom: 25 }
    horizontalTileMode: BorderImage.Stretch
    verticalTileMode: BorderImage.Round
    }

    MouseArea
    {
    id: mousearea
    anchors.fill: parent
    acceptedButtons: Qt.LeftButton
    cursorShape: "PointingHandCursor"
    }

    Text
    {
    id: text
    anchors.centerIn: parent
    text: "Button"
    color: "white"
    font.bold: true
    }
    }@
    When I import or "create" an instance of this button in main.qml, everything works fine:
    @import QtQuick 2.4
    import QtQuick.Window 2.2

    Window
    {
    id: window
    visible: true
    width: 350
    height: 500

    Rectangle
    {
    id: rectangleLayout
    anchors.fill: parent

    Image
    {
      id: imageBackground
      source: "qrc:/img/backgrounds/cyan.jpg"
      width: parent.width
      height: parent.height
      fillMode: Image.Stretch
    }
    
    Button
    {
      id: buttonDesignBlack
      anchors.top: rectangleHeader.bottom
      anchors.left: rectangleLayout.left
      anchors.topMargin: 30
      anchors.leftMargin: 10
      anchors.horizontalCenter: rectangleLayout.horizontalCenter
      height: 50
      mousearea.onClicked:
      {
        // this stuff doesn't work (see example later)
      }
      text: "Black design"
    }
    Button
    {
      id: buttonDesignGray
      anchors.top: buttonDesignBlack.bottom
      anchors.left: rectangleLayout.left
      anchors.topMargin: 5
      anchors.leftMargin: 10
      anchors.horizontalCenter: rectangleLayout.horizontalCenter
      height: 50
      mousearea.onClicked:
      {
        // ...
      }
      text: "Gray design"
    }
    

    }@
    But know, when I try to change the background images of the buttons like this:
    @ mousearea.onClicked:
    {
    imageBackground.source = "qrc:/img/backgrounds/black.jpg"
    buttonDesignBlack.image.source = buttonDesignBlack.mousearea.pressed ? "qrc:/img/buttons/black_hover.png" : "qrc:/img/buttons/menu_main/black.png"
    buttonDesignGray.image.source = buttonDesignGray.mousearea.pressed ? "qrc:/img/buttons/black_hover.png" : "qrc:/img/buttons/menu_main/black.png"
    }@
    The background of the buttons is correctly changed to black when clicking on the button, but the pressed effect doesn't appear anymore.
    Maybe this happens because the onClicked signal is emitted simultaneously and the pressed signal is overwritten immediately??

    Does anybody know how I can manage this? Maybe there is also a better way to change the background image for all instances of 'Button' instead of changing it via 'id' (I have about 15 different buttons with 15 different images)...

    Thank you in anticipation!



  • Clicked is a composite of press + release. Pressed would have been emitted before clicked. The name of your png seems to do something about the hover part. Try playing with hoverenabled: true in combination with onEntered and onExited



  • Hi and thank you for your reply.
    Well, I know that the pressed event is emitted before the clicked event fires. What I don't understand is why my code doesn't work.

    When I only define the background image of the button in the Button.qml like this:
    @ source: mousearea.pressed ? "qrc:/img/buttons/cyan_pressed.png" : "qrc:/img/buttons/cyan.png"@
    Then it works always fine. When I press and hold the button, the cyan_pressed.png ("hover was only the wrong word for it, html habit...) is displayed and after releasing the button, the cyan.png is displayed. This works fine and everytime.
    Only when I try to change the source property (see last post) using the same command but only different images, then the pressed image isn't shown anymore:
    @mousearea.onClicked:
    {
    buttonDesignXX.image.source = buttonDesignXX.mousearea.pressed ? "qrc:/img/buttons/XX_pressed.png" : "qrc:/img/buttons/XX.png"
    }@
    I mean, using this code and pressing + holding mouse button should show the pressed.png, but it doesn't...

    Does anyone know what I'm doing wrong??


  • Moderators

    Hi,

    [quote author="Binary91" date="1419520085"]When I only define the background image of the button in the Button.qml like this:
    @ source: mousearea.pressed ? "qrc:/img/buttons/cyan_pressed.png" : "qrc:/img/buttons/cyan.png"@
    Then it works always fine. When I press and hold the button, the cyan_pressed.png ("hover was only the wrong word for it, html habit...) is displayed and after releasing the button, the cyan.png is displayed.[/quote]The above code creates a property binding. The "value" of source is a binding expression, so the image changes when the value of pressed changes.

    [quote]Only when I try to change the source property (see last post) using the same command but only different images, then the pressed image isn't shown anymore:
    @mousearea.onClicked:
    {
    buttonDesignXX.image.source = buttonDesignXX.mousearea.pressed ? "qrc:/img/buttons/XX_pressed.png" : "qrc:/img/buttons/XX.png"
    }@
    I mean, using this code and pressing + holding mouse button should show the pressed.png, but it doesn't...

    Does anyone know what I'm doing wrong??[/quote]The above code evaluates the ternary expression and then assigns the result to source. Since pressed is always false by the time the clicked signal is emitted, your code reduces to:
    @
    mousearea.onClicked: {
    buttonDesignXX.image.source = "qrc:/img/buttons/XX.png"
    }
    @

    The "value" of source is now a static string, not a binding expression. Therefore, source is no longer related to pressed.

    See the "Property Binding":http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html article for more details, and see the section called "Creating Property Bindings from JavaScript" to assign a new binding expression instead of a static string.



  • Ah that is also interesting! Well, I understand the problem now and I know the difference between property binding and javascript asignment.
    What I'm asking myself is, why isn't it possible to use the property binding operator (image.source: ...) in a JS statement (like in my example: onClicked:) ? Or the other way round, why is assignment (image.source = ...) only possible within a JS statement?
    Is there any reason for that?


  • Moderators

    ':' is not the property binding operator. It is the QML initialization operator which assigns initial values to properties. It can be used to "assign binding expression values as well as static values":http://doc.qt.io/qt-5/qtqml-syntax-objectattributes.html#property-attributes:

    @
    Rectangle {
    id: root

    Rectangle {
        id: child
    }
    
    // This assigns a binding expression value
    width: child.width
    
    // This assigns a static value
    height: 100
    

    }
    @

    Similarly, '=' can be used to assign binding expressions too. Read the "Property Binding":http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html article for instructions.



  • Hi,

    ok, but then I don't understand why the following line in a js statement is a static assignment and not a property binding:
    @Button
    {
    mousearea.onClicked:
    {
    image.source = mousearea.pressed ? "imgPressed.png" : "img.png"
    }
    }@
    I mean, when I use this line outside of the js statement using the colon ":", the binding works perfectly:
    @{
    image.source: mousearea.pressed ? "imgPressed.png" : "img.png"
    mousearea.onClicked:
    {
    }
    }@
    Also, when I try to use the colon ":" in the js statement like this:
    @{
    mousearea.onClicked:
    {
    image.source: mousearea.pressed ? "imgPressed.png" : "img.png"
    }
    }@
    , then QtCreator throws an error and doesn't compile my code...

    In the link you posted, I read that the "imperative assignment operator" "=" is a JAVASCRIPT operator, so does that mean that I can use "=" only in JAVASCRIPT statements (like onClicked: {} in the above example)?
    If yes, then I'm still wondering why the code in example 1 (this post) doesn't bind the property value but instead overwrites (a previous existing) binding with a fixed value, either imgPressed.png or img.png...

    Do you know why?


  • Moderators

    First, it's helpful to understand the following:

    The QML language is designed by Qt engineers, specifically for Qt applications.

    However, the JavaScript language is designed by people that are completely unrelated to the Qt Project. JavaScript is far older than QML, and it is widely used outside of QML.

    JavaScript is an imperative language.

    QML is a hybrid language. It consists of imperative parts (which are JavaScript expressions) and declarative parts. In other words, QML contains JavaScript plus other things.

    If you don't know the difference between declarative and imperative programming, do some "extra reading":http://stackoverflow.com/questions/1784664/what-is-the-difference-between-declarative-and-imperative-programming

    Secondly, make sure you understand how a "JavaScript conditional operator":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator works.

    Ok, now to address your questions and observations.

    To specify a property and a signal handler in a QML component, you need the following format:

    @
    <COMPONENT> {
    <PROPERTY>: <JAVASCRIPT_EXPRESSION>
    <SIGNAL_HANDLER>: <JAVASCRIPT_STATEMENT>
    }
    @

    Examine the snippet above: The colon "assigns" JavaScript code to a QML component's attribute.

    Note that a property's JS code is used differently from a signal handler's JS code:

    • The JAVASCRIPT_EXPRESSION associated with a PROPERTY is evaluated every time a variable in the JAVASCRIPT_EXPRESSION changes value.
    • The JAVASCRIPT_STATEMENT associated with a SIGNAL_HANDLER is evaluated every time the corresponding signal is emitted.

    [quote author="Binary91" date="1419677212"]when I use this line outside of the js statement using the colon ":", the binding works perfectly:
    @{
    image.source: mousearea.pressed ? "imgPressed.png" : "img.png"
    mousearea.onClicked:
    {
    }
    }@
    [/quote]

    • The QML attribute is image.source (a property)
    • Your JavaScript expression is mousearea.pressed ? "imgPressed.png" : "img.png"

    [quote author="Binary91" date="1419677212"]I don't understand why the following line in a js statement is a static assignment and not a property binding:
    @Button
    {
    mousearea.onClicked:
    {
    image.source = mousearea.pressed ? "imgPressed.png" : "img.png"
    }
    }@
    [/quote]

    • The QML attribute is mousearea.onclicked (a signal handler)
    • Your JavaScript statement is image.source = mousearea.pressed ? "imgPressed.png" : "img.png"
    • Remember, this statement is evaluated whenever the clicked signal is emitted.
      ** Every time you release the mouse button, the QML engine checks the value of mousearea.pressed, and then chooses a URL, and then assigns that URL to image.source
      ** mousearea.pressed is always false when you release the mouse button, so the same URL is chosen every time this statement is evaluated. That's why it feels like nothing is happening.

    [quote author="Binary91" date="1419677212"]when I try to use the colon ":" in the js statement like this:
    @{
    mousearea.onClicked:
    {
    image.source: mousearea.pressed ? "imgPressed.png" : "img.png"
    }
    }@
    , then QtCreator throws an error and doesn't compile my code...[/quote]

    • The QML attribute is mousearea.onclicked (a signal handler)
    • You attempted to use image.source: mousearea.pressed ? "imgPressed.png" : "img.png" as the JavaScript statement. However, this is illegal JavaScript code.
    • Remember, QML needs to follow the rules of JavaScript

    Is this understandable? If not, please let me know and I'll try to explain differently.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.