Wednesday, March 7, 2012

Loading Tabs Dynamically

The next task is to remove all hard-coded CustomTab elements from the custom tab control and load them all through JavaScript. Furthermore, verify that any settings can be loaded from the XML file. For the start, let's use the simple XML file, suspiciously similar to the one I've recently used to playing with a ListView element.

<?xml version="1.0" encoding="utf-8"?>
<tabList>
<tab>
<name>Tab 1</name>
<size>Medium</size>
</tab>
<tab>
<name>Tab 2</name>
<size>Medium</size>
</tab>
<tab>
<name>Tab 3</name>
<size>Medium</size>
</tab>
<tab>
<name>Tab 4</name>
<size>Medium</size>
</tab>
<tab>
<name>Tab 5</name>
<size>Medium</size>
</tab>
</tabList>

Just like in a ListView example, the XmlListModel element will be used to read data from the XML file. The usage of the XmlListModel is not limited to lists - data can be accessed directly. When the XmlListModel element is just created, it has no data. Then, at some point, data is loaded and the onCountChanged event fires. This fact is used to trigger the creation of tabs.

XmlListModel{
id: xmlTabModel
source: "tabs.xml"
query: "/tabList/tab"
XmlRole{name: "name"; query: "name/string()" }
XmlRole{name: "size"; query: "size/string()"}

onCountChanged: {
createTabs(count);
}
}

This line

var tab = Qt.createComponent("CustomTab.qml");

creates a Component object created using the QML file that is located at the address specified - can be a local file, or a URL, for example. The next point of interest,

var obj = tab.createObject(tabsRow);

creates an object instance of that component. The parameter is the parent object. So, I want the tab to be a child of the tabsRow. Next, some simple JavaScript follows - I'm dynamically assigning properties to the object which were previously hard-coded and make sure the initial position of the tabs is correct. The line

obj.tabtext = xmlTabModel.get(i).name;

confirms that my XML file was read correctly: I'm displaying the name property from the XML file on the tab. That's about all that's interesting about this example - the behavior or the visual layout did not change, just the way the tabs are created inside the tab control.

The CustomTab.qml did not change much. I only added a tabtext property that is used by a Text element to display some text that is retrieved from the XML file and confirms that the tab control works as expected. So all the code added to the CustomTab.qml is summarized below:

import QtQuick 1.0

Rectangle {
id: customTab
...

property string tabtext;

Text{
id: textOnTab;
text: tabtext;
y:30;
z:1;
}
...
}

The screenshot is about the same.

The Tab Control - Looks the Same, but Dynamic

The full main QML file for reference, there's much more JavaScript and less QML layout:

import QtQuick 1.0

Rectangle {
id: screen
width: 490; height: 400
property int numTabs: 5
property int margin: 2
property string transparentColor : "transparent"
property string redColor: "red"
property string grayColor: "#B7B9BC" // "lightgray"

function createTab(i, num)
{
var tab = Qt.createComponent("CustomTab.qml");

if(tab.status == Component.Ready)
{
var obj = tab.createObject(tabsRow);

if(obj == null)
return false;
if(i == 0)
obj.iPosition = 0;
else if(i == num-1)
obj.iPosition = 2;
else
obj.iPosition = 1;

obj.ctlHeight = tabsRow.height - margin;
obj.isSelected = false;
obj.anchors.bottom = tabsRow.bottom;
obj.anchors.bottomMargin = margin;
obj.tabtext = xmlTabModel.get(i).name;
if(i == 0)
{
obj.ctlWidth = tabsRow.width/num;
obj.anchors.left = tabsRow.left;
}
else
{
obj.ctlWidth = tabsRow.width/num - margin;
obj.anchors.left = tabsRow.children[i-1].right;
obj.anchors.leftMargin = margin;
}
}
else
{
return false;
}
return true;
}

function createTabs(num)
{
var i=0;

for(i=0;i<=num-1;i++)
{
var success = createTab(i, num);
if(!success)
{
console.log("Failed to create tab #" + i);
}
}

tabsRow.clearState();
}

XmlListModel{
id: xmlTabModel
source: "tabs.xml"
query: "/tabList/tab"
XmlRole{name: "name"; query: "name/string()" }
XmlRole{name: "size"; query: "size/string()"}

onCountChanged: {
createTabs(count);
}
}

Rectangle {
id: backRect
radius: 10
width: parent.width
height: parent.height // need to expand to free space
color: grayColor
anchors.top: parent.top
anchors { leftMargin: 10; bottomMargin: 10; topMargin: 10; rightMargin:10 }

Rectangle{
id: tabsRect
radius: 10
width: parent.width
height: 80
anchors.top: parent.top

Row{
id:tabsRow
width: parent.width
height: parent.height
anchors.fill: parent

function clearState()
{
var j=1;
for(j=0;j<= numTabs-1;j++)
{
children[j].isSelected = false;
children[j].state = "unselected";
children[j].applyState();
}
}
}
}
}
}

References

Dynamic Object Management in QML
QML Advanced Tutorial 2 - Populating the Game Canvas by . Also posted on my website

No comments: