Monday, April 7, 2014

Adding Children to Extended Cascades Controls With QML

It doesn't take very long after starting to develop for BlackBerry 10 Cascades that a developer comes to a point when it is desirable to encapsulate some functionality into a user interface object by extending an existing control, or starting fresh with CustomControl. Not long after that a developer will want to add children to the control using QML something like this:


                RadialMenu {
                    id: radialMenu
                    easingCurve: StockCurve.ElasticOut
                    RadialMenuActionItem {
                        imageSource: "asset:///ic_share.png"
                    }
                } 


RadialMenu extends CustomControl, RadialMenuActionItem extends ImageView. If you are experienced with Qt development you probably already know how to make this happen, but Cascades is different enough from pure Qt that a BlackBerry developer quickly learns not to delve too deeply into Qt. If you follow the Cascades documentation and try this at run time you will see the following error:

bb::cascades::QmlDocument: error when loading QML from:   QUrl( "asset:///main.qml" )  
--- errors:  (asset:///main.qml:77:21: Cannot assign to non-existent default property) 
bb::cascades::QmlDocument:createRootObject document is not loaded or has errors, can't create root

Default property? To make a long story short this is the clue to solving the problem. In QML child nodes are assigned to a property even if it doesn't appear to be the case. It turns out that a container has a property called controls that works very much like the property attachedObjects. But we don't want to have to declare our children using a different, perhaps confusing syntax like this:


                RadialMenu {
                    id: radialMenu
                    easingCurve: StockCurve.ElasticOut
                    controls: [
                        RadialMenuActionItem {
                            imageSource: "asset:///ic_share.png"
                        },
                        ... // More children
                     ]
                } 

The secret is telling QML which property is the default property we want children assigned to unless otherwise specified. This is actually quite simple to do:


class RadialMenu :public CustomControl {
 Q_OBJECT

 Q_PROPERTY(QDeclarativeListProperty<QObject> actionItems READ actionItems)

 Q_CLASSINFO("DefaultProperty", "actionItems")

        // Other properties removed for clarity.
        ...
public:
 RadialMenu(Container *parent = 0);
 virtual ~RadialMenu();

 QDeclarativeListProperty<QObject> actionItems();

        ...

private:
 QList<QObject> mActionItems;
        ...
};


QDeclarativeListProperty<QObject> RadialMenu::actionItems() {
 qDebug() << __PRETTY_FUNCTION__;
 return QDeclarativeListProperty<QObject>(this, mActionItems);
}



This boilerplate is what allows QML to add children to the mActionItems member variable using the standard syntax. I have used QObject as the template type in this case. Ideally you may want to limit the type of children your control will accept. In my case I would like to limit children to being RadialMenuActionItems but the type must be know to the Qt system or it doesn't work.

No comments:

Post a Comment