QML: Extract information from repeater - qt

In the snippet of code below I'm displaying a variable number of TextEdit items, each one being in a Rectangle which is the delegate of a repeater. When I hit the 'Play' button, the slot for that will need to collect the player names and call a Q_INVOKABLE method (not shown) to pass that data to the C++ backend. For the purposes of this question I'm just trying to display the player names in the onClicked slot of the Play button but am not sure how. I left '???' where I am trying to figure out how to retrieve information inside the rectangle of each delegate instance.
At this point I'm open to a solution to this approach or just being told that I'm approaching this the wrong way. I'm really pretty new to the QML side of Qt.
Thanks
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Item {
id: root
property int playerCount: playerCountSelect.currentValue
RowLayout {
id: layout
anchors.top: parent.top
anchors.topMargin: 5
spacing: 6
anchors.horizontalCenter: parent.horizontalCenter
Label {
text: "Enter number of players"
}
ComboBox {
id: playerCountSelect
model: [2, 3, 4]
}
}
RowLayout {
id: mainRow
anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
spacing: 6
RoundButton {
text: "Play"
width: 40
radius: 2
font.pointSize: 12
onClicked: {
for (var i =0; i < playerList.count; i++) {
console.log(playerList.itemAt(i).???)
}
}
}
Column {
spacing: 6
Repeater {
id: playerList
model: root.playerCount
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 120
height: 32
TextEdit {
font.pointSize: 12
text: "Player " + (index+1) + " name"
}
}
}
}
RoundButton {
text: "Quit"
width: 40
radius: 2
font.pointSize: 12
}
}
}

You just need to expose the property you want in the Repeater's delegate.
Repeater {
id: playerList
model: root.playerCount
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 120
height: 32
// Expose the player name
property alias playerName: textField.text
TextEdit {
id: textField
font.pointSize: 12
text: "Player " + (index+1) + " name"
}
}
}
Then you can access that property in your print statement by doing this:
console.log(playerList.itemAt(i).playerName)

Related

How to call data from elsewhere when I click a button in QML

When I click a button in QML, I want the data from another place that I created before to come to the textfields in my table, how can I do it? Can I do this in QML or do I need to create a separate backend cpp file? Can you help me please?
For example this below code is one row of my table
Rectangle{
border.width: 2
border.color: "black"
id:rectangle_mov_mean_nokta_sayisi
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: mainWindow.width/8
Layout.preferredHeight: mainWindow.height/22
Layout.margins: -3
Layout.fillWidth: true
color: row_even
Text{
color:normal_text
id:text_rectangle_mov_mean_nokta_sayisi
text:"Mov Mean Nokta Sayısı"
anchors.centerIn: parent
}
}
Rectangle{
border.width: 2
border.color: "black"
id:rectangle_mov_mean_nokta_sayisi_deger
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: mainWindow.width/8
Layout.preferredHeight: mainWindow.height/22
Layout.margins: -3
Layout.fillWidth: true
color:row_even
TextField {
id:textfield_rectangle_mov_mean_nokta_sayisi_deger
anchors.centerIn: parent
placeholderText: qsTr("")
color:normal_text
}
}
There are 20 of these rectangles. When I click a button, a separate value will be displayed for each text field. I need to create these values elsewhere.
you should read Signal and Slots in QML.
For example :
If you have 3 separate QML files:
In main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("External Components with signals and slots")
Notifier{
id : notifierId
rectColor: "yellowgreen"
target: receiverId
}
Receiver {
id : receiverId
rectColor: "dodgerblue"
anchors.right: parent.right
}
Component.onCompleted: {
notifierId.notify.connect(receiverId.receiveInfo)//Connect signal to slot
}
}
and in Receiver.qml :
import QtQuick 2.12
Item {
property alias rectColor: receiverRectId.color
width: receiverRectId.width
height: receiverRectId.height
function receiveInfo( count){
receiverDisplayTextId.text = count
console.log("Receiver received number : "+ count)
}
Rectangle {
id : receiverRectId
width: 200
height: 200
color: "red"
Text {
id : receiverDisplayTextId
anchors.centerIn: parent
font.pointSize: 20
text : "0"
}
}
}
and in Notifier.qml:
import QtQuick 2.12
Item {
property alias rectColor: notifierRectId.color
width: notifierRectId.width
height: notifierRectId.height
property int count: 0
signal notify( string count)//Declare signal
property Receiver target : null
onTargetChanged: {
notify.connect(target.receiveInfo)
}
Rectangle {
id : notifierRectId
width: 200
height: 200
color: "red"
Text {
id : displayTextId
anchors.centerIn: parent
font.pointSize: 20
text : count
}
MouseArea{
anchors.fill: parent
onClicked: {
count++
notify(count)
}
}
}
}
you can see that by using signals and slots you can send data from Notifier.qml to Receiver.qml.

QML - Using ListView with dynamically generated ListElement

I have this QML with the ListView:
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.0
import smah.light 1.0
import smah.zone 1.0
Page {
id: page
property var lights: ({})
property var lightNames: ([])
title: "Device control"
ListView {
id: lightList
anchors.fill: parent
model: listModel
delegate:
Rectangle{
width: lightList.width
height: lightList.height / 8
}
}
ListModel {
id: listModel
dynamicRoles: true
Component.onCompleted: {
listModel.clear()
for (var i=0; i<lights.length; i++) {
var component = Qt.createComponent("LightControls.qml",listModel)
listModel.append(component.createObject(lightList, {light_name: lights[i].getName }))
}
}
}
}
The LightControls.qml is:
import QtQuick 2.0
import QtQuick.Controls 2.5
import smah.light 1.0
Rectangle {
id: rectangle
property int light_type: 0
property int light_id: 0
property var light_name: "_DEF"
width: parent.width
height: 50
color: "#040000"
Text {
id: txtName
color: "#fefdfd"
text: qsTr(light_name)
anchors.left: parent.left
anchors.leftMargin: 15
anchors.top: parent.top
anchors.topMargin: 8
font.pixelSize: 20
}
Slider {
id: slider
x: 179
y: 10
width: 302
height: 22
value: 0.5
}
Text {
id: txtValue
x: 517
width: 45
height: 15
color: "#ffffff"
text: qsTr("Text")
anchors.top: parent.top
anchors.topMargin: 8
font.pixelSize: 20
}
Button {
id: button
x: 694
y: 10
text: qsTr("Button")
}
}
I want a clean scrollable list with each of these items generated shown in it. I've looked at using a Repeater instead of the list, but I'll have more elements in the list than are desired on the screen. When running the program, everything is garbled into a single incoherent mess:
There are a few larger issues with your code:
You're trying to add a visual item to a ListModel, which expects ListElement objects. You can use append() to add rows to a ListModel.
Your root delegate item is a Rectangle, which doesn't manage the layout of its children. Use a RowLayout or Row instead.
Your delegate should be delegate: LightControls {}.

How to change the page to the next or previous item of a SwipeView by clicking on the right and left arrow respectively?

I have a SwipeView that loads its internal elements through a Repeater and a Loader.
I would like to swipe between the items forward and backward by just clicking the arrows on the right and left of the SwipeView.
How can I implement this behavior in QML?
SwipeView {
id: __swipeView
Layout.fillHeight: true
Layout.fillWidth: true
Repeater {
model: 3
Loader {
source: "qrc:/../SwipeDelegate.qml"
}
}
}
Within your delegate, you can access the SwipeView via the SwipeView attached property, and then increment or decrement the current index as necessary:
import QtQuick 2.6
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
SwipeView {
anchors.fill: parent
Repeater {
model: 3
Item {
id: delegate
Button {
text: "<"
enabled: index > 0
onClicked: delegate.SwipeView.view.decrementCurrentIndex()
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
Label {
text: "Page " + (index + 1)
anchors.centerIn: parent
}
Button {
text: ">"
enabled: index < delegate.SwipeView.view.count - 1
onClicked: delegate.SwipeView.view.incrementCurrentIndex()
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
It's important to use the functions as opposed to setting currentIndex directly, for the reasons described here.

Keep input field in view while using on-screen keyboard

I've a virtual keyboard which pops-up from the bottom of the screen and always stays on top. I'm going to use this in my application and have a small problem.
If the text input field which accepts input from this keyboard is in middle / bottom of the view (main window / screen), it gets hidden behind the keyboard i.e., can't see whats been entered until the keyboard is hidden.
Keyboard is running as platforminputcontext plugin which will know the field that is accepting the input.
void KeyboardPlatformInputContext::setFocusObject(QObject* object)
{
qDebug() << m_focusedObject << object;
m_focusedObject = object;
}
When the keys are pressed, they are passed as QEvents like this
void KeyboardPlatformInputContext::processNormalKeyClick(const QString& key)
{
qDebug() << m_focusedObject << key;
if (m_focusedObject) {
QInputMethodEvent inputEvent;
inputEvent.setCommitString(key);
QGuiApplication::sendEvent(m_focusedObject, &inputEvent);
}
}
Now, with the available information (m_focusedObject and QGuiApplication) can it be possible to do something to keep the input field in view. Always.
Kuba has the right idea; I'll just expand on it. You can use Flickable, for example, to manage the content of your application. For example, suppose your application was laid out like a form:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: root
width: 480
height: 800
visible: true
Column {
anchors.fill: parent
anchors.margins: 20
spacing: 20
Repeater {
model: 20
Row {
spacing: 20
Text {
text: "Input #" + (index + 1)
anchors.verticalCenter: parent.verticalCenter
}
TextInput {
width: 100
height: 30
onActiveFocusChanged: {
if (activeFocus)
keyboardRect.visible = activeFocus
}
Rectangle {
border.width: 1
anchors.fill: parent
anchors.margins: -1
z: -1
}
}
}
}
}
Rectangle {
id: keyboardRect
width: parent.width
height: parent.height * 0.3
anchors.bottom: parent.bottom
color: "grey"
visible: false
}
}
To make it usable with a virtual keyboard, move the content into a Flickable:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: root
width: 480
height: 800
visible: true
Flickable {
id: flickable
anchors.fill: parent
anchors.margins: 20
anchors.bottomMargin: keyboardRect.visible ? keyboardRect.height : anchors.margins
contentWidth: column.implicitWidth
contentHeight: column.implicitHeight
flickableDirection: Flickable.VerticalFlick
Column {
id: column
spacing: 20
Repeater {
model: 20
Row {
spacing: 20
Text {
text: "Input #" + (index + 1)
anchors.verticalCenter: parent.verticalCenter
}
TextInput {
width: 100
height: 30
onActiveFocusChanged: {
if (activeFocus) {
keyboardRect.visible = activeFocus
var posWithinFlickable = mapToItem(column, 0, height / 2);
flickable.contentY = posWithinFlickable.y - flickable.height / 2;
}
}
Rectangle {
border.width: 1
anchors.fill: parent
anchors.margins: -1
z: -1
}
}
}
}
}
}
Rectangle {
id: keyboardRect
width: parent.width
height: parent.height * 0.3
anchors.bottom: parent.bottom
color: "grey"
visible: false
}
}
A few things to note:
anchors.bottomMargin: keyboardRect.visible ? keyboardRect.height : anchors.margins
This ensures that the content is "pushed" up when the keyboard is visible, so that nothing is hidden below it.
onActiveFocusChanged: {
if (activeFocus) {
keyboardRect.visible = activeFocus
var posWithinFlickable = mapToItem(column, 0, height / 2);
flickable.contentY = posWithinFlickable.y - flickable.height / 2;
}
}
This code doesn't account for losing focus and hence the keyboard always stays open.
We focus the Flickable on the current input field by mapping the position of the field to the Column.
Finally, you'll see a bit of jumping around when you click on the fields near the top or bottom of the column. This can be probably solved by not setting the contentY if the field is near the top or bottom. An exercise for the reader. :)
For me correct answer is above (first one) plus following:
https://doc.qt.io/qt-5/qtvirtualkeyboard-deployment-guide.html#creating-inputpanel
import QtQuick 2.0
import QtQuick.VirtualKeyboard 2.1
Item {
id: root
Item {
id: appContainer
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: inputPanel.top
...
}
InputPanel {
id: inputPanel
y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height
anchors.left: parent.left
anchors.right: parent.right
}
}
Quote:
The input panel must be a sibling element next to the application
container. It is important not to put the input panel within the
application container, as it would then overlap with the contents of
the application. Also, the input panel height will be automatically
updated according to the available width; the aspect ratio of the
input panel is constant.

how to load a new screen in QML showing a new list based on previous user click input?

In my application i show a nested list, that shows groups and folders as its children. I have built the functions necesary to generate a new list in the backend in c++ based on which item is clicked by the user.
I allready have the necesary functionality to pass the list to qml through QProperty.
so my question is, how do i previous listviews and show new ones dynamically. Considering it should also be possible to click the button "back", which should load the previous page again showing the groups and the folders.
this is the code i have now, showing the groups and its children(folders)
import QtQuick 2.4
import QtQuick.Window 2.2
//import ListMode 1.0
Rectangle {
height: 250
width: 140
color: "pink"
//property var aNum: 0
Component {
id: folderDelegate
Item {
width: 140
height: col2.childrenRect.height
Column {
id: col2
anchors.left: parent.left
anchors.right: parent.right
Rectangle {
height: 20
width: parent.width
border.color: "black"
MouseArea {
anchors.fill: parent
onClicked: treemodel.getObject(model.ID + ":" + model.Name)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
id: name1
text: model.Name
}
}
}
}
}
ListView {
id: outer
model: myModel
delegate: groupsDelegate
anchors.fill: parent
}
Component {
id: groupsDelegate
Item {
width: 140
height: col.childrenRect.height
Column {
id: col
anchors.left: parent.left
anchors.right: parent.right
Text {
anchors.horizontalCenter: parent.horizontalCenter
id: t1
font.bold: true
font.underline: true
font.pointSize: 9
text: model.Name
}
ListView {
id: folderlist
model: treemodel.lists[treemodel.modIndex]
delegate: folderDelegate
contentHeight: contentItem.childrenRect.height
height: childrenRect.height
anchors.left: parent.left
anchors.right: parent.right
clip: true
}
}
}
}
}
i have been reading documentations and searching forums, but the information is pretty overwhelming. So a pointer in the right direction would be appreciated.
the main model is setup for each item to have its own unique ID. So when an item is clicked, i run a function that grabs and stores the item based on the ID + name that was clicked
MouseArea {
anchors.fill: parent
onClicked :{
treemodel.getObject(model.ID + ":" + model.Name)
stackView.push(Qt.resolvedUrl("content/ButtonPage.qml"))
}
}
next, based on the item that was clicked i have functions that fill different QList items which are loaded into the ButtonPage.qml.
the function in c++ that is invoked is:
Q_INVOKABLE void getObject(QString index) {
clickedItemID = index;
getClickedItem();
getFilesByFolder();
}
now, i am not sure if this is a good solution. But for me it works. Maybe it will work for someone else too.

Resources