I want to make an area, where a small rectangles appear in the places it where clicked. Later on I would try to add an ability to move those rectangles by dragging.
After studying Help, I tried to accomplish this with a MouseArea and a Component containing a Rectangle. Then, with onClicked, I was trying to create a new copy of a Component, but I failed whatever I tried (createComponent, createObject, etc.).
What is the correct way of creating a copy of an object in this case?
Am I using right tools for this goal?
MouseArea {
Component {
id: rect
Rectangle {
width: 10
height: 10
}
}
onClicked: < what? >
}
you can create a QML object from a string of QML using the Qt.createQmlObject() and set it's x and y values to mouseX and mouseY :
import QtQuick 2.3
import QtQuick.Window 2.0
Window {
id : root
visible: true
width: 1000
height: 500
MouseArea {
anchors.fill: parent
onClicked:{
var newObject = Qt.createQmlObject('import QtQuick 2.3; Rectangle {color: "red"; width: 10; height: 10}',
root);
newObject.x = mouseX;
newObject.y = mouseY;
}
}
}
Also if you put the code for your rectangle in a separate qml file say myRect.qml, you can create the object from the qml file by :
onClicked:{
var component = Qt.createComponent("myRect.qml");
var newObject = component.createObject(root, {"x": mouseX, "y": mouseY});
}
Related
I have encountered some strange behaviour.
An item's mapToGlobal() or mapFromGlobal() method, does not seem to be consistent in regard to what the coordinates is relative to.
For some items the position is relative to the application window.
For other item the position is relative to my screen. If I move the application window on the screen they will be different.
Below is a simplification example of my code (I actually have multiple components and signals to decide which component that should be loaded).
MyType.qml:
Item{
Button{
onClicked: {
loader.sourceComponent = component; // Different positions
}
}
Loader{
id: loader
anchors.fill: parent
}
Component{
id: component
Rectangle{
Component.onCompleted: {
var point = mapFromGlobal(0, 0);
console.log("temp 2: ", point.x, point.y);
}
}
}
Component.onCompleted: {
//loader.sourceComponent = component; // Same positions
var point = mapFromGlobal(0, 0);
console.log("temp 1: ", point.x, point.y);
}
}
main.qml
ApplicationWindow {
id: appWindow
visible: true
width: 600
height: 400
MyType{
anchors.fill: parent
}
}
The resulting output is
temp 1: 0 0
temp 2: -199 -85
Does anyone know why the positions are sometimes relative to screen and sometimes the application window? Or knows of another explanation for this strange behaviour?
Edit:
If I load the component directly in Component.onCompleted (outcommented in sample code), then both outputs are 0 0. Unfortunately this didn't get me closer to an explanation, except it has something to do with the Loader elements.
I have 2 QQuickItems like below which I can fetch on C++ side using the QMLEngine like this.
QQuickItem * quick_item_1 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem1");
QQuickItem * quick_item_2 = m_qml_engine->rootObjects()[0]->findChild<QQuickItem*>("quickitem2");
Note: quick_item_1's immediate parent is different & quick_item_2's immediate parent is also different. But they both are drawn on the same application window into different immediate parents.
I am drawing both of them offscreen on a different QQuickItem. Let's call it new_parent_surface. I draw both these items on new_parent_surface by changing their parent to new_parent_surface like this.
quick_item_1->setParentItem(new_parent_surface);
quick_item_2->setParentItem(new_parent_surface);
This works fine for the objective of drawing them on a new parent QQuickItem. I get the both quick_item_1 & quick_item_2 drawn on new_parent_surface. Even though new_parent_surface is not drawn on UI, but if I take a snapshot using grabToImage of new_parent_surface, I can see the 2 items drawn on them. Fine till here.
However the positioning of quick_item_1 & quick_item_2 is not correct. I want to position them similar to the way they were positioned their original parent item. I can do some percentage math & try positioning them the same way as they were drawn on their original parent but isn't there a QQQuickItem or Qt API to translate this positioning to a new parent?
I tried to look into QQuickItem's mapping APIs like mapToItem & trying them out like this.
quick_item_2->mapToItem(new_parent_surface, quick_item_2->position());
But the positioning is still not correct.
So, how can I map a QQuickItem's position into its new parent QQuickItem after doing a setParentItem?
Items position is always relative to its parent. And position of the parent is relative to its parent and and so on. But you always can get both relative or global position. QML has lots of coordination translation function. Here is small example that could explain the issue:
import QtQuick 2.7
import QtQuick.Window 2.0
import QtQuick.Controls 2.1
Window {
id:container
width: 800
height: 800
visible: true
Component {
id: rect
Rectangle {
property bool imParent: false
x: 50 + Math.round(Math.random() * 550)
y: 50 + Math.round(Math.random() * 550)
width: 100 + Math.round(Math.random() * 100)
height: 100 + Math.round(Math.random() * 100)
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
Drag.active: dragArea.drag.active
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
Text {
anchors.centerIn: parent
text: imParent ? "I'm parent" : "Drag me"
color: "white"
}
}
}
Rectangle {
id: blk
x: 10
y: 10
z: 100
parent: null
height: 50
width: 50
radius: 5
border.color: "white"
color: "black"
}
Repeater {
id: reptr
model: 5
property int pos: 0
Loader {
id: loader
sourceComponent: rect
onLoaded: {
if(blk.parent == null) {
blk.parent = loader.item;
loader.item.imParent = true;
}
}
}
}
Row {
anchors.horizontalCenter: container.contentItem.horizontalCenter
spacing: 2
Button {
text: "Reparent relative to parent"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
}
}
Button {
text: "Reparent relative to scene"
onClicked: {
reptr.pos ++;
if(reptr.pos >= reptr.model) {
reptr.pos = 0;
}
var item = reptr.itemAt(reptr.pos).item;
var coord = blk.mapToGlobal(blk.x, blk.y);
blk.parent.imParent = false;
blk.parent = item;
blk.parent.imParent = true;
coord = blk.mapFromGlobal(coord.x, coord.y);
blk.x = coord.x;
blk.y = coord.y;
}
}
}
}
I tried to make a function that will change button colors clockwise.
When I assign property var acolor: buttons.itemAt(0).color, the variable acolor changes value as buttons.itemAt(0).color changes. Why does this happen?
This is main.qml file:
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.4
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Window {
id: root
visible: true
width: 600
height: 400
title: qsTr("My Window")
property int numberOfButtons: 9
GridLayout{
id: grid
columns: 3
anchors.fill: parent
Repeater {
id: buttons
model: root.numberOfButtons;
delegate: Mybutton {id: butt; color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1); number: index}
}
}
}
This is Mybutton.qml:
Rectangle {
id: rectangle
width: 50; height: 100
color: "red"
property int number: 0
MouseArea {
id: mouse
anchors.fill: parent
property var acolor: buttons.itemAt(0).color
onClicked: {
function rotate()
{
acolor = buttons.itemAt(0).color
console.log(acolor)//heare is #11754c
buttons.itemAt(0).color = buttons.itemAt(3).color
console.log(acolor)// but heare is #6c2c3d
buttons.itemAt(3).color = buttons.itemAt(6).color
buttons.itemAt(6).color = buttons.itemAt(7).color
buttons.itemAt(7).color = buttons.itemAt(8).color
buttons.itemAt(8).color = buttons.itemAt(5).color
buttons.itemAt(5).color = buttons.itemAt(2).color
buttons.itemAt(2).color = buttons.itemAt(1).color
buttons.itemAt(1).color = acolor
}
rotate()
}
}
}
First of all, your design is wrong. It is conceptually backwards to put the rotation functionality in the button when it acts on the grid layout. The rotate() function should be a member of the grid layout, and the you simply invoke it onClicked.
Second, according to typeof(buttons.itemAt(0).color) the type of color is object. Which means that acolor is a reference to that particular object, which explains why that reference points to another value once you change the color of the item at index 0.
One way to trick it to implicitly convert the color to a string is to use it like this:
var acolor = ""
acolor += buttons.itemAt(0).color
console.log(acolor)
buttons.itemAt(0).color = buttons.itemAt(3).color
console.log(acolor) // the same!!!
And now it will be preserved, because acolor is now a string rather than an object reference.
Also, you don't really need the property var acolor: buttons.itemAt(0).color part.
You can also explicitly convert the color to a string:
var acolor = buttons.itemAt(0).color.toString()
Also, by moving the rotate function to where it belongs you considerably simplify the mouse area in the button:
MouseArea {
anchors.fill: parent
onClicked: grid.rotate()
}
Update:
Note that if we do it like this:
property color acolor
function rotate() {
acolor = buttons.itemAt(0).color
console.log(acolor)
buttons.itemAt(0).color = buttons.itemAt(3).color
console.log(acolor) // still the same
}
it would work as logically expected. Now acolor is of type object, but you have an object to object assignment, which transfers the value to the other object, whereas with var it behaves like an object reference.
Update: Isn't it often the way: you ask a question and then discover the answer on your own a short time later.
It seems I have some confusion between referencing mouseX and mouseY in a MouseArea and getting the mouse data from the MouseEvent
If I change my code below to:
var pt = Qt.point( mouse.x, mouse.y)
from what I had:
var pt = Qt.point(mouseX, mouseY)
Then the newly created sprites are located at the click point. That's great but I am still not understanding the difference, particularly in this case since the MouseArea fills the entire window (parent).
The mouse data is the same in either approach, unless the mouse event has been propagated – then the mouse approach gives the correct data while mouseX, mouseY does not. That is the part that is confusing me.
Can anyone explain the difference in what is going on here?
I have made a reusable QML component which can load .png images with an alpha channel and handle clicks on transparent pixels by propagating the mouse event. I've got it working in the code below (any suggestions for improvement much welcomed) but it seems the mouse data is wrong.
In the image below I have marked the order and location of 3 clicks. In the log statements even though the mouse click position has changed, the reported position stays the same. So what is occurring is that although 3 sprites have been created, they are stacking on top of each other
Am I missing something about how a MouseArea reports the mouse position or how propagateComposedEvents works?
main clicked. Creating sprite at: 598.01953125 492.953125
graphic alpha clicked: 5.69921875 103.41015625 <----- EVENT PASSED THROUGH
main clicked. Creating sprite at: 598.01953125 492.953125
graphic alpha clicked: 121.953125 103.01953125 <----- EVENT PASSED THROUGH
graphic alpha clicked: 5.69921875 103.41015625 <----- EVENT PASSED THROUGH
main clicked. Creating sprite at: 598.01953125 492.953125
// main.qml
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
ApplicationWindow {
id: appWindow
visible: true
width: 1024
height: 768
title: qsTr("QtQuick")
Item {
id: container
anchors.fill: parent
property var events: new Array()
property int counter: 0;
MouseArea {
anchors.fill: parent
onClicked: {
console.log("---------main clicked. Creating sprite at:", mouseX, mouseY);
var pt = Qt.point(mouseX, mouseY)
var component = Qt.createComponent("graphicAsset.qml");
var imageName = "Earth-icon.png";
var sprite = component.createObject(container, {"x": pt.x, "y": pt.y, "imageSource": imageName});
}
}
}
}
//graphicAsset.qml
import QtQuick 2.5
Canvas {
id: graphicAsset
width: 50
height: 50
property string imageSource;
onImageSourceChanged:loadImage(imageSource)
onImageLoaded: {
var ctx = getContext("2d")
var imageData = ctx.createImageData(imageSource)
graphicAsset.width = imageData.width;
graphicAsset.height = imageData.height;
x = x - (imageData.width /2);
y = y - (imageData.height /2);
requestPaint();
}
onPaint: {
if (isImageLoaded(imageSource)){
var ctx = getContext("2d")
var imageData = ctx.createImageData(imageSource)
ctx.drawImage(imageData, 0, 0)
}
}
MouseArea {
anchors.fill: parent
drag.target: parent
propagateComposedEvents: true
onClicked: {
var ctx = parent.getContext("2d")
var imageData = ctx.getImageData(mouseX, mouseY, 1, 1)
if (imageData.data[3] == 0 ){
console.log("graphic alpha clicked:", mouseX, mouseY);
mouse.accepted = false;
} else {
mouse.accepted = true;
}
}
}
}
Mouse events' x and y positions are relative to the MouseArea that generated the event, as well as the coordinates of the mouse cursor within the same area (named mouseX and mouseY).
My problem is the following: I have a custom QML object based on a Rectangle, and I need to execute some javascript function with the width & height of this object when it's created.
To do that, I use Component.onCompleted to execute the javascript but in this function the width and height properties are wrong (like 0;0 or 0;-30), as if they were not yet created.
Code:
import QtQuick 2.3
import "js/kloggr.js" as Game
Rectangle {
property var kloggr: undefined
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
}
}
This object is created like this:
Kloggr {
id: kloggr
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: pauseBtn.top
}
(I've removed the irrelevant parts of the code)
So what did I do wrong, or what could I do to get the same result?
You have forget to set the width and height of your Rectangle ...
import QtQuick 2.3
import "js/kloggr.js" as Game
Rectangle {
width: 300
height: 300
property var kloggr: undefined
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
//now h + w = 300
}
}
If your use anchors, change your Rectangle by Item, for exemple
import QtQuick 2.3
import "js/kloggr.js" as Game
Item{
//without W and Height if you don't use the designer, if you use the designer you should set a default size
property var kloggr: undefined
Rectangle{
anchors.fill : parent
MouseArea {
anchors.fill: parent
// outputs the right values (about 400;600)
onClicked: console.log(width+";"+height);
}
Component.onCompleted: {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
console.log(width+";"+height); // outputs 0;-30
}
}//end of rectangle
} //end of item
I did not find out it if was a bug or something but I found a workaround: instead of using Component.onCompleted I used onVisibleChanged:
onVisibleChanged: {
if (visible && kloggr === undefined) {
Game.kloggr = this;
kloggr = new Game.Kloggr(width, height);
}
}
It seems that the width and height properties are only valid when the object is set to visible...
Thank you #yekmen for your help