Tableview row should change on pressed in QML - qt

My delegate is image in tableview, How to change image of row which is selected
onPressed and onReleased it should go back in it's original state.
itemDelegate: Image
{
id:item_id
height: (tableView.height/(listmodel.count < 4 ? listmodel.count : 4))
source:
{
var activeRow = tableView.currentRow === styleData.row
(activeRow ? Image 1 : styleData.row % 2 ? (image 2): (image 3))
}
MouseArea
{
id:table_mouse_id
anchors.fill: parent
onPressed:
{
source = image 4
}
onReleased:
{
tableView.currentRow = styleData.row
}
}

You can use the pressed property of the MouseArea:
source: {
var activeRow = tableView.currentRow === styleData.row;
(activeRow ? table_mouse_id.pressed ? image4 //pressed
: Image1 //active
: styleData.row % 2 ? (image2) //odd
: (image3)) //even
}
Important note: you should remove the onPressed handler, as this will override the binding (which is probably also the reason it's not working in your current setup)

Related

In QML, how do I calculate the step size for a scroll bar such that the increase()/decrease() will only scroll to the next out-of-view item?

Pretty much included in the title. I have a ListView of some graphic items (100 - 10000+ items depending on the list model) that have specific heights/widths.
As it is right now, pgUp and pgDn will skip too many items in the list. How can I calculate the stepSize so that it will scroll to the next unseen item?
So far I've tried setting the index manually by calculating the number of entries in the view, which works somewhat, but it's not exactly the functionality I want.
I'm expecting it to be resizable, but always be able to pgUp/Dn to the next unseen entry.
You can use contentY to implement pgDn and pgUp by incrementing or decrementing in pixel units. The stepSize is simply, the visible height of the ListView:
function pgDn() {
listView.contentY += listView.height;
}
function pgUp() {
listView.contentY -= listView.height;
}
If you add bounds checking and updating the highlighted item, the above functions change to:
function pgDn() {
listView.contentY = Math.min(listView.contentY + listView.height, listView.contentHeight - listView.height);
listView.currentIndex = listView.indexAt(0, listView.contentY);
}
function pgUp() {
listView.contentY = Math.max(listView.contentY - listView.height, 0);
listView.currentIndex = listView.indexAt(0, listView.contentY);
}
Here's a sample that creates 50 items of random height. PgUp and PgDn buttons are at the bottom that help navigates the ListView and change the selected item. The delegates are clickable which allows for manual selection as well as setting input focus so keyboard presses will work.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
ListView {
id: listView
anchors.fill: parent
model: listModel
delegate: ItemDelegate {
width: ListView.view.width - 20
height: h
text: txt
background: Rectangle {
color: index & 1 ? "#eee" : "#ddd"
border.color: "grey"
}
onClicked: {
listView.currentIndex = index;
listView.forceActiveFocus();
}
}
ScrollBar.vertical: ScrollBar {
width: 20
policy: ScrollBar.AlwaysOn
}
clip: true
highlight: Rectangle {
border.color: "red"
color: "transparent"
width: 2
z: 2
}
Keys.onPressed: (event) => {
if (event.key == Qt.Key_PageUp) {
pgUp();
event.accepted = true;
}
if (event.key == Qt.Key_PageDown) {
pgDn();
event.accepted = true;
}
}
}
ListModel {
id: listModel
}
footer: Frame {
RowLayout {
width: parent.width
Button {
text: "PgUp"
onClicked: pgUp()
}
Button {
text: "PgDn"
onClicked: pgDn()
}
}
}
function populate() {
for (let i = 0; i <= 50; i++) {
let txt = `Item ${i}`;
let h = Math.floor(Math.random() * 100 + 40);
listModel.append( {txt, h} );
}
}
function pgDn() {
listView.contentY = Math.min(listView.contentY + listView.height, listView.contentHeight - listView.height);
listView.currentIndex = listView.indexAt(0, listView.contentY);
}
function pgUp() {
listView.contentY = Math.max(listView.contentY - listView.height, 0);
listView.currentIndex = listView.indexAt(0, listView.contentY);
}
Component.onCompleted: Qt.callLater(populate)
}
You can Try It Online!

Drag and drop multiple items in Qml

I need to draw bunch of Rectangles in my application. The user should be able to select each of them individually and move them freely and change their position. The user also should be able to select multiple Rectangles and move all the selected one simultaneously and release them somewhere else.
I could already implement something based on Gridview that can handle selection and movement of one Rectangle, but I cannot make it work for multiple selection/movement. Here is a snippet of code I have currently:
GridView {
id: mainGrid
cellWidth: 7;
cellHeight: 7;
ListModel {
id: myModel
function createModel() {
for(var i = 0; i < totalZoneList.length; i++)
{
for (var j = 0; j < moduleZoneList.length; j++)
{
myModel.append({"item1": ITEM1, "item2": ITEM2})
}
}
}
Component.onCompleted: {createModel()}
}
Component {
id: myblocks
Item {
id: item
width: mainGrid.cellWidth;
height: mainGrid.cellHeight;
Rectangle {
id: box
parent: mainGrid
x: //CALCULATED BASED ON MODEL
y: //CALCULATED BASED ON MODEL
width: //CALCULATED BASED ON MODEL
height: //CALCULATED BASED ON MODEL
MouseArea {
id: gridArea
anchors.fill: parent
hoverEnabled: true
drag.axis: Drag.XandYAxis
drag.minimumX: 0
drag.minimumY: 0
property int mX: (mouseX < 0) ? 0 : ((mouseX < mainGrid.width - mainGrid.cellWidth) ? mouseX : mainGrid.width - mainGrid.cellWidth)
property int mY: (mouseY < 0) ? 0 : ((mouseY < mainGrid.height - mainGrid.cellHeight) ? mouseY : mainGrid.height - mainGrid.cellHeight)
property int index: parseInt(mX/mainGrid.cellWidth) + 5*parseInt(mY/mainGrid.cellHeight) //item underneath cursor
property int activeIndex
property var xWhenPressed
property var yWhenPressed
propagateComposedEvents: true
onPressed: {
activeIndex = index
drag.target = box
xWhenPressed = box.x
yWhenPressed = box.y
gridArea.drag.maximumX = mainGrid.width - box.width
gridArea.drag.maximumY = mainGrid.height - box.height
}
onReleased: {
if(xWhenPressed !== box.x || yWhenPressed !== box.y)
{
//RECALCULATE THE POSITION
}
}
onPositionChanged: {
if (drag.active && index !== -1 && index !== activeIndex) {
mainGrid.model.move(activeIndex, activeIndex = index, 1)
}
}
} // Mousearea
} // Rectangle
} // Item
} // Component
} //mainGrid
I didn't manage to have your code working. First, I see mistakes on it:
Rectangle {
id: box
parent: mainGrid
...
}
you just need to remove the parent Item which is of no use, and set the Rectangle as root of the delegate.
Then, you forgot to mention that the target of drag is the Rectangle
drag.target: parent
Here is the code after correcting:
Component {
id: myblocks
Rectangle {
id: box
color: "red"
width: 20
height: 20
MouseArea {
id: gridArea
anchors.fill: parent
drag.target: parent
hoverEnabled: true
drag.axis: Drag.XandYAxis
} // Mousearea
} // Rectangle
} // Component
Then, you shouldn't use a GridView because you want elements to be moved. If you use a Repeater it works, and you just have to set x and y in the Rectangle to place the elements at the beginning.
Now this is a solution for your problem: you click on an element to select it, and you can move all selected items at once.
Window {
visible: true
width: 640
height: 480
property var totalZoneList: ["total1", "total2"]
property var moduleZoneList: ["module1", "module2"]
Repeater{
id: iRepeater
model: ListModel {
id: myModel
function createModel() {
for(var i = 0; i < totalZoneList.length; i++)
{
for (var j = 0; j < moduleZoneList.length; j++)
{
myModel.append({"item1": totalZoneList[i], "item2": moduleZoneList[j]})
}
}
}
Component.onCompleted: {createModel()}
}
delegate: myblocks
}
Component {
id: myblocks
Rectangle {
id: box
color: {
switch(index){
case 0: selected ? "red" : "#FF9999";break;
case 1: selected ? "blue" : "lightblue";break;
case 2: selected ? "green" : "lightgreen";break;
case 3: selected ? "grey" : "lightgrey";break;
}
}
x: (width + 5)*index
width: 20
height: 20
property int offsetX:0
property int offsetY:0
property bool selected: false
function setRelative(pressedRect){
disableDrag();
x = Qt.binding(function (){ return pressedRect.x + offsetX; })
y = Qt.binding(function (){ return pressedRect.y + offsetY; })
}
function enableDrag(){
gridArea.drag.target = box
}
function disableDrag(){
gridArea.drag.target = null
}
MouseArea {
id: gridArea
anchors.fill: parent
hoverEnabled: true
drag.axis: Drag.XandYAxis
onClicked: parent.selected=!parent.selected
onPressed: {
var pressedRect = iRepeater.itemAt(index);
if (pressedRect.selected == true){
for (var i=0; i<iRepeater.count; i++ ){
var rect = iRepeater.itemAt(i);
if (i != index){
//init for breaking existing binding
rect.x = rect.x
rect.y = rect.y
rect.disableDrag()
if (rect.selected == true){
rect.offsetX = rect.x - pressedRect.x
rect.offsetY = rect.y - pressedRect.y
rect.setRelative(pressedRect)
}
}
}
pressedRect.enableDrag()
}
}
} // Mousearea
} // Rectangle
} // Component
}
Recipe One:
Use a Repeater, so the positioning is not determined by a view but by yourself.
Use an invisible helper Item. This is your drag.target.
Implement your prefered way of selecting your objects - weather by clicking, or drawing boxes and performing a check which objects are included in this box. Make the positions of all selected objects relative to your invisible helper object.
Drag the helper object, and move all other objects accordingly.
When finished, unselect the objects again and make their positions absolute (within the parents coordinate system)
Recipe Two:
Reparent all selected Objects to a new Item, while mapping their coordinates accordingly
Move the new Item
Reparen all objects back to the original canvas, mapping their coordinates back.
I hope this is sufficient to solve your problem (as far as I understood it)
If it solves another problem, than you need to be more specific about the expected behavior of your draged objects.

How to get id of child in qml

When I tried to get children ID's slightly modifying this http://qmlbook.github.io/en/ch01/index.html example
// animate.qml
import QtQuick 2.0
Item
{
id: root
width: background.width
height: background.height
property string information: "this is root"
property int rotationStep: 45
Image {
id: background
source: "images/background.png"
}
Image {
id: pole
property string information: "this is pole"
x: (root.width - width)/2+2
y: root.height - height
source: "images/pole.png"
}
Image {
id: pinwheel
anchors.centerIn: parent
source: "images/pinwheel.png"
property string information: "this is pinweel"
// visible: false
Behavior on rotation {
NumberAnimation { duration: 125 }
}
}
Image {
id: blur
opacity: 0
anchors.centerIn: parent
source: "images/blur.png"
property string information: "this is blur"
// visible: false
Behavior on rotation {
NumberAnimation { duration: 125 }
}
Behavior on opacity {
NumberAnimation { duration: 125 }
}
}
// M1>>
focus: true
Keys.onLeftPressed: {
blur.opacity = 0.8
pinwheel.rotation -= root.rotationStep
blur.rotation -= root.rotationStep
}
Keys.onRightPressed: {
blur.opacity = 0.5
pinwheel.rotation += root.rotationStep
blur.rotation += root.rotationStep
}
Keys.onReleased: {
blur.opacity = 0
}
Keys.onSpacePressed:
{
for (var i=0; i<root.children.length; ++i)
console.info(root.children[i].information)
}
Keys.onDeletePressed:
{
for (var i=0; i<root.children.length; ++i)
console.info(root.children[i].id)
}
// <<M1
}
Unfortunately pressing Delete key gives me an error:
qml: undefined
qml: undefined
qml: undefined
qml: undefined
as opposed to pressing spacebar:
qml: undefined
qml: this is pole
qml: this is pinweel
qml: this is blur
Why this script returns undefined id's ?
I need to traverse some objects and be able to tell what is what - so I need to know how to traverse root tree to get id's of its childs and their object types.
Unfortunately I was unable to print the most trival id's and had to add some simple property to get it done but this means a lot of work in real life project since every object needs information property :(
So to reiterate:
Why the id's in this example are undefined?
How to traverse object tree using qml and print its id's and types ?
Id is not an ordinary object property, so it is undefined when you try to assess it through js. And qml doesn't provide operators like typeof. So you need to add type or objectname property manually. I would consider subclassing Image and adding type. Ref: How to do a "is_a", "typeof" or instanceof in QML?
Item
{
id: root
Image {
id: background
type: "image"
}
Image {
id: pole
type: "image"
}
function iter(){
for(var i = 0; i < root.children.length; ++i)
if(root.children[i].type==="image"){
//balabala
}
}
}
}

Clear QML anchor

I have a MouseArea that I want to start off centered and then have an absolute position once the up/down/left/right keys are pressed. My problem is that I don't know how to clear the anchor on the MouseArea so that I can specify an absolute position:
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: screen
width: 360
height: 360
visible: true
Rectangle {
anchors.fill: parent
states: [
State {
name: "moved"
AnchorChanges {
target: mouseArea
anchors.bottom: undefined
anchors.left: undefined
anchors.right: undefined
anchors.top: undefined
}
}
]
MouseArea {
id: mouseArea
anchors.centerIn: parent
width: 250
height: 250
focus: true
onClicked: console.log("clicked!")
onPositionChanged: console.log("position changed!")
function moveMouseArea(x, y) {
mouseArea.x += x;
mouseArea.y += y;
mouseArea.state = "moved";
mouseAreaPosText.text = 'Mouse area was moved... new pos: '
+ mouseArea.x + ', ' + mouseArea.y;
}
Keys.onPressed: {
if (event.key === Qt.Key_Up)
moveMouseArea(0, -1);
if (event.key === Qt.Key_Down)
moveMouseArea(0, 1);
if (event.key === Qt.Key_Left)
moveMouseArea(-1, 0);
if (event.key === Qt.Key_Right)
moveMouseArea(1, 0);
}
Rectangle {
anchors.fill: parent
border.width: 2
border.color: "black"
color: "transparent"
}
Text {
id: mouseAreaPosText
anchors.centerIn: parent
}
}
}
}
At first I just tried setting mouseArea.anchors to undefined but got an error about anchors being a read-only property. I then discovered AnchorChanges, but I can't find a way to remove/clear the anchor; setting anchors.bottom etc. to undefined doesn't work.
According to docs, setting an anchor attribute to undefined should work. I don't quite get why AnchorChanges did not allow to set anchors.centerIn, but you can workaround it in your moveMouseArea function:
function moveMouseArea(x, y) {
mouseArea.anchors.centerIn = undefined; // <-- reset anchor before state change
mouseArea.pos.x += x;
mouseArea.pos.y += y;
mouseArea.state = "moved";
mouseAreaPosText.text = 'Mouse area was moved... new pos: '
+ mouseArea.pos.x + ', ' + mouseArea.pos.y;
}
Thanks for your help guys. I have found that setting undefined within a state works (if by works you mean only that it doesn't give errors), however once the element moves to yet another state, the anchors magically (and very frustratingly) return. This happens EVEN if you set all anchors undefined in the final state. As mentioned above however, setting undefined in the function before changing state works great. In my case, I set it in my mouseArea in onPressed.
onPressed: {
plotWindow04Frame.anchors.bottom = undefined
plotWindow04Frame.anchors.left = undefined
plotWindow04Frame.state = "inDrag"
}
I found it was not necessary to mention the anchor in the onReleased, just the next state.
onReleased: {
plotWindow04Frame.state = "dropped"
}
Also, I should mention, that the final "dropped" state does not mention anchors either, just opacity.
states: [
State {
name: "inDrag"
PropertyChanges {
target: plotWindow04Frame
opacity: .5
}
},
State {
name: "dropped"
PropertyChanges {
target: plotWindow04Frame
opacity: 1
}
}
]
transitions: Transition {
NumberAnimation { properties: "opacity"; duration:200 }
}
}
(The idea here was that these plot windows would become translucent (opacity: 0.5) while dragging, but return to opaque (opacity: 1) when the user drops them)
What is nice is that the plotwindow "rectangles" are initially anchored to the bottom of the GUI, but once the user picks them up, they can put them where ever they like.

how to imitate the button in QML

I want to imitate the Button in QML, but why does it only work with containsMouse and !containsMouse, it doesn't work with OnPressed. can you help me ? Thank you.
Item {
id: area
property string str_noPressed;
property string str_hover;
property string str_pressed;
Image {
id: background
width: 75; height: 75
source: userArea.containsMouse ? str_hover : str_noPressed
MouseArea {
id: userArea
anchors.fill: parent
onClicked: {}
onPressed: background.source = str_pressed
hoverEnabled: true
}
}
}
Obviously, in the button is pressed, it also contains the mouse.
I assume containsMouse takes precedence over onPressed. Try:
onContainsMouseChanged: {
if (containsMouse && !pressed) {
background.source = str_pressed
}
}

Resources