Simple keyboardless touchscreen widgets in Qt - c++

I'm looking for a simple way to make widgets for a touch-screen that will allow users to set the time and IP address on the computer running the code and provide a simple (uppercase Latin-alphabetic) name.
This question is not about how to actually set the system time or IP address; I'm just looking for information about how to make the graphical widgets themselves.
What I want is for each editable property (time, address, and name) to be divided into "scrollable" fields, where the fields for "time" are hours, minutes, possibly seconds, and AM/PM/24-hr, and the fields for address/name are the individual characters. Each field would have an arrow above and below it, and touching on an arrow would scroll through the valid values for that field.
I think this is a pretty common UX pattern, especially in meatspace (e.g. on alarm clocks), but just in case it's not clear what I'm trying to describe, here's an example with a user editing the "name" property:
^^^
BN
vvv
User presses "down" below the "N":
^^^
BO
vvv
User presses "down" below the empty space:
^^^^
BOA
vvvv
...and again on the same down-arrow:
^^^^
BOB
vvvv
I'm writing this using C++14 with Qt 5. (If worst comes to worst, I'd be open to writing a separate app using a different language and/or framework, but I'm not asking for framework suggestions here; if you have one, let me know and I'll open a corresponding question on Software Recommendations SE.)
I don't see anything in the Qt 5 widget library like this; most of the input widgets are text fields. QSpinBox looks somewhat promising, but the arrows are probably too small for my touchscreen, and using a separate spinbox for each letter would probably be confusing and ugly.
I don't really know enough about Qt or GUI-programming in general to feel confident trying to write my own widgets from scratch, but this interface looks simple enough that I would expect a couple lines of QML would get me well on my way.

ListView as well as PathView can produce the desired result with slightly different behaviors and slightly different performances. Differently from ListView, PathView is circular, i.e. elements can be iterated continuously by using just one of the selection controls. It is also easier to fully customize the behavior of the path in PathView via the PathAttribute type. Anyhow path customization seems not to be a required feature, according to the question.
If you implement the solution via a ListView you should ensure that just one element is shown and that any model is processed.
Component {
id: spinnnnnnnner
Column {
width: 100
height: 110
property alias model: list.model
property string textRole: ''
spacing: 10
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "-"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.decrementCurrentIndex() }
}
ListView {
id: list
clip: true
width: 100
height: 55
enabled: false // <--- remove to activate mouse/touch grab
highlightRangeMode: ListView.StrictlyEnforceRange // <--- ensures that ListView shows current item
delegate: Text {
width: ListView.view.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 50
font.bold: true
text: textRole === "" ? modelData :
((list.model.constructor === Array ? modelData[textRole] : model[textRole]) || "")
}
}
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.incrementCurrentIndex() }
}
}
}
The checks over the model ensure that any type of model can be passed to the component. Here is an example using three very different models:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 400
height: 300
ListModel {
id: mod
ListElement {texty: "it1"}
ListElement {texty: "it2"}
ListElement {texty: "it3"}
}
Row {
Repeater {
id: rep
model: 3
delegate: spinnnnnnnner
Component.onCompleted: {
rep.itemAt(0).model = mod // listmodel
rep.itemAt(0).textRole = "texty"
rep.itemAt(1).model = 10 // number model
//
rep.itemAt(2).model = ["foo", "bar", "baz"] // array model
}
}
}
}
PathView implementation is not so different from the ListView one. In this case it is sufficient to define a vertical path and specify that just one one element is visible at a time via pathItemCount. Finally, setting preferredHighlightBegin/preferredHighlightEnd ensures that the visible element is centered in the view. The revisited component is the following:
Component {
id: spinnnnnnnner
Column {
width: 100
height: 110
property alias model: list.model
property string textRole: ''
spacing: 10
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "-"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.decrementCurrentIndex() }
}
PathView {
id: list
clip: true
width: 100
height: 55
enabled: false // <--- remove to activate mouse/touch grab
pathItemCount: 1
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
path: Path {
startX: list.width / 2; startY: 0
PathLine { x: list.width / 2; y: list.height }
}
delegate: Text {
width: PathView.view.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 50
font.bold: true
text: textRole === "" ? modelData :
((list.model.constructor === Array ? modelData[textRole] : model[textRole]) || "")
}
}
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.incrementCurrentIndex() }
}
}
}

Related

Changing the model does not redraw objects in QML sometimes

Repeater {
model: myModel.buttonParameters
delegate: Button
{
width: 47
height: 47
contentItem: Text {
id: content
text: modelData.name
font.family: MyStyle.fontFamily
fontSizeMode: Text.Fit
font.pixelSize: 30
font.styleName: "Bold"
topPadding: height / 6
color: modelData.visibility ? MyStyle.colorFromSeriesName(this.text) : MyStyle.dividerColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle
{
anchors.fill: parent
radius: 4
color: MyStyle.backgroundColor
border.color:MyStyle.dividerColor
border.width: 2
}
onClicked: {
cntModel.visibilityOfChartChanged(modelData.name, "plot");
}
}
}
On the C++ side.
myModel.buttonParameters is a QList<MyModel*> , where MyModel is a class inherited from QObject.
Q_PROPERTY(QVariant buttonParameters READ buttonParametersList NOTIFY buttonParametersChanged)
QVariant buttonParametersList()
{
return QVariant::fromValue(m_buttonParametersList );
}
The problem is that with a certain change in the model
(the signal buttonParametersChanged is sent), namely,
if the number of objects was equal to one and after the update there
is also one object, but with different characteristics, no redrawing
takes place, the old button remains. Moreover, it somehow depends on
the runtime. Also, if I remove the line with color, the model will update.
The issue is that buttonParametersChanged is a signal that triggers on the assignment of a new container (QList<>) to buttonParameters. It doesn't trigger on changes to the contents of an existing QList<> assigned to that property.
However, you can always manually trigger buttonParametersChanged when you know you've modified the contents of the QList<> which should give you the effect you want.
Note, a QML ListModel or C++ equivalent would likely be more appropriate for this use case. Many QML components are designed to specifically integrate with them and handle the cases of container contents changing.

Qt QML TableView Header not displaying [duplicate]

I am creating a Table using the new qml tableview (Qt 5.12).
I am able to create a model in C++ and able to populate the model in tabular format along with scrollbar.How do I add column headers to this table?
Code:
import QtQuick 2.12
import QtQuick.Controls 2.5
import Qt.labs.qmlmodels 1.0
//import QtQuick.Controls.Styles 1.4
import TableModel 0.1
Rectangle {
id:table
border.width: 3
border.color: 'dark blue'
QtObject{
id:internals
property int rows:0
property int col:0
property int colwidth:0
property var columnName:[]
}
function setRows(num){ internals.rows = num}
function setCols(num){ internals.col = num}
function setColWidth(num){internals.colwidth = num}
function setColNames(stringlist){
if(stringlist.length > 1)
internals.col = stringlist.length
dataModel.setColumnName(stringlist);
}
function addRowData(stringlist){
var len = stringlist.length
if(len >0)
{
dataModel.addData(stringlist)
}
}
TableModel {
id:dataModel
}
TableView{
id:tbl
anchors.top: headerCell
anchors.fill: parent
//columnSpacing: 1
//rowSpacing: 1
clip: true
ScrollBar.horizontal: ScrollBar{}
ScrollBar.vertical: ScrollBar{}
model:dataModel
Component{
id:datacell
Rectangle {
implicitWidth: 100
implicitHeight: 20
color: 'white'
border.width: 1
border.color: 'dark grey'
Text {
id:txtbox
anchors.fill: parent
wrapMode: Text.NoWrap
clip: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: display
}
}
}
}
function init(){
console.log("Calling init")
tbl.delegate= datacell
}
}
Currently TableView does not have headers so you should create it, in this case use Row, Column and Repeater.
On the other hand you must implement the headerData method and you must do it Q_INVOKABLE.
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
// ...
Q_INVOKABLE QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
// ...
TableView {
id: tableView
model: table_model
// ...
Row {
id: columnsHeader
y: tableView.contentY
z: 2
Repeater {
model: tableView.columns > 0 ? tableView.columns : 1
Label {
width: tableView.columnWidthProvider(modelData)
height: 35
text: table_model.headerData(modelData, Qt.Horizontal)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
Column {
id: rowsHeader
x: tableView.contentX
z: 2
Repeater {
model: tableView.rows > 0 ? tableView.rows : 1
Label {
width: 60
height: tableView.rowHeightProvider(modelData)
text: table_model.headerData(modelData, Qt.Vertical)
color: '#aaaaaa'
font.pixelSize: 15
padding: 10
verticalAlignment: Text.AlignVCenter
background: Rectangle { color: "#333333" }
}
}
}
The complete example you find here.
If you're using Qt 5.15, you can use a HorizontalHeaderView for column labels.
https://doc.qt.io/qt-5/qml-qtquick-controls2-horizontalheaderview.html
There's also VerticalHeaderView for row labeling.
https://doc.qt.io/qt-5/qml-qtquick-controls2-verticalheaderview.html
I'm new to the QML. I came to the answer of eyllanesc so many times through my struggle with the new TableView (qt 5.12+), so I wanna thank you and share what helped me even more.
It's this video:
Shawn Rutledge - TableView and DelegateChooser: new in Qt 5.12
part of Qt Virtual Tech Summit 2019
The discussed code
It's a bit long but he covers
the differences between old and new TableView
how to create universal model for the views
resizable headers
different representation per column type - DelegateChooser
sortable columns
column reorder

StackPanel Equivalent In Qt Quick 2 / QML - Problem With Width

I'm trying to make an equivalent to wpf stackpanel, I already had a logic and implemented it but something is wrong about width, I don't know how to create new components without getting into width loop binding, here is my stackpanel:
StackPanel.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import KiMa.Models 1.0
Item {
id:root
property var orientation : UOrientation.Horizontal
property int itemSpacing : 10
default property list<Item> pageData
Loader{
property var childs
anchors.fill: parent
id:loader
onChildsChanged: {
if(root.pageData != null){
for(var z = 0;z<root.pageData.length;++z){
root.pageData[z].parent = loader.childs
}
}
}
}
state: orientation == UOrientation.Horizontal ? "row": "col"
states: [
State {
name: "row"
PropertyChanges {
target: loader
sourceComponent : row
}
},
State{
name: "col"
PropertyChanges {
target: loader
sourceComponent : col
}
}
]
Component{
id:col
Column{
Component.onCompleted: {
childs = _col;
}
id:_col
width: parent.width
spacing: root.itemSpacing
}
}
Component{
id:row
Row{
Component.onCompleted: {
childs = _row
}
id:_row
width: parent.width
layoutDirection: Qt.RightToLeft
spacing: root.itemSpacing
}
}
}
and my orientation enum is like this:
#ifndef UORIENTATION_H
#define UORIENTATION_H
#include<QObject>
class UOrientation
{
Q_GADGET
public:
explicit UOrientation();
enum Orientation{
Horizontal,
Vertical
};
Q_ENUM(Orientation)
};
#endif // UORIENTATION_H
and usage example should be like this:
StackPanel{
x: 320
height: 50
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 25
Button{
}
Button{
}
}
you need to add this into main.cpp:
qmlRegisterUncreatableType<UOrientation>("KiMa.Models",1,0,"UOrientation","its not creatable type!");
This code is working , if you have anything to suggest me to change or you think I made a mistake let me know, the only problem I can see here is width binding.
I already tried using childrenRect but it is not working:
width: childrenRect.width
height: childrenRect.height
Note : stackpanel allowing you to stack item after item on top of each other you can set orientation to horizontal or vertical so in qt its a column and row together which i made it already.
vertical one :
horizontal one :
You can do this easily with a Grid by setting the number of columns.
If you want a separate component, you can create your StackPanel.qml with the following:
import QtQuick 2.0
Grid {
property int orientation: Qt.Horizontal
columns: orientation === Qt.Horizontal ? -1 : 1
}
If you want a scrollable object, you could also use a ListView with an ObjectModel model. ListView has an orientation property.

QML Label max width & multiline

I have a Label, but it should only be as long as the form. If the label is longer it should start on a new line.
How can I realize this? I am using QtQuick Controls 2.0
My current code for the Label:
Label {
id: lblMsg
width: ApplicationWindow.width - 10 // not working
text: "ajksdlldjklasdasdasdasdasdasdasdasdasdasdasdasdasdasdasd"
x: 20
y: 20
}
add this line:
wrapMode: Text.Wrap
Use the wrapMode property:
Label {
id: lblMsg
width: ApplicationWindow.width - 10
text: "ajksdlldjklasdasdasdasdasdasdasdasdasdasdasdasdasdasdasd"
x: 20
y: 20
wrapMode: Label.WordWrap
}
I'd suggest using Qt Quick Layouts to manage the layout of your application instead of sizing items manually.

Autocomplete and suggesstion in QML textInput element

I have a QML textInput element like this:
TextBox.qml
FocusScope {
id: focusScope
property int fontSize: focusScope.height -30
property int textBoxWidth: parent.width * 0.8
property int textBoxHeight: 45
property string placeHolder: 'Type something...'
property bool isUserInTheMiddleOfEntringText: false
width: textBoxWidth
height: textBoxHeight
Rectangle {
width: parent.width
height: parent.height
border.color:'blue'
border.width: 3
radius: 0
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
}
Text {
id: typeSomething
anchors.fill: parent; anchors.rightMargin: 8
verticalAlignment: Text.AlignVCenter
text: placeHolder
color: 'red'
font.italic: true
font.pointSize: fontSize
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
TextInput {
id: textInput
anchors {
right: parent.right
rightMargin: 8
left: clear.right
leftMargin: 8
verticalCenter: parent.verticalCenter
}
focus: true
selectByMouse: true
font.pointSize: fontSize
}
Text {
id: clear
text: '\u2717'
color: 'yellow'
font.pointSize: 25
opacity: 0
visible: readOnlyTextBox ? false : true
anchors {
left: parent.left
leftMargin: 8
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
onClicked: {
textInput.text = ''
focusScope.focus = true;
textInput.openSoftwareInputPanel()
}
}
}
states: State {
name: 'hasText'; when: textInput.text != ''
PropertyChanges {
target: typeSomething
opacity: 0
}
PropertyChanges {
target: clear
opacity: 0.5
}
}
transitions: [
Transition {
from: ''; to: 'hasText'
NumberAnimation {
exclude: typeSomething
properties: 'opacity'
}
},
Transition {
from: 'hasText'; to: ''
NumberAnimation {
properties: 'opacity'
}
}
]
}
I want to add autocomplete and suggestions like google search to this text box. Autocomple get data from database and database return a list of dictionaries by a pyside SLOT.(or c++ slot)
How I can do this work?
Take a look at this code: https://github.com/jturcotte/liquid/blob/master/qml/content/SuggestionBox.qml
I bet it will do the job.
Edit:
Code that linked above is somewhat complicated and requires C++ backend, so I simplified it and made pure Qml example application, that you can play with, edit a little and apply to your needs. Sources can be found here. Most important things there are:
This implementation of SuggestionBox that uses some sort of model as it's source for completing/suggesting something
Its signal itemSelected(item) will be emitted every time user clicks on item
Main component of application that binds its LineEdit component to SuggestionBox
Note that code is quite rough and written for a sake of example.
I was looking for something very similar: a QML autocomplete component built around QML TextField, rather than the lower-level, more flexible but also more work intensive TextInput as in the question.
Since I could not find that, I implemented it. If anyone wants to use it: it's licensed under MIT and available as part of an application I am developing. You find the component in src/qml/AutoComplete.qml, and the application may serve as usage example. Features:
highlighting of autocompleted characters in bold, as in Google Search
Key bindings (navigating with arrow keys, Return / Enter, Esc to close completion box, Esc Esc to unfocus)
uses a simple QStringList as model for now, with the application showing how to update the model with live SQL database queries when the next key is pressed
heavily documented code, so it should be easy enough to adapt
Let me know if this is useful, I might then package it as a Qt QPM package or even try to make it mature enough to be added to the QML UI library KDE Kirigami.