Inside the delegate, I bind Image's source property to ListView's currentIndex which determines which image to load. This works great:
ListView {
id: answerListView
model: 5
currentIndex: -1
delegate: answerDelegate
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
source: answerListView.currentIndex === index
? "selected.png" : "not_selected.png"
}
MouseArea {
anchors.fill: parent
onClicked: {
answerListView.currentIndex = index
}
}
Component.onCompleted: {
answerListView.currentIndex = 1; // doesn't work!!
}
}
}
Since currentIndex: -1, it will always show not_selected.png. To show selected.png, I change currentIndex in Component.onLoaded inside delegate.
I was expecting image to load selected.png since currentIndex was updated.
What is the correct way and what am I misunderstanding here?
Ok, new guess:
You want to have the posibility to select multiple Items. As currentIndex only stores one value, which is the value you assigned it last, you can use it to mark only one Item.
Therefore you need to find another way to store your selection. You might for example have a property in the delegate: property bool selected: false which you set to true upon selection.
The problem with this solution is, that it only works if all Items are instantiated at all times. As soon as one Item will be destroyed again, the information will be lost, and uppon the next creation, the selection/unselection is undone.
The better way would be to introduce a role in your model, that stores the selection outside of the non-persistant delegates for you:
ListView {
id: answerListView
model: lm
delegate: answerDelegate
width: 100
height: 220
}
ListModel {
id: lm
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
anchors.fill: parent
source: model.selected ? "selected.png" : "notselected.png"
}
Text {
text: (model.selected ? 'selected ' : 'notselected ')
}
Component.onCompleted: {
model.selected = true // doesn't work!!
}
MouseArea {
anchors.fill: parent
onClicked: {
model.selected = !model.selected
}
}
}
}
Another option would probably be a ItemSelectionModel, but I don't know atm, how it works.
Otherwise your example works as expected:
The Item with index 1 is shown, and displays the Image selected.png. All other Items are not shown (for the ListView is to small) but if the would be shown, they would show notselected.png for the answerListView.currentIndex is not equal to their index.
Related
I got a listview with a c++ listmodel and a rectangle with a mousarea as delegate.
Internally in my code, my listmodel gets data appended, and I do
beginResetModel();
list.append(element);
endResetModel();
My model updates and the element appears just fine, but I am not able to set the currentIndex to the newly created item.
I am trying to use Component.onCompleted inside the delegate like this
Component.onCompleted: {
ListView.currentIndex = index;
console.log("Show List: ", ListView.currentIndex, index);
}
But the index in the log output says the currentIndex doesn't change after I set it to my models index.
I do the same inside my mousearea, in onClicked, and there it works fine.
How can I make my newly created listview element the currentItem/currentIndex?
I am using this currentIndex to change the color of the created element (this is inside my rectangle delegate)
color: ListView.currentIndex == index ? "lightred" : "darkred"
Are there better ways of going about what I am trying to do possibly?
Full code example below:
ListView {
id: listView
focus: true
clip: true
model: listModel
delegate: Rectangle {
id: listDelegate
color: listView.currentIndex == index ? "green" : "red"
height: 20
width: 100
radius: 2
Component.onCompleted: {
listView.currentIndex = index
console.log("Show List: ", listView.currentIndex, index)
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.currentIndex = index
console.log("onClicked: ", listView.currentIndex, index)
}
}
}
}
You can just check for when the count changes, like this:
ListView {
...
onCountChanged: {
listView.currentIndex = listView.count - 1;
}
}
I have a TableView defined in my QML which will obviously have multiple rows populated by a ListModel.
I want to fetch the ListElement associated with the row which is double clicked.
I have my rowDelegate of the table view defined as such:
rowDelegate: Rectangle {
color: "#D3D3D3"
height: 30
MouseArea {
anchors.fill: parent
onDoubleClicked: {
console.log("table view row clicked...")
// How to fetch the ListElement associated with the row
// and return it for use by another module?
}
}
}
My comment pretty much emphasises what I'm looking for.
You probably don't even need a MouseArea to handle the click in your delegate.
The TableView already has a doubleClicked signal that you can use to retrieve the model data from the clicked row index:
TableView {
model: ListModel {
ListElement {
name: "name 1"
}
ListElement {
name: "name 2"
}
}
TableViewColumn {
title: "name"
delegate: Text {
text: model.name
}
}
rowDelegate: Rectangle {
color: "#D3D3D3"
height: 30
// no MouseArea
}
// handle the click directly in TableView
onDoubleClicked: {
const element = model.get(row)
console.error("doubleClicked on", element.name)
}
}
In scope of your delegate you can use model pseudo-property to fetch associated ListElement (or any other piece of data which is displayed via that delegate). You may think of it as a reference to original data item. It has all properties of ListElement (for example text or color or whatever) and also index property (index of item in your ListModel or any other model).
I'm trying to create a QML keyboard with the following code on Qt4.8.
Item {
id: keyboard
property string keys: "azertyuiopqsdfghjklmwxcvbn****^<"
Rectangle {
height: parent.height
width: parent.width
Grid {
id: keyboardGrid
rows: 4
columns: 10
spacing: 1
Repeater {
model: keys.length
KeyboardButton {
visible: { (keys.charAt(index) == "*") ? false : true; }
btnKeyText: keys.charAt(index);
}
}
}
}
}
I've put some '*' in keys in order to make some invisible button to go to the next line of the grid, but when I set a KeyboardButton to visible = false, QML interpreter just ignore it.
See the screenshots for more detail, first one is with this code, second one is when I comment the line where i set visible to false.
Why invisible components are just ingored ? Any tricks ?
As BaCaRoZzo says, element with opacity: 0 or visibility: 0 the element is not rendered (in Qt4.8, in 5 and superior opacity : 0 does not affect rendering), so I have found another way to do what I wanted.
I achieve this by creating my own grid with a Repeater and Rows as follow :
Item {
id: keyboard
property variant keys: ["azertyuiop", "qsdfghjklm", "wxcvbn,;:!", "⇧* ↵←"]
Repeater{
id: lineRpt
model: 4
anchors.fill: parent
Row {
spacing: 1
anchors.verticalCenter: parent.top
anchors.verticalCenterOffset: 25+(index*52)
anchors.left: parent.left
property string currentLine: keys[index]
Repeater {
model: keys.length
KeyboardButton {
visible: { (keys.charAt(index) == "*") ? false : true; }
btnKeyText: keys.charAt(index);
}
}
}
}
}
Edit after comments:
You can also set the background color to transparent, and in my case, I need to remove de "*" of the text too.
I have QtQuick 1.0
I use the following code:
Rectangle {
Component {
id: appDelegate
MouseArea{
id:myMouseArea
hoverEnabled: true
onClicked:{
onClicked: load.source = page;
}
}
Loader {
id: load
}
}
GridView {
id: view
// I am unable to access myMouseArea here.
highlight: myMouseArea.containsMouse ? appHighlight : !appHighlight
delegate: appDelegate
}
}
It gives me the following error:
ReferenceError: Can't find variable: myMouseArea
/usr/lib/i386-linux-gnu/qt4/bin/qmlviewer exited with code 0
I don't know if the details I provided are sufficient, please let me know if theres anything else I am missing.
I am using this code as an example:
http://docs.knobbits.org/qt4/declarative-modelviews-gridview-qml-gridview-example-gridview-example-qml.html
You cannot access myMouseArea because it's created inside delegate context. You cannot access delegate other then currentItem. But you can freely access view inside the context of delegate, to set currentIndex to attached property index.
This is a corrected code:
Rectangle {
width: 360
height: 360
Component { // It must be a component, if we want use it as delegate
id: appDelegate
// its not possible to have more than one element inside component
Rectangle
{
// need to set size of item, anchors wont work here
// could use view.cellWidth and view.cellHeight to keep it DRY
width: 96
height: 66
color: "green" // color only to see the place of MouseArea
MouseArea {
id:myMouseArea
anchors.fill: parent // this setup the size to whole rectangle
// it this item have the size 0,0 it will simple do not work
hoverEnabled: true
onEntered: {
// we know the mouse is inside this region
// setting this value will show the highlight rectangle
view.currentIndex = index;
}
onClicked:{
onClicked: load.source = page;
}
}
Loader {
// this is not needed but it's wise to not keep zero size
anchors.fill: parent
id: load
}
}
}
GridView {
id: view
// the size of GridView must be set,
// as otherwise no delegate will not show
anchors.fill: parent
anchors.margins: 5
cellWidth: 100
cellHeight: 70
// Rectangle will act as a border.
// Size and position is set by GridView
// to the size and position of currentItem.
// This is no a item, this makes a Component
// as highlight property needs one.
// You can create a Component like appDelegate.
highlight : Rectangle {
border.width: 2
border.color: "blue"
}
// some ListModel to setup the page variable inside delegate context
model: ListModel {
ListElement { page: "test1.qml"; }
ListElement { page: "test2.qml"; }
ListElement { page: "test3.qml"; }
}
delegate: appDelegate
}
}
I encounter a problem which is that the pop-up window cannot get the focus when it is shown. I tried to use the activefocus function in main window, but it doesn't work. It is supposed that if I press the enter key, the pop-window will be closed. How can I get the focus for the pop-up window? Thanks.
...
GridView {
id:grid_main
anchors.fill: parent
focus: true
currentIndex: 0
model: FileModel{
id: myModel
folder: "c:\\folder"
nameFilters: ["*.mp4","*.jpg"]
}
highlight: Rectangle { width: 80; height: 80; color: "lightsteelblue" }
delegate: Item {
width: 100; height: 100
Text {
anchors { top: myIcon.bottom; horizontalCenter: parent.horizontalCenter }
text: fileName
}
MouseArea {
anchors.fill: parent
onClicked: {
parent.GridView.view.currentIndex = index
}
}
}
Keys.onPressed: { //pop up window
if (event.key == 16777220) {//enter
subWindow.show();
subWindow.forceActiveFocus();
event.accepted = true;
grid_main.focus = false;
}
}
}
Window {
id: subWindow
Keys.onPressed: {
if (event.key == 16777220) {//press enter
subWindow.close();
}
}
}
...
Let's start with some basics:
Keys.onPressed: { //pop up window
if (event.key == 16777220) {//enter
subWindow.show()
...
event.accepted = true
}
}
Not to mention how error-prone it is, just for the sake of readability, please don't hard-code enum values like 16777220. Qt provides Qt.Key_Return and Qt.Key_Enter (typically located on the keypad) and more conveniently, Keys.returnPressed and Keys.enterPressed signal handlers. These convenience handlers even automatically set event.accepted = true, so you can replace the signal handler with a lot simpler version:
Keys.onReturnPressed: {
subWindow.show()
...
}
Now, the next thing is to find the correct methods to call. First of all, the QML Window type does not have such method as forceActiveFocus(). If you pay some attention to the application output, you should see:
TypeError: Property 'forceActiveFocus' of object QQuickWindowQmlImpl(0x1a6253d9c50) is not a function
The documentation contains a list of available methods: Window QML type. You might want to try a combination of show() and requestActivate().
Keys.onReturnPressed: {
subWindow.show()
subWindow.requestActivate()
}
Then, you want to handle keys in the sub-window. Currently, you're trying to attach QML Keys to the Window. Again, if you pay attention to the application output, you should see:
Could not attach Keys property to: QQuickWindowQmlImpl(0x1ddb75d7fe0) is not an Item
Maybe it's just the simplified test-case, but you need to get these things right when you give a testcase, to avoid people focusing on wrong errors. Anyway, what you want to do is to create an item, request focus, and handle keys on it:
Window {
id: subWindow
Item {
focus: true
Keys.onReturnPressed: subWindow.close()
}
}
Finally, to put the pieces together, a working minimal testcase would look something like:
import QtQuick 2.9
import QtQuick.Window 2.2
Window {
id: window
width: 300
height: 300
visible: true
GridView {
focus: true
anchors.fill: parent
// ...
Keys.onReturnPressed: {
subWindow.show()
subWindow.requestActivate()
}
}
Window {
id: subWindow
Item {
focus: true
anchors.fill: parent
Keys.onReturnPressed: subWindow.close()
}
}
}
PS. Key events rely on focus being in where you expect it to be. This may not always be true, if the user tab-navigates focus elsewhere, for example. Consider using the Shortcut QML type for a more reliable way to close the popup.