Creating QML Controls From Scratch: Tabs

Creating QML Controls From Scratch: Tabs

By Chris Cortopassi

Tabs

Continuing our QML Controls from Scratch series, this time we will implement Tabs. They are used to expand limited screen real estate by providing two or more "tabs" that divide the user interface into screens (content), only one of which is shown at a time. The Tabs control only renders the tabs themselves. A screen (content) for each tab must be implemented separately.

Tabs has two public properties of interest: model (an array of strings) and currentIndex (indicating the currently selected tab), both of which are from ListView. To implement the content for each tab, simply provide an Item for each tab, and connect each Item's visible property to currentIndex in a binding. For instance:

Item {
    visible: tabs.currentIndex == 0
    ...

Here are a couple of our tricks for implementing Tabs:

1. To render a half-rounded Rectangle for each tab, we wrap a rounded Rectangle (twice the tab height) in an Item whose clip property is set true to hide the lower half the the Rectangle (and its two unwanted bottom rounded corners).

2. To render the horizontal line at the bottom of Tabs (implemented with three Rectangles), we draw outside the bounding rectangle of the delegate. This technique is not usually needed nor recommended, but it does come in handy once in a while (as here). The horizontal line is broken into two lines: one left of the selected tab and one right of the selected tab.

Tabs.qml

import QtQuick 2.0

ListView {
    id: root
    
// public
    model:          []//'Zero', 'One', 'Two']
    currentIndex:   0
    
// private
    width: 500;  height: 100 // default size

    orientation: ListView.Horizontal
    interactive: false
    spacing: 0.1 * height
    clip: true // horizontal line
    
    header: Item{width: root.width - count * (2 * 0.7 * root.height + spacing)} // left

    delegate: Item { // tab
        width: 2 * height;  height: 0.7 * root.height
        y: root.height - height            // align bottom
        opacity: mouseArea.pressed? 0.3: 1 // pressed state
    
        Item { // don't clip horizontal line
            anchors.fill: parent
            clip: true
            
            Rectangle { // background
                width: parent.width;  height: 2 * parent.height
                border.width: 0.02 * root.height
                radius: 0.2 * root.height
                color: currentIndex == index? 'transparent': 'black'
            }
        }

        Text {
            text: modelData
            font.pixelSize: 0.3 * root.height
            anchors.centerIn: parent
            color: currentIndex == index? 'black': 'white'
        }
        
        Rectangle { // horizontal line at bottom left
            visible: currentIndex == index;  
            anchors{bottom: parent.bottom;  right: parent.left}
            width: root.width;    height: 0.02 * root.height
            color: 'black'//green'
        }
        
        Rectangle { // horizontal line at bottom right
            visible: currentIndex == index;
            anchors{bottom: parent.bottom;  left:  parent.right}
            width: root.width;    height: 0.02 * root.height
            color: 'black'//blue'
        }
        
        MouseArea {
            id: mouseArea
            
            anchors.fill: parent
            enabled: currentIndex != index
            
            onClicked:  currentIndex = index
        }
    }
}

Test.qml

import QtQuick 2.0

Tabs {
    model:          ['Zero', 'One', 'Two']
    currentIndex:   0
}

Summary

In this post, we created a Tabs control, which is useful in situations when you have limited screen space. Next time we'll create a Table. The source code can be downloaded here.