End to End GUI Development with Qt5
上QQ阅读APP看书,第一时间看更新

Styling the navigation bar

Starting with the easy part, let’s first move our hard-coded colors and icon pixel size from NavigationButton into Style.qml:

readonly property color colourNavigationBarBackground: "#000000"
readonly property color colourNavigationBarFont: "#ffffff"
readonly property int pixelSizeNavigationBarIcon: 42

We now need to think about how we want to size the elements of our button. We have an icon which we want to be square, so the width and height will be the same. Next, to that, we have a text description that will be the same height as the icon but will be wider:

The width of the entire component is the width of the icon plus the width of the description. The height of the entire component is the same as both the height of the icon and description; however, it gives us more flexibility to make the height the same as whichever is the larger of the two. That way, if we ever decide to make one item larger than the other, we know that the component will be large enough to contain them both. Let’s pick starter sizes of 80 x 80 for the icon and 80 x 240 for the description and define the properties:

readonly property real widthNavigationButtonIcon: 80
readonly property real heightNavigationButtonIcon: widthNavigationButtonIcon
readonly property real widthNavigationButtonDescription: 240
readonly property real heightNavigationButtonDescription: heightNavigationButtonIcon
readonly property real widthNavigationButton: widthNavigationButtonIcon + widthNavigationButtonDescription
readonly property real heightNavigationButton: Math.max(heightNavigationButtonIcon, heightNavigationButtonDescription)

There are a couple of things to note here. Properties can be bound directly to other properties, which reduces the amount of duplication and makes the whole setup much more dynamic. We know that we want our icon to be square, so by binding the height to be the same as the width, if we want to change the total size of the icon, we just need to update the width, and the height will automatically update. QML also has strong integration with a JavaScript engine, so we can use the Math.max() function to help us figure out which is the larger height.

Another thing we would like the navigation buttons to do is to provide some kind of visual cue when the user hovers the mouse over a button to indicate that it is an interactive element. To do that, we need each button to have its own background rectangle.

In the NavigationButtonwrap the Row element in a new Rectangle and plug the sizes into our component:

Item {
    property alias iconCharacter: textIcon.text
    property alias description: textDescription.text
width: Style.widthNavigationButton height: Style.heightNavigationButton
Rectangle { id: background anchors.fill: parent color: Style.colourNavigationBarBackground Row { Text { id: textIcon width: Style.widthNavigationButtonIcon height: Style.heightNavigationButtonIcon font { family: Style.fontAwesome pixelSize: Style.pixelSizeNavigationBarIcon } color: Style.colourNavigationBarFont text: "uf11a" } Text { id: textDescription width: Style.widthNavigationButtonDescription height: Style.heightNavigationButtonDescription color: Style.colourNavigationBarFont text: "SET ME!!" } } } }

Run again, and you’ll see a slight improvement:

We’re getting part of the description cut off because our navigation bar is hard-coded to be 100 pixels wide. We need to change this and also implement the toggle expanded/collapsed functionality. We have already calculated the sizes we need, so let’s prepare by adding a couple of new properties to Style.qml:

readonly property real widthNavigationBarCollapsed: widthNavigationButtonIcon
readonly property real heightNavigationBarExpanded: widthNavigationButton

The collapsed state will be just wide enough for the icon, while the expanded state will contain the entire button, including description.

Next, let’s encapsulate our navigation bar in a new component. There won’t be any reuse benefits in this case as there will only ever be one, but it helps keep our QML organized and makes MasterView more concise and easy to read.

You can right-click on the Rectangle component in MasterView and refactor our navigation bar into a new QML file, as we did for our NavigationButton. However, let’s do it manually so that you are comfortable with both approaches. Right-click on components.qrc and select Add New… > Qt > QML File. Add NavigationBar.qml to cm/cm-ui/components:

Edit components.qrc and move our new NavigationBar into the /components prefix section with an alias:

<file alias="NavigationBar.qml">components/NavigationBar.qml</file>

Add the component to our components module by editing qmldir:

NavigationBar 1.0 NavigationBar.qml

Cut the Rectangle and its child elements from MasterView and paste it into NavigationBar.qml inside the root Item element. Update the QtQuick module import to version 2.9 if it has been initialized to some older version. Add an import for our assets module to gain access to our Style object. Move the Rectangle's anchors and width properties to the root Item and set the Rectangle to fill its parent:

import QtQuick 2.9
import assets 1.0
Item { anchors { top: parent.top bottom: parent.bottom left: parent.left } width: 100
Rectangle { anchors.fill: parent color: "#000000"
Column { NavigationButton { iconCharacter: "uf0c9" description: "" } NavigationButton { iconCharacter: "uf015" description: "Dashboard" } NavigationButton { iconCharacter: "uf234" description: "New Client" } NavigationButton { iconCharacter: "uf002" description: "Find Client" } } } }

Back in MasterView, you can now add the new NavigationBar component in where the Rectangle used to be:

NavigationBar {
 id: navigationBar
}

Although you get the dreaded red squigglies again, you will actually be able to run the application and verify that the refactoring hasn’t broken anything.

The anchoring of our new NavigationBar component is fine, but the width is a little more complicated—how do we know whether it should be Style.widthNavigationBarCollapsed or Style.heightNavigationBarExpanded? We’ll control this with a publicly accessible Boolean property that indicates whether the bar is collapsed or not. We can then use the value of this property to decide which width we want using the conditional ? operator syntax. Set the property to be true initially, so the bar will render in its collapsed state by default:

property bool isCollapsed: true

With that in place, replace the hard-coded width of 100, as follows:

width: isCollapsed ? Style.widthNavigationBarCollapsed : Style.heightNavigationBarExpanded

Next, update the color property of Rectangle to Style.colourNavigationBarBackground:

We’re getting there now, but one key thing we’ve missed along the way is that clicking on the buttons now doesn’t actually do anything anymore. Let’s fix that next.