Dynamically assign Behavior animation to dynamically created components via JavaScript



  • In QtQuick 2 (Qt 5.x), is it possible to assign animation Behaviors dynamically to properties of dynamically created QtQuick components during run time via JavaScript? If yes: how?

    Rationale: I want to support multiple different animation types (scrolling, fading, scaling, etc.) for components and let the user of the app configure which one to use. So the selection of properties to animate (x, y, width, height, scale, rotation, opacity, etc.) has to be applied dynamically at run time rather than statically predefined in QML. Since I don't use predefined states - about everything is to be dynamic here - Transition-type animations are not an option either.

    If I were using QML with a predefined property to animate I would statically code something like:

    MyComponent {
        width: 100; height: 200
        Behavior on width { NumberAnimation { duration: 1000 } } }
    

    However, I mostly do without static QML but instantiate my components dynamically using JavaScript with Qt.createQmlObject() or Qt.createComponent() instead, like this:

    var comp = Qt.createQmlObject ('import QtQuick 2.3; Rectangle { }', root);
        comp.width  = 100;  // I actually use variables as values
        comp.height = 200;
    

    Then, how can I assign a Behavior animation to this dynamically created comp, e.g. to its width property, so that a NumberAnimation of duration 1000 applies to it?

    // ??? <-- script code to assign Behavior animation for width property of comp
        comp.width += 200; // This change of width should be animated
    

    I have been experimenting with approaches like this but they don't seem to work:

    var behavior = Qt.createQmlObject ('import QtQuick 2.3; Behavior { }', root);
    var anim = Qt.createQmlObject ('import QtQuick 2.3; NumberAnimation { }', root);
        anim.target = comp;
        anim.duration = 1000;
        anim.property = "width";
        behavior.animation = anim;
        myGlobals.behavior = behavior; // Prevent from being garbage collected
        comp.width += 200; // Should be animated but changes instantly
    

    Am I missing something or is this approach completely wrong?


  • Moderators

    Hi @Quteroid,
    If I understood you correctly I think you are close. Instead of creating two separate components create single component for Rectangle, Behavior and NumberAnimation. Try the following code. I Hope this is what you were trying :)

    import QtQuick 2.4
    
    Rectangle {
        id: root
        width: 300
        height: 300
    
        MouseArea {
            anchors.fill: parent
            onClicked: {
                var prop = "width"
                var rect = Qt.createQmlObject ('import QtQuick 2.4; Rectangle { color: "red" \n Behavior on '+prop+' { NumberAnimation { duration: 1000 } } }', root);
                rect.width = 300
                rect.height = 300
            }
        }
    }
    
    

    To animate other properties, assign that property as a string to the prop variable.



  • @p3c0 said:

    Hi @Quteroid,
    If I understood you correctly I think you are close. Instead of creating two separate components create single component for Rectangle, Behavior and NumberAnimation. Try the following code. I Hope this is what you were trying :)

    It is close and if I won't find a better way this is probably what I'll have to stick with. However, it falls short of what I'm trying to achieve in three aspects:

    1.: depending on the type of animation selected by the user, the number of Behaviors + Animations may vary. For instance, moving component from top left to middle would require both x and y. Additionally animating width and height (for a shift-in effect) and opacity could also be an option I'd like to implement. So the number of Behaviors + Animations should be dynamic as well.

    2.: type of animation may change during existence of a component. For instance, it may fade in during creation but be moved away to bottom right during disposal. What type of animation to apply for disposal may not be known during (or change after) creation of the component. So the animation behavior should be applied / changeable without (re)creating the component.

    3.: as I'm working on a highly dynamic GUI concept for my app (you guessed it?), I try to make as little use of static QML as possible and use JavaScript (for the time being, later hopefully mostly C++) to build the GUI. So I'd like to figure how to recreate the "Behavior on" construct of QML in code. First in JavaScript, later perhaps in C++.

    Perhaps I should rephrase (or extend) my question to: is it possible to recreate / emulate the "Behavior on" syntax and animation assignment in JavaScript (or C++) on a per QmlObject basis?

    I've also tried changing parents for Qt.createQmlObject() calls from root to various combinations of comp, anim and behavior, but to no avail.

    I also noticed that PropertiesAnimation has property "properties", to animate multiple properties within one animation instance. If possible, I'd like to utilize this optimization as well (via code). This seems not possible with "Behavior on" syntax in QML, as it requires one Behavior + Animation per property?


  • Moderators

    @Quteroid

    is it possible to recreate / emulate the "Behavior on" syntax and animation assignment in JavaScript (or C++) on a per QmlObject basis?

    It has to be on per QML Object basis. AFAIK you can't load just Behavior or NumberAnimation using createQmlObject as they are not Item's.

    I also noticed that PropertiesAnimation has property "properties", to animate multiple properties within one animation instance. If possible, I'd like to utilize this optimization as well (via code).

    yes multiple properties are possible but as said earlier cant just inject them into existing QML.



  • Meanwhile I have found a way to achieve at least what I wanted to achieve, although not quite how I originally intended it. Reminder: my goal is to assign animations fully dynamically to any number of properties of a component, and possibly change these assignments during lifetime of the object.

    I have found no way to create operational Behavior objects in JavaScript. Furthermore, when attaching dynamically created Animation objects to Behavior objects and trying to trigger them manually (via start(), restart() or running = true) the error "setRunning() cannot be used on non-root animation nodes" occurs.

    However, just creating Animation objects and attaching them to target objects and their properties without Behaviors at all permits at least functional animations, created in a fully dynamic way. Something like this works:

    function testAnim (parent) {
        var comp = Qt.createQmlObject ('import QtQuick 2.3; Rectangle { }', parent);
        var anim = Qt.createQmlObject ('import QtQuick 2.3; PropertyAnimation { }', parent);
            comp.width = 100;
            comp.height = 100;
            anim.target = comp;
            anim.property = "width";
            anim.from = comp.width;
            anim.to = 200;
            anim.duration = 5000;
            anim.restart ();
    }
    

    Downside of this approach is that I cannot simply assign comp.width = 200 to get it animated, as would be the case with working Behaviors. Instead I'll have to create a little framework module (I name it AnimMonitor) that keeps track of what properties of what components are attached to animations. It's sort of an own implementation of Behavior. Fortunately, this is not too complex and consists mainly of four methods:

    // Register new animated property for a component
    AnimMonitor.register (targetComponent, componentProperty, animObject) {...}
    // Always use this method for all property accesses to all objects from now on.
    // If property is not animated, set it directly. Else reprogram attached Anim object.
    AnimMonitor.setProperty (targetComponent, componentProperty, newValue) {...}
    // Unregister particular property animation from existing component
    AnimMonitor.unregister (targetComponent, componentProperty) {...}
    // Unregister all animations of a component; must be called before destruction
    AnimMonitor.unregisterComponent (targetComponent) {...}
    

    So I'll always have to use the wrapper method setProperty() from now on. This and the fact that animations cannot get auto-garbage-collected on component destruction but must be explicitly unregistered seems a bit cumbersome but I think this is the price to pay for this fully dynamic approach, which QtQuick apparently is not prepared to support directly (as yet). Or is it?


  • Moderators

    @Quteroid

    Downside of this approach is that I cannot simply assign comp.width = 200 to get it animated, as would be the case with working Behaviors

    Property bindings from JavaScripts can be created too.

    property bool animationState: false
    
    function testAnim (parent) {
        var comp = Qt.createQmlObject ('import QtQuick 2.3; Rectangle { }', parent);
        var anim = Qt.createQmlObject ('import QtQuick 2.3; PropertyAnimation { }', parent);
            comp.width = 100;
            comp.height = 100;
            anim.target = comp;
            anim.property = "width";
            anim.from = comp.width;
            anim.to = 200;
            anim.duration = 5000;
            anim.running = Qt.binding(function() { return animationState })
    }
    

    and then when required set animationState to true.



  • @p3c0 said:

    Property bindings from JavaScripts can be created too.

    property bool animationState: false
    
    function testAnim (parent) {
        var comp = Qt.createQmlObject ('import QtQuick 2.3; Rectangle { }', parent);
        var anim = Qt.createQmlObject ('import QtQuick 2.3; PropertyAnimation { }', parent);
            comp.width = 100;
            comp.height = 100;
            anim.target = comp;
            anim.property = "width";
            anim.from = comp.width;
            anim.to = 200;
            anim.duration = 5000;
            anim.running = Qt.binding(function() { return animationState })
    }
    

    and then when required set animationState to true.

    No, property bindings are not of any help here. They are just used to synchronize values of two properties, i.e. provide another sort of alias mechanism. Your example just provides an alternate way to state "anim.running = true" by stating "animationState = true". It doesn't provide new functionality but just consumes a bit more of memory and, in this example, pollutes the (global) namespace.

    Besides: the statement "property bool animationState: false" is static QML and not JavaScript. However, as mentioned earlier, except for the root element I work essentially without static QML but prefer to create objects and values dynamically in code. Also regard that I may have n Animation objects (dynamically) instantiated. They may be triggered independently. So one global state property to control them all is not appropriate.

    Property bindings have other uses. For instance, I think one could synchronize animations for several components (or properties) with just one Animation object. For instance, if comp is the original component and comp2 is another one in reach, then "comp2.width = Qt.binding (function () { return comp.width; })" would synchronously animate the widths of both comp and comp2 with just one Animation instance. This without QML properties or namespace pollutions ;-)

    Thanks for your thoughts on the matter, nevertheless.


Log in to reply
 

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