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

Panels

Now, let's flesh out our CreateClientView a little so that we can actually save some meaningful data rather than just a bunch of empty strings. We still have lots of fields to add in, so we'll break things up a little, and also visually separate the data from the different models, by encapsulating them in discreet panels with descriptive titles and a drop shadow to give our UI a bit of pizzazz:

We’ll begin by creating a generic panel component. Create a new QML file in cm-ui/components named Panel.qml. Update components.qrc and qmldir, as we have done for all the other components:

import QtQuick 2.9
import assets 1.0
Item { implicitWidth: parent.width implicitHeight: headerBackground.height +
contentLoader.implicitHeight + (Style.sizeControlSpacing * 2)
property alias headerText: title.text property alias contentComponent: contentLoader.sourceComponent
Rectangle { id: shadow width: parent.width height: parent.height x: Style.sizeShadowOffset y: Style.sizeShadowOffset color: Style.colourShadow }
Rectangle { id: headerBackground anchors { top: parent.top left: parent.left right: parent.right } height: Style.heightPanelHeader color: Style.colourPanelHeaderBackground
Text { id: title text: "Set Me!" anchors { fill: parent margins: Style.heightDataControls / 4 } color: Style.colourPanelHeaderFont font.pixelSize: Style.pixelSizePanelHeader verticalAlignment: Qt.AlignVCenter } }
Rectangle { id: contentBackground anchors { top: headerBackground.bottom left: parent.left right: parent.right bottom: parent.bottom } color: Style.colourPanelBackground
Loader { id: contentLoader anchors { left: parent.left right: parent.right top: parent.top margins: Style.sizeControlSpacing } } } }

This is an extremely dynamic component. Unlike our other components, where we pass in a string or maybe even a custom class, here we are passing in the entire contents of the panel. We achieve this using a Loader component, which loads a QML subtree on demand. We alias the sourceComponent property so that calling elements can inject their desired content at runtime.

Due to the dynamic nature of the content, we can’t set the component to be a fixed size, so we leverage the implicitWidth and implicitHeight properties to tell parent elements how large the component wants to be based on the size of the title bar plus the size of the dynamic content.

To render the shadow, we draw a simple Rectangle, ensuring that it is rendered first by placing it near the top of the file. We then use the x and y properties to offset it from the rest of the elements, moving it slightly across and down. The remaining Rectangle elements for the header strip and panel background are then drawn over the top of the shadow.

To support the styling here, we need to add a collection of new Style properties:

readonly property real sizeControlSpacing: 10
readonly property color colourPanelBackground: "#ffffff"
readonly property color colourPanelBackgroundHover: "#ececec"
readonly property color colourPanelHeaderBackground: "#131313"
readonly property color colourPanelHeaderFont: "#ffffff"
readonly property color colourPanelFont: "#131313"
readonly property int pixelSizePanelHeader: 18
readonly property real heightPanelHeader: 40
readonly property real sizeShadowOffset: 5
readonly property color colourShadow: "#dedede"

Next, let’s add a component for address editing so that we can reuse it for both the supply and billing addresses. Create a new QML file in cm-ui/components named AddressEditor.qml. Update components.qrc and qmldir as earlier.

We’ll use our new Panel component as the root element and add an Address property, so that we can pass in an arbitrary data model to bind to:

import QtQuick 2.9
import CM 1.0
import assets 1.0
Panel { property Address address
contentComponent: Column { id: column spacing: Style.sizeControlSpacing StringEditorSingleLine { stringDecorator: address.ui_building anchors { left: parent.left right: parent.right } } StringEditorSingleLine { stringDecorator: address.ui_street anchors { left: parent.left right: parent.right } } StringEditorSingleLine { stringDecorator: address.ui_city anchors { left: parent.left right: parent.right } } StringEditorSingleLine { stringDecorator: address.ui_postcode anchors { left: parent.left right: parent.right } } } }

Here, you can see the flexibility of our new Panel component in action, thanks to the embedded Loader element. We can pass in whatever QML content we want, and it will be presented in the panel.

Finally, we can update our CreateClientView to add our new refactored address components. We’ll also move the client controls onto their own panel:

import QtQuick 2.9
import QtQuick.Controls 2.2 import CM 1.0 import assets 1.0 import components 1.0
Item { property Client newClient: masterController.ui_newClient
Column { spacing: Style.sizeScreenMargin anchors { left: parent.left right: parent.right top: parent.top margins: Style.sizeScreenMargin } Panel { headerText: "Client Details" contentComponent: Column { spacing: Style.sizeControlSpacing StringEditorSingleLine { stringDecorator: newClient.ui_reference anchors { left: parent.left right: parent.right } } StringEditorSingleLine { stringDecorator: newClient.ui_name anchors { left: parent.left right: parent.right } } } } AddressEditor { address: newClient.ui_supplyAddress headerText: "Supply Address" } AddressEditor { address: newClient.ui_billingAddress headerText: "Billing Address" } } CommandBar { commandList: masterController.ui_commandController.ui_createClientViewContextCommands } }

Before we build and run, we just need to tweak the background color of our StringEditorSingleLine textLabel so that it matches the panels they are now displayed on:

Rectangle {
    width: Style.widthDataControls
    height: Style.heightDataControls
    color: Style.colourPanelBackground
    Text {
        id: textLabel
        …
    }
}

Go ahead and create a new client and check the database. You should now see the supply and billing address details successfully saved. We’ve now got the C in our CRUD operational, so let’s move on to the ‘R’.