Previously thought that Qt Quick Canvas talent enough self-painting. Later found not, there are several ways to draw!
Ability to use original OpenGL (Qt Quick with OpenGL rendering). Ability to construct qsgnode to draw and use Qpainter! Wow. Qpainter I know very well. So, I used qpainter combined with QML to achieve a simple graffiti program: Painteditem. It has the following functions:
- Set line width
- Set Line Color
- Set Background color
- Clear Graffiti
- Infinite level Undo
The program is very primitive. Effects such as the following:
Watermark/2/text/ahr0cdovl2jsb2cuy3nkbi5uzxqvzm9ydw9r/font/5a6l5l2t/fontsize/400/fill/i0jbqkfcma==/dissolve/70 /gravity/center "/>
Figure 1 Painteditem
The program is simple though. But there are some new things that were not mentioned before:
- Qquickpainteditem
- C + + Implementation QML visual entities (Item)
- Define your own entity how to handle mouse events
Let's take a look at one of the following.
All rights reserved Foruok, reprint please indicate source: Http://blog.csdn.net/foruok.
Qquickpainteditem
The core of Qt Quick is scene graph, which is able to retrieve learning by "scene graph" as keyword in the QT help indexing mode. Scene Graph's design idea is similar to the qgraphicsview/qgraphicsscene frame, a scene. Very many elements are placed in the scene. The difference is the drawing of Item. The Qgraphicsview frame is driven by the View's Paint event to draw the item, and Qgraphicsitem has a paint () virtual function that simply implements the paint () function from the item you inherited from Qgraphicsitem. Can be drawn on the qpaintdevice, logic directly. While the Qt Quick draw, in fact there is another rendering thread, Scene of the Item does not paint () such an intuitive drawing function, there is only a Updatepaintnode () method allows you to construct your Item's geometric representation. When the program is rotated to the render loop. The render loop draws the Qsgnode tree of all Item.
Updatepaintnode () is a very non-intuitive way of drawing, which comes from OpenGL or Direct 3D drawing mode: You construct the geometry of the element, and others will draw it for you at some point, just as you throw a bag of rubbish into the doorway. Someone will come and take you away for a while. Developers accustomed to QT Widgets and Qpainter may not be able to adapt to such a way, so QT Quick provides a way to be compatible with old habits: introduce Qquickpainteditem, use Qpainter to draw.
In general, you can understand this: Qquickpainteditem uses the usual 2D drawing methods in Qt Widgets. Draw the lines, pictures, text, etc. you want into an in-memory qimage. Then put this qimage as a qsgnode and wait for the Qt Quick rendering thread to take it and draw it into the actual scene. According to this understanding, Qquickpainteditem will have multiple drawing steps. There is a loss of performance!
Just for the sake of development convenience, sometimes this little bit of performance loss can be sustained-just to keep your application flowing smoothly.
Qquickpainteditem is the base class for all Qt Quick Item that wants to use qpainter to paint. It has a pure virtual function--paint (qpainter * painter), your own definition of Item just to implement the paint () virtual function can be.
Qquickpainteditem is a derived class of Qquickitem. The Qquickitem Boundingrect () method returns a rectangle of item, and you can draw your item based on it. FillColor () returns the fill color of item (which is transparent by default), and Qt Quick uses this color to draw the background of your item before the paint () method call.
Setfillcolor () can change the fill color.
Qt Quick provides a "Scene graph-painted Item" Demo sample to demonstrate how qquickpainteditem is used, which you can refer to.
C + + implementation QML visual entities
A significant portion of the graphical elements provided by Qt Quick are exported to the QML environment after implementation in C + +, for example Text.
Then we can do that just by inheriting from Qquickitem (the Item element in the corresponding QML) to implement your C + + class.
Our demo sample is to use Qpainter drawing. So inherit from Qquickpainteditem, rewrite the Paint () method.
Complete the C + + class and export to the QML environment. You can use the class that we exported, just as you would with QML built-in elements. How to export and how to use in QML, please refer to "Qt Quick QML and C + + mixed programming specific explanation".
Define your own entity how to handle mouse events
In QML we have been using Mousearea to handle mouse events. Mousearea the Qquickmousearea class in the corresponding C + + is, in fact, a derived class of Qquickitem.
In fact, Qquickitem defines a series of virtual functions that handle mouse events. For example, Mousepressevent, Mousemoveevent, mousemoveevent and so on. It can handle mouse events on its own. Just Qquickitem did not export these functions. We can't use it in the QML.
The reason for introducing Qquickmousearea (Mousearea in QML) is to facilitate the handling of mouse events, and you do not need to rewrite a very many methods for each Item like Qwidget. That's really annoying, QML in a way that uses an object even more. But more convenient. But our Painteditem class, which assumes that we are going back to QML using Mousearea to handle mouse events, when we track the mouse tracks to draw lines, we need to keep the pixel information carried in the mouse event back into C + +. Very troublesome. Performance is not good, so we directly rewrite Qquickitem's related virtual functions to handle mouse events.
We know that Mousearea has a acceptedbuttons property that can set the Item to handle which mouse button, and actually. The "mouse button to process" message. is stored in Qquickitem, and is set by the Setacceptedmousebuttons () method.
By default. Qquickitem does not handle any mouse button, so we have to deal with the mouse button, it must be set in our Painteditem, just like Mousearea. In our demo sample, we did this in the Painteditem constructor:
Painteditem::P ainteditem (Qquickitem *parent) : Qquickpainteditem (parent) , m_element (0) , m_benabled ( true), m_bpressed (false), m_bmoved (False) , M_pen (qt::black) { setacceptedmousebuttons (Qt:: LeftButton);}
As the code sees, we just deal with the left mouse button. Suppose you don't set this up. You don't receive any mouse events.
Painteditem Source Code Analysis
The source code is not complex because of the simplicity of the functions we implement.
Define Item Yourself
First Look at PaintedItem.h:
#ifndef painteditem_h#define painteditem_h#include <QQuickPaintedItem> #include <QVector> #include < qpointf> #include <QLineF> #include <qpen>class elementgroup{public:elementgroup () {} ELEMENTGR OUP (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<qlinef> 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) publ Ic: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);p rotected:void mousepressevent (qmouseevent *event); void Mousemoveevent (Qmouseevent *event); void Mousereleaseevent (Qmouseevent *event); void Purgepaintelements ();p rotected:qpointf m_lastpoint; Qvector<elementgroup*> 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
Next elementgroup this class. It saves the left mouse button press, move, until the left button to release this action sequence generated by the need to draw the line, saved in the member variable M_lines, and the brush used to draw these lines is represented by M_pen.
In Painteditem, the member variable m_elements represents all the action sequences in the paint process. The m_element points to the current action sequence, and M_pen represents the brush that the user configures.
The other methods are more intuitive and don't repeat them.
Here are the PaintedItem.cpp:
#include "PaintedItem.h" #include <QPainter> #include <QPen> #include <QBrush> #include <qcolor > #include <qdebug>painteditem::P ainteditem (Qquickitem *parent): Qquickpainteditem (parent), m_element (0) , m_benabled (True), m_bpressed (false), m_bmoved (false), M_pen (qt::black) {setacceptedmousebuttons (Qt::lef TButton);} Painteditem::~painteditem () {purgepaintelements ();} void Painteditem::clear () {purgepaintelements (); Update ();} void Painteditem::undo () {if (M_elements.size ()) {delete M_elements.takelast (); Update (); }}void Painteditem::p aint (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) {QQuick Painteditem::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::p urgepaintelements () {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;}
When you click on the "clear" button in Figure 1, the Clear () method of Painteditem is called, and Clear () calls Purgepaintelements () internally. Delete all the drawing sequences stored in the m_elements, and then call the update () method to trigger another draw.
Undo () method The Undo function on the corresponding interface, which deletes a recent drawing sequence and then triggers the drawing.
Now let's talk about the generation logic of the drawing sequence.
A new paint sequence is generated in mousepressevent (), where the current and previous points are combined as a line in Mousemoveevent (), the current drawing sequence (m_element) is added, and when Mousereleaseevent () 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 your own definition item
Look directly at the code (MAIN.CPP):
int main (int argc, char *argv[]) { qguiapplication app (argc, argv); Qmlregistertype<painteditem> ("An.qml.Controls", 1, 0, "Apainteditem"); Qqmlapplicationengine engine; Engine.load (Qurl (qstringliteral ("qrc:///main.qml")); return app.exec ();}
QML documentation
There are two QML documents, MAIN.QML is responsible for the main interface, COLORPICKER.QML implements the color selection button.
Main.qml
MAIN.QML documentation There is nothing to say, Painteditem exported to Apainteditem, its use is consistent with the general QML element. The following is the complete main.qml:
Import QtQuick 2.2import qtquick.window 2.1import an.qml.Controls 1.0import qtquick.controls 1.2import qtquick.layouts 1.1import QtQuick.Controls.Styles 1.2Window {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.tex T Font.pointsize:12; Color: "Blue"; HorizontalAlignment:Text.AlignHCenter; VerticalAlignment: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:foreground; 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:splitter.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: "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; }}
Don't say more ...
Implementation of Color Select button
Also more intuitive, directly on the code:
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.cr Eateqmlobject ("Import QtQuick 2.2;importQtquick.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 internally calls ColorDialog to select the color.
ColorDialog is created dynamically using Qt.createqmlobject (), please refer to the "Qt Quick component and Object dynamic creation specific explanation" for specific usage methods.
After the user has selected a color. The fill color of the rectangle on the top half of the button changes, and the colorpicked () signal is emitted at the same time. Assuming the user cancels the selection, the default color is used.
OK, here we are.
All rights reserved Foruok, reprint please indicate source: Http://blog.csdn.net/foruok.
Source code Download point I dot me.
Think back to my QT Quick Series article:
- A brief introduction to Qt quick
- QML Language Basics
- Qt Quick's Hello World Text detail explanation
- Qt Quick Simple Tutorial
- The signal and slot of Qt Quick event processing
- Qt Quick Event Processing mouse, keyboard, timer
- Pinch scaling and rotation of Qt Quick event processing
- Qt Quick component and Object dynamic creation specific explanation
- Introduction to Qt Quick layout
- Qt Quick's QML and C + + mixed programming specific explanation
- Qt Quick Image Processing example of beauty 美图秀秀 (download with source code)
- Qt Quick's Pathview specific explanation
- Qt Quick Example Digging avatar
- A file viewer for the Qt quick Synthesis instance
- QT Quick Debug Display code line number
Qt Quick implementation of graffiti program