Implement your own QtQuick element and use QPainter to draw ......
I used to think that Canvas in Qt Quick can be self-painted. later I found that no. There are several ways to draw the Canvas! You can use the original OpenGL (Qt Quick uses OpenGL rendering) to construct a QSGNode for plotting, or use QPainter! Wow, I'm familiar with QPainter. Therefore, I used QPainter and QML to implement a simple graffiti program: PaintedItem. It has the following functions:
- Set line width
- Set line color
- Set Background color
- Clear graffiti
- Unlimited undo
The program is simple and the effect is as follows:
PaintedItem
Although the program is simple, there are still some new content that was not mentioned before:
- QQuickPaintedItem
- C ++ implements QML visual elements (Item)
- How to handle mouse events with custom elements
Let's talk about it one by one.
Copyright foruok, reprinted please indicate the source: http://blog.csdn.net/foruok.
QQuickPaintedItem
The core of Qt Quick is Scene Graph, which can be searched and learned with the keyword "Scene Graph" in the index mode with the help of Qt. The design concept of Scene Graph is similar to that of QGraphicsView/QGraphicsScene. in a scenario, many elements are stored in the scenario. The difference is that the Item is drawn. in the QGraphicsView framework, the View drawing event is used to drive the Item painting. QGraphicsItem has a paint () virtual function, as long as you implement the paint () function from the Item inherited by QGraphicsItem, you can draw it on the QPaintDevice. The logic is direct. In fact, there is another rendering thread for the Qt Quick painting, items in Scene do not have the intuitive plotting function of painting (). there is only one updatePaintNode () method for you to construct the geometric representation of your items. when the program rotates to the rendering loop, the rendering cycle draws the QSGNode tree of all items.
The updatePaintNode () method is not intuitive. it comes from the OpenGL or Direct 3D drawing mode: you create the geometric representation of the graph element, someone else will help you draw based on the materials you provide at a certain time, just as you throw a bag of garbage at the door, and someone will help you take it away for a while. Developers who are familiar with Qt Widgets and QPainter may not be able to adapt to this method. Therefore, Qt Quick provides a way to be compatible with the old habit: introducing QQuickPaintedItem and using QPainter to draw.
Generally, you can understand this as follows: QQuickPaintedItem uses the usual 2D drawing method in Qt Widgets to draw the lines, images, and text you want into a QImage in memory, then, place the QImage as a QSGNode and wait for the Qt Quick rendering thread to take it away and draw it into the actual scenario. According to this understanding, QQuickPaintedItem has multiple drawing steps, causing performance loss! However, for the convenience of development, sometimes this little performance loss can be tolerated-as long as your application can still run smoothly.
QQuickPaintedItem is the base class of Qt Quick Item to be drawn using QPainter. it has a pure virtual function-paint (QPainter * painter). you only need to implement painting () for your custom Item () you can use the virtual function.
QQuickPaintedItem is the derived class of QQuickItem. the boundingRect () method of QQuickItem returns the rectangle of an Item. you can draw your Item based on it. FillColor () returns the fill color of the Item (transparent by default). Qt Quick uses this color to draw the background of your Item before calling the paint () method. SetFillColor () can change the fill color.
Qt Quick provides a "Scene Graph-Painted Item" example to demonstrate the usage of QQuickPaintedItem. you can refer to it.
C ++ implements QML visual elements
A considerable number of graphic elements provided by Qt Quick are implemented in C ++ and then exported to the QML environment, such as Text. We can also do this as long as you inherit from QQuickItem (corresponding to the Item element in QML) to implement your C ++ class.
In our example, we want to use QPainter plotting, so we inherit from QQuickPaintedItem and override the paint () method.
After the C ++ class is completed and exported to the QML environment, you can use the exported class just like using the QML built-in elements. For more information about how to export and use QML, see QML and C ++ hybrid programming in Qt Quick.
How to handle mouse events with custom elements
In QML, we always use MouseArea to handle mouse events. MouseArea corresponds to the QQuickMouseArea class in C ++. it is actually a derived class of QQuickItem. In fact, QQuickItem defines a series of virtual functions for processing mouse events, such as mousePressEvent, mouseMoveEvent, and mouseMoveEvent. it can process mouse events, but QQuickItem does not export these functions, we cannot use it in QML. The introduction of QQuickMouseArea (MouseArea in QML) is to facilitate the processing of mouse events. you don't need to rewrite many methods for every Item like QWidget, which is really annoying, although this method of QML uses an object, it is more convenient. However, for our PaintedItem class, if we use MouseArea to handle mouse events in QML, when we track the mouse track to draw lines, we need to constantly upload the pixel information carried in the mouse event back to C ++, which is very troublesome and has poor performance. Therefore, we directly rewrite the virtual functions of QQuickItem to process the mouse event.
We know that MouseArea has an acceptedButtons attribute, which can be used to set which mouse button the Item is to process. In fact, the information "the mouse button to process" is saved in QQuickItem and is saved through setAcceptedMouseButtons () method. By default, QQuickItem does not process any mouse buttons, so to process the mouse keys, you must set them in our PaintedItem, just like MouseArea. In our example, we did this in the PaintedItem constructor:
PaintedItem::PaintedItem(QQuickItem *parent) : QQuickPaintedItem(parent) , m_element(0) , m_bEnabled(true) , m_bPressed(false) , m_bMoved(false) , m_pen(Qt::black){ setAcceptedMouseButtons(Qt::LeftButton);}
As shown in the code, we only process the left mouse button. If you do not set this, you will not receive any mouse events.
PaintedItem source code analysis
Because the functions we implement are simple and the source code is not complex.
Custom Item
First look at PaintedItem. h:
#ifndef PAINTEDITEM_H#define PAINTEDITEM_H#include
#include
#include
#include
#include
class ElementGroup{public: ElementGroup() { } ElementGroup(const QPen &pen) : m_pen(pen) { } ElementGroup(const ElementGroup &e) { m_lines = e.m_lines; m_pen = e.m_pen; } ElementGroup & operator=(const ElementGroup &e) { if(this != &e) { m_lines = e.m_lines; m_pen = e.m_pen; } return *this; } ~ElementGroup() { } QVector
m_lines; QPen m_pen;};class PaintedItem : public QQuickPaintedItem{ Q_OBJECT Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(int penWidth READ penWidth WRITE setPenWidth) Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor)public: PaintedItem(QQuickItem *parent = 0); ~PaintedItem(); bool isEnabled() const{ return m_bEnabled; } void setEnabled(bool enabled){ m_bEnabled = enabled; } int penWidth() const { return m_pen.width(); } void setPenWidth(int width) { m_pen.setWidth(width); } QColor penColor() const { return m_pen.color(); } void setPenColor(QColor color) { m_pen.setColor(color); } Q_INVOKABLE void clear(); Q_INVOKABLE void undo(); void paint(QPainter *painter);protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); void purgePaintElements();protected: QPointF m_lastPoint; QVector
m_elements; ElementGroup * m_element; // the Current ElementGroup bool m_bEnabled; bool m_bPressed; bool m_bMoved; QPen m_pen; // the Current Pen};#endif // PAINTEDITEM_H
To put it down, the ElementGroup class stores the lines that need to be drawn by pressing and moving the left mouse button until the left mouse button releases the action sequence, and stores them in the member variable m_lines, the paint brush used to draw these lines is represented by m_pen.
In PaintedItem, the member variable m_elements indicates all action sequences in the drawing process. M_element points to the current action sequence, and m_pen indicates the paint brush configured by the user.
Other methods are intuitive and will not be described in detail.
The following is PaintedItem. cpp:
#include "PaintedItem.h"#include
#include
#include
#include
#include
PaintedItem::PaintedItem(QQuickItem *parent) : QQuickPaintedItem(parent) , m_element(0) , m_bEnabled(true) , m_bPressed(false) , m_bMoved(false) , m_pen(Qt::black){ setAcceptedMouseButtons(Qt::LeftButton);}PaintedItem::~PaintedItem(){ purgePaintElements();}void PaintedItem::clear(){ purgePaintElements(); update();}void PaintedItem::undo(){ if(m_elements.size()) { delete m_elements.takeLast(); update(); }}void PaintedItem::paint(QPainter *painter){ painter->setRenderHint(QPainter::Antialiasing); int size = m_elements.size(); ElementGroup *element; for(int i = 0; i < size; ++i) { element = m_elements.at(i); painter->setPen(element->m_pen); painter->drawLines(element->m_lines); }}void PaintedItem::mousePressEvent(QMouseEvent *event){ m_bMoved = false; if(!m_bEnabled || !(event->button() & acceptedMouseButtons())) { QQuickPaintedItem::mousePressEvent(event); } else { //qDebug() << "mouse pressed"; m_bPressed = true; m_element = new ElementGroup(m_pen); m_elements.append(m_element); m_lastPoint = event->localPos(); event->setAccepted(true); }}void PaintedItem::mouseMoveEvent(QMouseEvent *event){ if(!m_bEnabled || !m_bPressed || !m_element) { QQuickPaintedItem::mousePressEvent(event); } else { //qDebug() << "mouse move"; m_element->m_lines.append(QLineF(m_lastPoint, event->localPos())); m_lastPoint = event->localPos(); update(); }}void PaintedItem::mouseReleaseEvent(QMouseEvent *event){ if(!m_element || !m_bEnabled || !(event->button() & acceptedMouseButtons())) { QQuickPaintedItem::mousePressEvent(event); } else { //qDebug() << "mouse released"; m_bPressed = false; m_bMoved = false; m_element->m_lines.append(QLineF(m_lastPoint, event->localPos())); update(); }}void PaintedItem::purgePaintElements(){ int size = m_elements.size(); if(size > 0) { for(int i = 0; i < size; ++i) { delete m_elements.at(i); } m_elements.clear(); } m_element = 0;}
Let's talk about the implementation of the "clear" function. when you click the "clear" button in, the clear () method of PaintedItem is called. clear () internally calls purgePaintElements (), delete all the drawing sequences saved in m_elements, and call the update () method to trigger re-drawing.
The undo () method corresponds to the "undo" function on the interface. it deletes the closest drawing sequence and triggers the painting.
Now let's talk about the generation logic of the drawing sequence.
Generate a new drawing sequence in mousePressEvent (). in mouseMoveEvent (), combine the current vertex and the previous vertex into a line and add the current drawing sequence (m_element). when mouseReleaseEvent () when it is called, the coordinates of the pointer position when the left mouse button is lifted are also processed, so that a complete drawing sequence is generated.
Export custom Item
Directly look at the code (main. cpp ):
int main(int argc, char *argv[]){ QGuiApplication app(argc, argv); qmlRegisterType
("an.qml.Controls", 1, 0, "APaintedItem"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); return app.exec();}
QML document
There are two QML documents, main. qml is responsible for the main interface, ColorPicker. qml implements the color selection button.
Main. qml
The main. qml document has nothing to say. PaintedItem is exported as APaintedItem, and its usage is consistent with the general QML element. The following is the complete main. qml:
Import QtQuick 2.2 import QtQuick. window 2.1 import. qml. controls 1.0 import QtQuick. controls 1.2 import QtQuick. layouts 1.1 import QtQuick. controls. styles 1.2 Window {visible: true; minimumWidth: 600; minimumHeight: 480; Rectangle {id: options; anchors. left: parent. left; anchors. right: parent. right; anchors. top: parent. top; implicitHeight: 70; color: "lightgray"; Component {id: btnStyle; ButtonStyle {Background: Rectangle {implicitWidth: 70; implicitHeight: 28; border. width: control. hovered? 2: 1; border. color: "#888"; radius: 4; gradient: Gradient {GradientStop {position: 0; color: control. pressed? "# Ccc": "# eee"} GradientStop {position: 1; color: control. pressed? "# Aaa": "# ccc" }}label: Text {text: control. text; font. pointSize: 12; color: "blue"; horizontalAlignment: Text. alignHCenter; verticalignment: Text. alignVCenter ;}} ColorPicker {id: background; anchors. left: parent. left; anchors. leftMargin: 4; anchors. verticalCenter: parent. verticalCenter; text: "background"; selectedColor: "white"; onColorPicked: painter. fillColor = clr;} ColorPicker {id: fo Reground; anchors. left: background. right; anchors. top: background. top; anchors. leftMargin: 4; text: "foreground"; selectedColor: "black"; onColorPicked: painter. penColor = clr;} Rectangle {id: splitter; border. width: 1; border. color: "gray"; anchors. left: foreground. right; anchors. leftMargin: 4; anchors. top: foreground. top; width: 3; height: foreground. height;} Slider {id: thickness; anchors. left: spl Itter. right; anchors. leftMargin: 4; anchors. bottom: splitter. bottom; minimumValue: 1; maximumValue: 100; stepSize: 1.0; value: 1; width: 280; height: 24; onValueChanged: if (painter! = Null) painter. penWidth = value;} Text {id: penThickLabel; anchors. horizontalCenter: thickness. horizontalCenter; anchors. bottom: thickness. top; anchors. bottomMargin: 4; text: "Paint brush: % 1px ". arg (thickness. value); font. pointSize: 16; color: "steelblue";} Text {id: minLabel; anchors. left: thickness. left; anchors. bottom: thickness. top; anchors. bottomMargin: 2; text: thickness. minimumValue; font. pointSize: 12;} Text {id: maxLabel; anchors. right: thickness. right; anchors. bottom: thickness. top; anchors. bottomMargin: 2; text: thickness. maximumValue; font. pointSize: 12;} Rectangle {id: splitter2; border. width: 1; border. color: "gray"; anchors. left: thickness. right; anchors. leftMargin: 4; anchors. top: foreground. top; width: 3; height: foreground. height;} Button {id: clear; anchors. left: splitter2.right; anchors. leftMargin: 4; anchors. verticalCenter: splitter2.verticalCenter; width: 70; height: 28; text: "clear"; style: btnStyle; onClicked: painter. clear () ;}button {id: undo; anchors. left: clear. right; anchors. leftMargin: 4; anchors. top: clear. top; width: 70; height: 28; text: "undo"; style: btnStyle; onClicked: painter. undo ();} Rectangle {border. width: 1; border. color: "gray"; width: parent. width; height: 2; anchors. bottom: parent. bottom ;}} APaintedItem {id: painter; anchors. top: options. bottom; anchors. left: parent. left; anchors. right: parent. right; anchors. bottom: parent. bottom ;}}
Needless to say ......
Color selection button implementation
It is also intuitive, and the code is displayed directly:
import QtQuick 2.2import QtQuick.Dialogs 1.0Rectangle { id: colorPicker; width: 64; height: 60; color: "lightgray"; border.width: 2; border.color: "darkgray"; property alias text: label.text; property alias textColor: label.color; property alias font: label.font; property alias selectedColor: currentColor.color; property var colorDialog: null; signal colorPicked(color clr); Rectangle { id: currentColor; anchors.top: parent.top; anchors.topMargin: 4; anchors.horizontalCenter: parent.horizontalCenter; width: parent.width - 12; height: 30; } Text { id: label; anchors.bottom: parent.bottom; anchors.bottomMargin: 4; anchors.horizontalCenter: parent.horizontalCenter; font.pointSize: 14; color: "blue"; } MouseArea { anchors.fill: parent onClicked: if(colorDialog == null){ colorDialog = Qt.createQmlObject("import QtQuick 2.2;import QtQuick.Dialogs 1.0; ColorDialog{}", colorPicker, "dynamic_color_dialog"); colorDialog.accepted.connect(colorPicker.onColorDialogAccepted); colorDialog.rejected.connect(colorPicker.onColorDialogRejected); colorDialog.open(); } } function onColorDialogAccepted(){ selectedColor = colorDialog.color; colorPicked(colorDialog.color); colorDialog.destroy(); colorDialog = null; } function onColorDialogRejected(){ colorPicked(color); colorDialog.destroy(); colorDialog = null; }}
ColorPicker calls ColorDialog internally to select the color. ColorDialog is dynamically created using Qt. createQmlObject (). For more information about how to use it, see "dynamic creation of Qt Quick components and objects".
After you select a color, the filling color of the rectangle in the upper part of the button changes, and the colorPicked () signal is also sent. If you cancel the selection, the default color is used.
OK, so we will introduce it here.
Copyright foruok, reprinted please indicate the source: http://blog.csdn.net/foruok.
Download the source code and click me.
Let's review my Qt Quick series of articles:
- Qt Quick Introduction
- QML language basics
- Qt Quick's Hello World Graphic explanation
- Simple Qt Quick tutorial
- Signal and slot of Qt Quick event processing
- Qt Quick event processing-mouse, keyboard, and timer
- Qt Quick event processing-pinch, zoom, and rotate
- Dynamic Creation of Qt Quick components and objects
- Introduction to Qt Quick layout
- QML and C ++ mixed programming in Qt Quick
- Qt Quick Image processing instance Meitu Xiuxiu (source code download)
- Explanation of Qt Quick PathView
- Picture digging for Qt Quick instances
- Qt Quick integrated instance File Viewer
- Display the code line number for Qt Quick debugging