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

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.

Related

QML: Extract information from repeater

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)

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.

Signals between pages

I am trying to communicate between 2 QML pages.
In my page Main.qml I receive a signal from my C++ code. On receiving this signal I want text on InputPage.qml to change. This page is shown within Main.qml using a Loader. The only way I could find so far is to set up another signal between the 2 pages. However, I think there is a much easier way to do this. I already tried this way but I could not get it to work. So before I proceed I would like to know if this is the right method or not.
Any ideas on how to do this, and if the method described above is the correct one?
My code:
Main.qml
Item {
id: screen_InputPage
width: 1920
height: 930
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 100
visible: false
opacity: 1
Loader {//Loads the pages
id: pageLoader_ID2
source: "inputPage.qml"
}
}
And i would like to access the text(and maybe functions) placed on inputPage.qml
Text {
id: text_volume_perc_ID1
height: 48
text: qsTr("50")
anchors.right: parent.right
anchors.rightMargin: 0
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
anchors.top: parent.top
anchors.topMargin: 126
anchors.left: parent.left
anchors.leftMargin: 0
font.pixelSize: 42
}
To access the created object, you can use
idLoader.item.idInputpage.idText.text
I propose that you load objects dynamically to improve performance. To do so you can create your own CustomLoader.qml:
CustomLoader.qml
import QtQuick 2.0
Item {
id: idRoot
width: childrenRect.width
height: childrenRect.height
property Item createdObject
property string source
function fnSourceChange() {
if (""!== source){
var component
// create component
component = Qt.createComponent(source)
if (Component.Ready === component.status) {
createdObject= component.createObject(idRoot)
if(!createdObject)
console.log("Loader::Could not create the object ")
}
else {
console.log("Loader::Could not create panel", component.errorString(), "component has errors")
}
}
else {
createdObject.destroy();
createdObject = null
// unComment this line if you want to force the garbage collector
//gc()
}
}
onSourceChanged: {
fnSourceChange()
}
// even without that it should detect the source change and create it
// you can unComment this line if you want, but like that it will parse the function
// two times one on the sourceChanged signal and on on in this handler
// print the source or somthing in the function and you'll see
// Component.onCompleted: fnSourceChange()
}
main.qml
import QtQuick 2.0
Item {
id: screen_InputPage
width: 1920
height: 930
anchors.left: parent.left
anchors.leftMargin: 0
anchors.top: parent.top
anchors.topMargin: 100
visible: false
opacity: 1
CustomLoader{
id: pageLoader_ID2
source: "inputPage.qml"
}
}
InputPage.qml
import QtQuick 2.0
Item {
width: 800
height: 480
property alias text: idText.text
property alias label: idText
property alias rect: idRect
Text{
id: idText
}
Rectangle{
id: idRect
width: 100
height: 200
}
}
In your main add :
//or another scope like click button
Component.onCompleted: {
pageLoader_ID2.createdObject.text = "hello"
pageLoader_ID2.createdObject.rect.color = "red"
}

QML ListModel: are multiple ListModels allowed to live on screen?

GridView {
id: gridv
model: ListModel {
id: modelone;
}
delegate: componentId
}
Rectangle {
id: whattheproblem
color: red
ListView {
id: listv
model: ListModel {
id: modeltwo;
}
delegate: anotherComponentId
}
}
I can do gridv.model.append(element), it adds elements to displayed GridView.
But, I can't do listv.model.append(element), it doesn't draw anything (the component code is valid, though), but at the same time, modeltwo.count shows that element is added to model. Rectangle was added to check the layout (it's managed by RowLayout currently), and it seems to be working; other layout things (think anchor, x/y/z) do not help.
QT 5.3, QtQuick 2..
From my point of view, I can only think now, that modelone associates all the ListModel logics to GridView it's created from, so ListModel can't work with ListView anymore. Sounds illogical, but already spent two hours on this.
is there a necessity to create custom Model's, when dealing with multiple views?
I think the problem is with the delegate, since i tried your example with some modifications and it worked.
Following is the code:
Item {
width: 200
height: 200
GridView {
id: gridv
width: 200
height: 100
model: ListModel {
id: modelone;
}
delegate: Text { text: name }
}
Rectangle {
id: whattheproblem
anchors.top: gridv.bottom
ListView {
id: listv
model: ListModel {
id: modeltwo;
}
delegate: Text { text: name }
}
}
Button {
anchors.left: parent.left
anchors.bottom: parent.bottom
text: "Add to Grid"
onClicked: gridv.model.append({name: "grid"})
}
Button {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: "Add to List"
onClicked: listv.model.append({name: "list"})
}
}
I tried it with Qt 5.3.1

Resources