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

Custom TextBox

We’ll start with the name data item of our client. Back when we worked with another QString property in our UI with the welcome message, we displayed it with the basic text component. This component is read only, so to view and edit our property, we will need to reach for something else. There are a couple of options in the base QtQuick module: TextInput and TextEdit. TextInput is for a single line of editable plain text, while TextEdit handles multiline blocks of text and also supports rich text. TextInput is ideal for our name.

Importing the QtQuick.Controls module makes additional text-based components like Label, TextField, and TextArea available. Label inherits and extends Text, TextField inherits and extends TextInput and TextArea inherits and extends TextEdit. The basic controls are enough for us at this stage, but be aware that these alternatives exist. If you find yourself trying to do something with one of the basic controls which it doesn’t seem to support, then import QtQuick.Controls and take a look at its more powerful cousin. It may well have the functionality you are looking for.

Let's build on what we've learned and create a new reusable component. As usual, we'll begin by preparing the Style properties we'll need:

readonly property real sizeScreenMargin: 20
readonly property color colourDataControlsBackground: "#ffffff"
readonly property color colourDataControlsFont: "#131313"
readonly property int pixelSizeDataControls: 18
readonly property real widthDataControls: 400
readonly property real heightDataControls: 40

Next, create StringEditorSingleLine.qml in cm/cm-ui/components. It’s not the most beautiful of names, but at least it’s descriptive!

It's generally helpful to use a prefix with custom QML views and components to help distinguish them from the built-in Qt components and avoid naming conflicts. If we were using that approach with this project, we could have called this component CMTextBox or something equally short and simple. Use whatever approach and conventions work for you, it makes no functional difference.

Edit components.qrc and qmldir as we did previously to make the new component available in our components module.

What we're trying to achieve with this component is as follows:

  • To be able to pass in any StringDecorator property from any data model and view/edit the value
  • View a descriptive label for the control as defined in the ui_label property of the StringDecorator
  • View/edit the ui_value property of the StringDecorator in a TextBox
  • If the window is wide enough, then the label and textbox are laid out horizontally
  • If the window is not wide enough, then the label and textbox are laid out vertically

With these goals in mind, implement StringEditorSingleLine, as follows:

import QtQuick 2.9
import CM 1.0
import assets 1.0
Item { property StringDecorator stringDecorator
height: width > textLabel.width + textValue.width ?
Style.heightDataControls : Style.heightDataControls * 2
Flow { anchors.fill: parent
Rectangle { width: Style.widthDataControls height: Style.heightDataControls color: Style.colourBackground Text { id: textLabel anchors { fill: parent margins: Style.heightDataControls / 4 } text: stringDecorator.ui_label color: Style.colourDataControlsFont font.pixelSize: Style.pixelSizeDataControls verticalAlignment: Qt.AlignVCenter } }
Rectangle { id: background width: Style.widthDataControls height: Style.heightDataControls color: Style.colourDataControlsBackground border { width: 1 color: Style.colourDataControlsFont } TextInput { id: textValue anchors { fill: parent margins: Style.heightDataControls / 4 } text: stringDecorator.ui_value color: Style.colourDataControlsFont font.pixelSize: Style.pixelSizeDataControls verticalAlignment: Qt.AlignVCenter } }
Binding { target: stringDecorator property: "ui_value" value: textValue.text } } }

We begin with a public StringDecorator property (public because it is in the root Item element), which we can set from outside of the component.

We introduce a new kind of element—Flow—to lay out our label and textbox for us. Rather than always laying out content in a single direction like row or column, the Flow item will lay out its child elements side by side until it runs out of available space and then wraps them like words on a page. We tell it how much available space it has to play with by anchoring it to the root Item.

Next comes our descriptive label in a Text control and the editable value in a TextInput control. We embed both controls in explicitly sized rectangles. The rectangles help us align the elements and give us the opportunity to draw backgrounds and borders.

The Binding component establishes a dependency between the properties of two different objects; in our case, the TextInput control called textValue and the StringDecorator instance called stringDecorator. The target property defines the object we want to update, the property is the Q_PROPERTY we want to set, and value is the value we want to set it to. This is a key element that gives us true two-way binding. Without this, we will be able to view the value from the StringDecorator, but any changes we make in the UI will not update the value.

Back in CreateClientView, replace the old Text element with our new component and pass in the ui_name property:

StringEditorSingleLine {
    stringDecorator: newClient.ui_name
}

Now build and run the app, navigate to the Create Client view, and try editing the name:

If you switch to the Find Client view and back again, you will see that the value is retained, demonstrating the updates are successfully being set in the string decorator.

Our newly bound view isn't exactly overflowing with data just yet, but over the coming chapters, we will add more and more to this view, so let's add a few finishing touches to prepare us.

Firstly, we only need to add another three or four properties to the view, and we'll run out of space as the default size we’ve set for the window is very small, so in MasterView bump the window size up to something comfortable for your display. I'll treat myself and go full HD at 1920 x 1080.

Even with a larger window to work with, we still need to prepare for the possibility of overflow, so we'll add our content to another new element called ScrollView. As its name suggests, it works in a similar way to flow and manages its content based on the space it has available to it. If the content exceeds the available space, it will present scrollbars for the user. It's also a very finger friendly control and on a touch screen, the user can just drag the content rather than having to fiddle around with a tiny scrollbar.

Although we only have one property currently, when we add more, we will need to lay them out so we'll add a column.

Finally, the controls are stuck to the bounds of the view, so we'll add a little gutter around the view and some spacing in the column.

The revised view should look as follows:

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
Rectangle { anchors.fill: parent color: Style.colourBackground }
ScrollView { id: scrollView anchors { left: parent.left right: parent.right top: parent.top bottom: commandBar. top margins: Style.sizeScreenMargin } clip: true Column { spacing: Style.sizeScreenMargin width: scrollView.width StringEditorSingleLine { stringDecorator: newClient.ui_name anchors { left: parent.left right: parent.right } } } }
CommandBar { id: commandBar commandList: masterController.ui_commandController.ui_createClientViewContextCommands } }

Build and run, and you should see the nice neat screen margin. You should also be able to resize the window from wide to narrow and see the string editor automatically adjust its layout accordingly.