Long time C++ Qt user tries out QML
-
I have been using Qt for the past few years to create UIs for various C++ applications but I haven't tried Qt Quick and QML until now. So the thing I found really interesting about Qt Quick was that it lets you specify a UI in a declarative way (instead of imperative, as in C++) One of the things which I was wondering was if you could also specify the way graphics are drawn in a declarative way or weather it was just for the layout of ui widgets (as in the XML files produced by the Qt Designer in Qt 4.x).
After playing with it for a while I ended up with a simple QML file which draws the Koch Kurve using the HTML5 canvas.
@/**
- Koch Curve
- F -> F + F - F - F + F
- F means draw a straight line, + means turn left Pi/2 and - means turn
- right Pi/2.
- See wikipedia for more.
- by NilsB
*/
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.0Rectangle {
width: 400 height: 600 Column { Canvas { id: canvas // NOTE If I make the height and width dependent on the parent, // then it somehow ends in an infinite loop, why? width: 400 height: 500 antialiasing: true property int l: 2 property int nRecursions: 1 function turnLeft(ctx) { ctx.rotate(-Math.PI/2); } function turnRight(ctx) { ctx.rotate(Math.PI/2); } function drawLine(ctx) { ctx.lineTo(l, 0); ctx.translate(l, 0); } function drawBasicShape(ctx) { drawLine(ctx); turnLeft(ctx); drawLine(ctx); turnRight(ctx); drawLine(ctx); turnRight(ctx); drawLine(ctx); turnLeft(ctx); drawLine(ctx); } function rKoch(ctx, n) { if (n === 1) { drawBasicShape(ctx); return; } var m = n - 1; rKoch(ctx, m); turnLeft(ctx); rKoch(ctx, m); turnRight(ctx); rKoch(ctx, m); turnRight(ctx); rKoch(ctx, m); turnLeft(ctx); rKoch(ctx, m); } function kochCurve(ctx, nRecursions) { ctx.lineWidth = 2; ctx.strokeStyle = "darkgray"; ctx.beginPath(); ctx.translate(10, 200); rKoch(ctx, nRecursions); ctx.stroke(); } onPaint: { var ctx = canvas.getContext('2d'); ctx.reset(); kochCurve(ctx, nRecursions); x = 0; y = 100; } } Row { Text { text: "# Recursions: " } Slider { id: sliderRecursions tickmarksEnabled: true stepSize: 1.0 minimumValue: 1 maximumValue: 6 value: 1 onValueChanged: { canvas.nRecursions = value; canvas.requestPaint(); } } } }
}@
This was easy to do by using the HMTL 5 canvas, but I kept wondering weather this is the way to do things the "QtQuick-Way" after all the canvas API is nothing Qt specific. Browsing around a bit and reading some more I figured out that there is also QQuickPaintedItem which is a wrapper around the QPainter API. So.. what is the way to paint something like a Koch Kurve in a way Qt Quick intended?
Also I ran into some problem I could not solve: For example the debugger did not show a proper stack trace when putting a breakpoint int onPaint(), but I want to see where it gets called from. And also for many JavaScript objects it just show "object" as value. So I ended up using console.log.
I also wanted to specify the size of the Canvas in terms of the size of it's parent rectangle:
@
width: parent.width
height: parent.height - 100
@But doing so it gets stuck in some kind of endless loop where onPaint(..) gets called all the time. I could not figure out why and from where using the debugger.
One more thing I also could not figure out is weather I have to use JavaScript or I can just use QML and C++ for all the non-declarative code.
-
The Canvas api is a convenience api. If your onPaint function is relatively CPU intensive, you may get a performance boost by implementing the element as a QQuickPaintedItem. You can still keep the property interface the same for the rest of the declarative usage.
QQuickPaintedItem still isn't the fastest way to paint in QtQuick. You can do faster painting in c++ using QSG-nodes or even using direct OpenGL drawing before or after rendering of the QtQuick scene.
In your case, the contents of the Canvas seem to update rarely, while the GUI could be refreshed due to other animating items. Therefore it makes sense to use QQuickPaintedItem so the gui can refresh as fast as possible since its contents are available directly as a texture.
The reason you get a binding loop if you make the Canvas item depend on the size of the parent is because the parent is a Column. The Column item resizes itself based on the sizes of its children, therefore the loop.
-
Thanks a lot for the detailed answer! So the canvas API is the only way to paint from QML/JavaScript? For all the other options you still have to relay on C++. What about a delarative way to specify vector graphics? I could only find QML Rectagle and wonder if I could recursivley declare them somehow that the will form a Koch Curve :D
However I am a bit disappointed by the fact that you can create binding loops by doing something simple as possible. I mean I thought QML is as an easy to use solution for fronteds, like HTML but with a cleaner syntax and IIRC you cannot create binding loops in HTML.