Updating a QML canvas - qt

I am a QML / Javascript noob, and would like some help with that.
I want to insert some points (represented as small black circles) onto a white QML canvas element and then run an algorithm on them (such as finding convex hulls via an external geometric library)
Here is my QML code.
import QtQuick 2.5
import QtQuick.Window 2.2
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
width: 1000
height: 1000
onPaint: {
var context = getContext("2d");
}
MouseArea {
id: mymouse
anchors.fill: parent
property var arrpoints : []
onClicked: {
// Record mouse-position
arrpoints = arrpoints.concat([mouseX, mouseY])
console.log(arrpoints)
}
}
}
}
So far the above code, opens up a window, with a QML canvas on it, and can keep track of the positions on the canvas (via the array arrpoints) where I single-clicked with my mouse, and outputs the array of clicked-points to the console.
But now, everytime the arrpoints changes, how do I 'tell' QML to draw a small black circle at that point immediately?
I would have thought the onPaint part of QML would trigger the rendering of the new state immediately, but it seems that part is only for the initial drawing on the canvas, before the user starts interacting with it.

You have to call the canvas requestPaint() function to force the painting. It is also advisable to save the data of the positions appropriately: {"x": x_value, "y": y_value}
import QtQuick 2.5
import QtQuick.Window 2.2
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: canvas
width: 1000
height: 1000
onPaint: {
var context = getContext("2d")
context.strokeStyle = Qt.rgba(0, 0, 0, 1)
context.lineWidth = 1
for(var i=0; i < mymouse.arrpoints.length; i++){
var point = mymouse.arrpoints[i]
context.ellipse(point["x"]-5, point["y"]-5, 10, 10)
}
context.stroke()
}
MouseArea {
id: mymouse
anchors.fill: parent
property var arrpoints : []
onClicked: {
arrpoints.push({"x": mouseX, "y": mouseY})
canvas.requestPaint()
}
}
}
}

Related

QML: Move the rectangle outside the window

Is it possible to move the rectangle outside the window? The only thing I came up with is to write custom logic that will resize the top window when moving the rectangle outside the window.
Current behavior (imgur .gif):
Current behavior
Desired behavior (imgur .png):
Desired behavior
main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: root
width: 300
height: 500
visible: true
flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
color: "#00000000"
Rectangle {
id: draggable
color: "blue"
x: 100
y: 100
width: 100
height: 100
MouseArea {
anchors.fill: parent
property real lastMouseX: 0
property real lastMouseY: 0
onPressed: {
lastMouseX = mouseX
lastMouseY = mouseY
}
onMouseXChanged: {
draggable.x += (mouseX - lastMouseX)
}
onMouseYChanged: {
draggable.y += (mouseY - lastMouseY)
}
}
}
Rectangle {
color: "blue"
x: 100
y: 300
width: 100
height: 100
// ...
}
}
Windows can be children of other Windows. The Window behavior is still subject to certain platform-dependent behavior, but on a Desktop environment child Windows should still be able to move outside the parent Window. So simply changing your Rectangle to be a Window will give you the desired effect.
Window {
id: root
Window {
id: draggable
...
}
}

Why dynamic objects inside ScrollView won't scroll with the other content?

So in my QML application I a have got a ScrollView with some fixed content.
Then I'd like to add some dynamic content (with Javascript) inside this ScrollView.
The problem is that this newly created objects won't scroll up and down with the other content (the Image).
What am I doing wrong?
CODE:
import QtQuick 2.15
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
Window {
id: window
width: 640
height: 480
visible: true
title: qsTr("Hello World")
signal hideBounds(bool hide)
property var newArrow: []
color: "light grey"
function destroyArrow(number) {
newArrow[number].destroy(500)
}
ScrollView {
id: scroll
width: parent.width
height: parent.height
contentHeight: pdfImage.height
Image {
id: pdfImage
fillMode: Image.PreserveAspectFit
width:parent.width*0.8
x: (parent.width-width)/2
source: "file:///Users/Riccardo/Downloads/file.png"
}
Component.onCompleted: {
var component = Qt.createComponent("CompleteArrow.qml")
for(var i = 0; i < 3; i++) {
newArrow[i] = component.createObject(scroll, {id: "dynamicArrow"+i, arrayNumber: i, quoteNumber: (i + 1), x: scroll.width*0.05, y: i, width: scroll.width*0.1, height: scroll.width*0.1, mirror: true, flip:false})
newArrow[i].destroyMe.connect(destroyArrow)
}
}
}
}
According to the docs:
Items created dynamically need to be explicitly parented to the contentItem.
So your call to createObject needs to change to this:
newArrow[i] = component.createObject(scroll.contentItem, ...)
EDIT:
Actually, the link to the docs I gave was for Flickable. For some reason, I was thinking a ScrollView was a Flickable, but it's not. I still think the answer might work, but I haven't tried it myself.

Line colors overlap when changed

I am working on an QML program in which I change the color and the border color of a polygon painted on a Canvas. I have 2 buttons that change the colors, ie. 1 button making the border color red and the other one blue.
My problem is that each time I change the color set, the border of the polygon seems "corrupted", such as the used border colors mixed with each other. The drawn polygon is being resized each time I resize the window. So when I resize, its being repainted I believe. The colors are getting fixed at that point.
My question is: is there a way to disable the overlapping or is there a way to manually force the redrawing of all Canvases in the project?
Ucolors.qml:
import QtQuick 2.9
/**
* #brief Holds the color parameters of the whole UI
*
*/
Item
{
property var canvas
property var text
property var spiderBckgrnd
property var spiderLines
}
main.qml
Ucolors
{
id: colDay
canvas: "#eaedf1"
text: "#0e0e12"
spiderBckgrnd: "#f7fbff"
spiderLines: "#C8CBD0"
}
Ucolors
{
id: colNight
canvas: "#22212c"
text: "#ffffff"
spiderBckgrnd: "#0e0e12"
spiderLines: "#3C3C3F"
}
property var colGlob: colDay
Button
{
id: btn1
anchors.left: parent.left
text: "button1"
onClicked:
{
colGlob = colNight;
}
}
Button
{
id: btn2
anchors.left: btn1.right
text: "button2"
onClicked:
{
colGlob = colDay;
}
}
Then in the code colors are set like this: some_property: colGlob.spiderLines
If you do not explicitely call clearRect() on the context object of your Canvas, any drawing is painted on the existing content.
Since you're drawing a polygon, some antialiasing pixels are added on the edges to get smooth lines. Those pixels are semi-transparent, so when you draw the same polygon over the existing one with another color, color blending occurs on the edges; hence the "corrupted" appearance.
When you change the height or width of the canvas, the context is implicitely cleared, so the "corrupted" edge disappears.
An easy fix is to call clearRect in the onPaint handler of your Canvas.
Canvas
{
id: canvas
onPaint: {
var ctx = getContext("2d")
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw anything you want
...
}
}
Here is a small example that reproduces your problem, and show how it is fixed by calling clearRect
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
ApplicationWindow {
id: root
visible: true
width: 400
height: 200
property string color: "red"
onColorChanged: {
canvas.requestPaint()
}
ColumnLayout
{
anchors.fill: parent
anchors.margins: 20
spacing: 20
RowLayout
{
Layout.alignment: Qt.AlignHCenter
Repeater
{
model: ["red", "blue", "yellow"]
Button
{
text: modelData
highlighted: root.color == modelData
onClicked: {
root.color = modelData
}
}
}
CheckBox
{
id: clearBeforeRepaintCb
text: "Clear before paint"
}
}
Canvas
{
id: canvas
Layout.fillHeight: true
Layout.fillWidth: true
onPaint: {
var ctx = getContext("2d")
if(clearBeforeRepaintCb.checked)
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.strokeStyle = root.color
ctx.lineWidth = 10
ctx.beginPath()
ctx.moveTo(10, height/2)
ctx.lineTo(width-10, height/2+3)
ctx.stroke()
}
}
}
}

How do I permanently delete objects from a QML canvas?

Consider the QML code below, which allows me to insert points onto a blank QML canvas, with mouse-clicks and then clear all the input points and the corresponding pictures on the canvas, using a button placed in the upper-left hand corner
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: mycanvas
width: 1000
height: 1000
property var arrpoints : []
onPaint: {
var context = getContext("2d");
// Delete everything drawn before?
context.clearRect(0, 0, mycanvas.width, mycanvas.height);
// Render all the points as small black-circles
context.strokeStyle = Qt.rgba(0, 1, 1, 0)
// Draw all the points
for(var i=0; i < arrpoints.length; i++){
var point = arrpoints[i]
context.ellipse(point["x"], point["y"], 10, 10)
context.fill()
context.stroke()
}
}
// For mousing in points.
MouseArea {
id: mymouse
anchors.fill: parent
onClicked: {
// Record mouse-position into all the input objects
mycanvas.arrpoints.push({"x": mouseX, "y": mouseY})
mycanvas.requestPaint()
console.log( mycanvas.arrpoints )
} // onClicked
}// MouseArea
} // Canvas
Button {
text: "clear input"
onClicked: {
mycanvas.arrpoints.length = 0
mycanvas.requestPaint()
console.log( mycanvas.arrpoints )
}
}
}//Window
This code behaves quite strangely. Suppose I input a few points onto the canvas, and then click the "clear input" button. then as expected all the pictures (ie little circles corresponding to points) vanish from the canvas
and the arrpoints array is set to empty.
But when I start clicking on the canvas again, the cleared pictures are redrawn, alongside the new points being entered!! Why should this be? After printing to the console, I can still see arrpoints=[] so the problem should be with the clearing of the canvas in the onPaint section.
How do I tell QML to erase its canvas memory completely?
If you want to clean the Canvas you must reset the context. In this case, implement a function that does it and force the canvas to update.
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: mycanvas
width: 1000
height: 1000
property var arrpoints : []
onPaint: {
var context = getContext("2d");
// Delete everything drawn before?
context.clearRect(0, 0, mycanvas.width, mycanvas.height);
// Render all the points as small black-circles
context.strokeStyle = Qt.rgba(0, 1, 1, 0)
// Draw all the points
for(var i=0; i < arrpoints.length; i++){
var point = arrpoints[i]
context.ellipse(point["x"], point["y"], 10, 10)
context.fill()
context.stroke()
}
}
function clear() {
var ctx = getContext("2d");
ctx.reset();
mycanvas.requestPaint();
}
// For mousing in points.
MouseArea {
id: mymouse
anchors.fill: parent
onClicked: {
// Record mouse-position into all the input objects
mycanvas.arrpoints.push({"x": mouseX, "y": mouseY})
mycanvas.requestPaint()
console.log( mycanvas.arrpoints )
} // onClicked
}// MouseArea
} // Canvas
Button {
text: "clear input"
onClicked: {
mycanvas.arrpoints.length = 0
mycanvas.clear()
console.log( mycanvas.arrpoints )
}
}
}//Window

How to catch the moment when QML Component is completely layouted?

I have a GridView in QML ApplicationWindow which should be filled
with some Items.
I place my items with JS function "placeItems".
But the problem is that when Component.onCreated signal of ApplicationWindow is called the GridView is not yet layouted.
For example, the GridView has x coordinate equal to -425 in Component.onCreated of ApplicationWindow.
If I call the same function a second later - everything is ok and GridView
has correct coordinates (=75).
I've check the Qt reference back and forth and haven't found other signals (something like onLayouted or onLayoutComplete) that may be helpful.
The question is when to call "placeItems" so the GridView in ApplicationWindow
already has correct coordinates?
UPDATE1:
To observe the bad behaviour just click File->Start after the application started. It will place the item in the correct place.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
function max (a,b) { return a>b ? a : b; }
function min (a,b) { return a<b ? a : b; }
property int sizeMin: width < height ? width : height
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
property var items: []
function centerCell(column,row) {
return {x: field.x + (column + 0.5) * cellWidth,
y: field.y + (row + 0.5) * cellHeight}
}
function placeItem(name, col, row) {
var c = centerCell(col,row)
items[name].centerX = c.x
items[name].centerY = c.y
}
function placeItems() {
placeItem ("a", 3, 3)
//placeItem ("b", 4, 4)
}
}
Component.onCompleted: field.placeItems()
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int centerX: 0
property int centerY: 0
property int margin: 5
property var cell: field.items["a"] = this
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
x: centerX - width/2
y: centerY - height/2
color: "red"
opacity: 0.5
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Start")
onTriggered: field.placeItems();
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
function placeItem(name, col, row) {
items[name].anchors.horizontalCenter = field.left;
items[name].anchors.verticalCenter = field.top;
items[name].anchors.horizontalCenterOffset = (col + 0.5) * cellWidth;
items[name].anchors.verticalCenterOffset = (row + 0.5) * cellHeight;
}
The key is to anchor the element in the grid view and then move it according to your calculations.
BTW, you know that QML has built in functions Math.min/Math.max?
EDIT
Or better yet, why not define the bindings in rectItemA directly?
Another, less hackish way to have the right behavior (don't play with Timer with layout, really, it's a bad idea):
You are defining your Rectangle as an item centered in a instance of a item belonging to your GridView. So, I use a little of your way (getting an item at the r row and the c column in the gridview), and then I reparent the Rectangle to this item. To make it centered, it is only needed to anchor it to the center of its newly bound parent.
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.1
ApplicationWindow {
id: mainWindow
width:1000
height: 900
color : "white"
visible: true
flags: Qt.Window
property int sizeMin: Math.min(width, height)
property int dimField: sizeMin - 50
property int dimCellSpacing: 3
property int dimCell: (dimField / 5 ) - 1 - dimCellSpacing
GridView {
id: field
anchors.centerIn: parent
model: 20
width: dimField
height: dimField
cellWidth: dimCell
cellHeight: dimCell
delegate: cell
function cellAt(row, col) {
return itemAt(row * (dimCell + dimCellSpacing), col * (dimCell + dimCellSpacing));
}
}
Component {
id: cell
Rectangle {
id: rectCell
width: dimCell
height: dimCell
color: "lightgray"
border.width: 3
border.color: "brown"
}
}
Rectangle
{
id: rectItemA
property int dimItem: 100
property int margin: 5
border.color: "black"
border.width: 3
width: dimItem
height: dimItem
anchors.centerIn: parent
color: "red"
opacity: 0.5
}
Component.onCompleted: {
rectItemA.parent = field.cellAt(3, 3);
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit();
}
}
}
}
Why don't you just delay the placeItems function so it runs with a tiny delay so that when it runs the "static" components are all completed.
Timer {
interval: 250 // might want to tune that
repeat: false
onTriggered: placeItems()
}
In a perfect world, Component.onCompleted would nest perfectly and the root item would be the last one to be emitted. But unfortunately Qt does not guarantee the order, and indeed as I did a quick test, the parent item emits (or at least responds to) onCompleted BEFORE the child items.
And if you don't want to pollute your QML file with the timer, you can actually instantiate a "helper object" from a JS function dynamically, set it to do its work and then delete itself when done. Similar to the "reparent helper" I outlined in this answer: Better way to reparent visual items in QML but rather delete itself on the timer timeout rather than in the JS function which would not give it the time to trigger.

Resources