//In the custom type example, we showed how to integrate custom types with the meta object system
//Enable them to be stored in the QVariant object, write debugging information, and use for signal slot communication
//In this example, we create a new value class Block
//And register it with the meta object system
//Enables us to send its instances between threads using queued signals and slots.
//The Block Class
//The Block class is similar to the Message class described in the custom type example
//It provides a default constructor in the public part of the classes required by the meta object system
//Copy constructors and destructors. It describes a colored rectangle.
class Block { public: Block(); Block(const Block &other); Block(const QRect &rect,const QColor &color); QColor color() const; QRect rect() const; private: QRect m_rect; QColor m_color; }; Q_DECLARE_METATYPE(Block);
//We still need to call qRegisterMetaType() at runtime
//The template function registers it in the meta object system
//Then we can make any connection using this type of signal slot.
//Although we are not going to use a type with QVariant in this example
//But it's best to use Q_DECLARE_METATYPE() declares a new type.
//The implementation of the Block class is trivial, so we avoid referencing it here.
//The Window Class
//We define a simple Window class with a common slot that accepts Block objects.
//The rest of this class is related to managing the user interface and working with images.
class Window : public QWidget { Q_OBJECT public: Window(QWidget *parent = nullptr); void loadImage(const QImage &image); public slots: void addBlock(const Block &block); public slots: void loadImage(); void resetUi(); private: QLabel *label; QPixmap pixmap; QPushButton *loadButton; QPushButton *resetButton; QString path; RenderThread *thread; };
//The Window class also contains a worker thread
//Provided by the RenderThread object
//This signals the Block object to the addBlock(Block) slot of the window.
//The Window class also contains a worker thread provided by the RenderThread object.
//This signals the Block object to the addBlock(Block) slot of the window.
//The most relevant parts of the Window class are the constructor and the addBlock(Block) slot.
//Constructor creates a thread for rendering an image
//Set up a user interface with a label and two buttons connected to slots in the same class.
Window::Window(QWidget *parent) :QWidget(parent),thread(new RenderThread(this)) { label = new QLabel(this); label->setAlignment(Qt::AlignCenter); loadButton = new QPushButton(tr("&Load image ..."),this); resetButton = new QPushButton(tr("&Stop"),this); resetButton->setEnable(false); connect(loadButton,&QPushButton::clicked, this,QOverload<>::of(&Window::loadImage)); connect(resetButton,&QPushButton::clicked, thread,&RenderThread::stopProcess); connect(thread,&RenderThread::finished, this,&Window::resetUi); connect(thread,&RenderThread::sendBlock, this,&Window::addBlock); }
//In the last connection
//We connect the signal in the RenderThread object to the addBlock(Block) slot in the window
//The rest of the constructor just sets the layout of the window.
//adBlock(Block) slot receiver receives blocks from the rendering thread through the signal slot connection set in the constructor:
void Window::addBlock(const Block& block) { QColor color = block.color(); color.setAlpha(64); QPainter painter; painter.begin(&pixmap); painter.fillRect(block.rect(),color); painter.end(); label->setPixmap(pixmap); }
//We just put them on the label when they arrive.
//RenderThread class
//The RenderThread class processes images
//Create Block objects and send them to other components in the example using sendBlock(Block) signals.
class RenderThread : public QThread { Q_OBJECT public: RenderThread(QObject *parent = nullptr); ~RenderThread(); void processImage(const QImage &image); signals: void sendBlock(const Block &block); public slots: void stopProcess(); protected: void run(); private: bool m_abort; QImage m_image; QMutex mutex; };
//There are no references to constructors and destructors.
//They are responsible for setting the internal state of a thread and cleaning it up when it is destroyed.
//Processing starts with the processImage() function
//It calls the RenderThread class to re implement the QThread::run() function:
void RenderThread::processImage(const QImage &image) { if(image.isNull()) return; m_image = image; m_abort = false; start(); } void RenderThread::run() { int size = qMax(m_image.width() / 20, m_image.height()/20); for(int s = size; s > 0; --s) { for(int c = 0; c < 400; ++c) { //Ignore details of image processing methods //We see that signals containing blocks are transmitted in the usual way //Ignore details of image processing methods //We see that signals containing blocks are transmitted in the usual way: //.... Block block(QRect(x1,y1,x2 - x1 + 1,y2 - y1 + 1), QColor(red/n,green/n,blue/n)); emit sendBlock(block); if(m_abort) return; msleep(10); } }
//Each signal emitted will be queued and later transmitted to the windows adBlock(Block) slot.
//Registering the Type
//In the main() function of the example
//We register the Block class as a custom type of the meta object system by calling the qRegisterMetaType() template function:
int main(int argc,char *argv[]) { QApplication app(argc,argv); qRegisterMetaType<Block>(); Window window; window.show(); window.loadImage(createImage(256, 256)); return app.exe(); }
//This call is placed here to ensure that the type is registered before any signal slot connection using it.
//The rest of the main() function is about seeding the pseudo-random number generator
//Create and display windows and set default images
//See the implementation source code of the createImage() function.
//Further Reading
//This example shows how to register a custom type in the meta object system
//So that it can be used with signals and slot connections between threads
//For general communications involving direct signals and slots
//It is sufficient to declare types as described in the custom type example.
//In fact, Q_DECLARE_METATYPE() macro and qRegisterMetaType()
//Template functions can be used to register custom types
//However, qRegisterMetaType() only needs to communicate with the signal slot or create and destroy self
//You only need to type the type at run time when defining an object.
//For more information about using custom types in Qt, see the documentation on creating custom Qt types
main.cpp
#include <QApplication> #include <QPainter> #include <QTime> #include "block.h" #include "window.h" //Qt provides four classes for processing image data: QImage, QPixmap, QBitmap and QPicture. //QImage is designed and optimized for I/O and direct pixel access and operation //QPixmap is designed and optimized to display images on the screen. //QBitmap is just a convenience class that inherits QPixmap and ensures a depth of 1. //Finally, the QPicture class is a drawing device that records and replays QPainter commands. //Because QImage is a subclass of QPaintDevice, //So you can use QPainter to draw directly on the image. //When using QPainter on QImage, you can perform drawing in a thread other than the current GUI thread. //The QImage class supports several image formats described by the Format enumeration. //These include monochrome, 8-bit, 32-bit, and alpha blend images, //These images are available in all versions of Qt 4.x. //QImage provides a set of functions that can be used to obtain various information about images. There are also several functions to convert images. //QImage objects can be passed by value because the QImage class uses implicit data sharing. //QImage objects can also be streamed and compared. //Note: if you want to load QImage objects in the static build of Qt, see the plug-in HowTo. //Warning: QImage:: format is not supported_ Indexed8 format painting on QImage. //Reading and writing image files //QImage provides several methods to load image files: //You can load the file when the QImage object is constructed, or you can load the file later using the load() or loadFromData() functions. //QImage also provides a static fromData() function //Construct a QImage from the given data. //When loading an image, the file name can refer to the actual file on disk //It can also refer to one of the embedded resources of the application //More about how to embed images and other resource files in your application's executable //Just call the save() function to save the QImage object. //A complete list of supported file formats is available through QImageReader::supportedImageFormats() //And QImageWriter::supportedImageFormats() functions. //New file formats can be added as plug-ins. //By default, Qt supports the following formats: QImage createImage(int width, int height) { QImage image(width, height, QImage::Format_RGB16); //QPainter provides highly optimized functions to complete the work required by most drawing GUI programs. //It can draw everything from simple lines to complex shapes such as pies and chords. //It can also draw aligned text and pixel maps. //Typically, it is drawn in a "natural" coordinate system, but it can also perform view and world transformations. //QPainter can operate on any object that inherits the QPaintDevice class. //The common use of QPainter is in widget drawing events: //Construct and customize painters, such as setting up pens or brushes. //Then draw. Remember to destroy the QPainter object after drawing. For example: ```cpp void SimpleExampleWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setPen(Qt::blue); painter.setFont(QFont("Arial",30)); painter.drawText(rect(), Qt::AlignCenter,"Qt"); } ``` //The core function of QPainter is drawing //However, this class also provides several functions //Allows you to customize the settings of QPainter and its rendering quality //And other clip enabled features. //In addition, you can control how different shapes are merged by specifying the painter's synthesis mode. //The core function of QPainter is drawing //However, this class also provides several functions //Allows you to customize the settings of QPainter and its rendering quality //And other clip - enabled features //In addition, you can control how different shapes are combined by specifying the painter's composition mode. //The isActive() function indicates whether the painter is active //The painter is activated by the begin() function and the constructor that accepts the QPaintDevice parameter //The end() function and the destructor deactivate it. //Together with QPaintDevice and QPaintEngine classes //QPainter forms the basis of Qt painting system. //QPainter is a class used to perform drawing operations. //QPaintDevice represents a device that can be drawn using QPainter. //QPaintEngine provides interfaces for painters to draw different types of devices. //If the painter is active, device() returns the painting device that the painter paints //paintEngine() returns the painting engine that the artist is currently working on. For more information, see drawing system. //Sometimes I want others to paint on an unusual QPaintDevice //QPainter supports a static function to do this, setRedirected(). QPainter painter; QPen pen; pen.setStyle(Qt::NoPen); QBrush brush(Qt::blue); //Start drawing the drawing device, and return true if successful; Otherwise, false is returned. //Note that when begin() is called, all painter settings (setPen(), setBrush(), and so on) are reset to the default values. //Possible errors are serious problems, such as: ```cpp painter->begin(0); //impossible - paint device cannot be 0 QPixmap image(0,0); painter->begin(&image); //impossible - image.isNull() == true painter->begin(myWidget); painter2->begin(myWidget); //impossible - only one painter at a time ``` painter.begin(&image); //void QPainter::fillRect(const QRectF &rectangle, const QBrush &brush) //Fills the given rectangle with the specified brush. //Alternatively, you can specify QColor instead of QBrush; //The QBrush constructor (with the QColor parameter) will automatically create a pure pattern brush. painter.fillRect(image.rect(), brush); brush.setColor(Qt::white); painter.setPen(pen); painter.setBrush(brush); //A point is specified by the x and y coordinates and can be accessed using the x() and y() functions. //For accuracy, use a finite floating point number to specify the coordinates of the point. //If x and y are both set to 0.0, the isNull() function returns true. //You can set (or change) the coordinates using the setX() and setY() functions //Or use the rx() and ry() functions that return coordinate references (allowing direct operations). //Given a point p, the following statements are equivalent: ```cpp QPointF p; p.setX(p.x() + 1.0); p += QPointF(1.0,0.0); p.rx()++; ``` //QPointF objects can also be used as vectors: //Addition and subtraction are defined the same as vectors (each component is added separately). //QPointF objects can also be divided by or multiplied by int or qreal. //In addition, the QPointF class provides a way to convert a QPointF object to QPointF //Object Constructors //And the corresponding toPoint() function, which returns a QPoint copy of the point. //Finally, QPointF objects can be streamed and compared. static const QPointF points1[3] = { QPointF(4, 4), QPointF(7, 4), QPointF(5.5, 1) }; static const QPointF points2[3] = { QPointF(1, 4), QPointF(7, 4), QPointF(10, 10) }; static const QPointF points3[3] = { QPointF(4, 4), QPointF(10, 4), QPointF(1, 10) }; //Painting equipment is an abstraction of two-dimensional space //You can draw with QPainter. //The origin of its default coordinate system is located in the upper left corner. //X increases to the right and Y increases downward. The unit is one pixel. //The drawing function of QPaintDevice is currently composed of QWidget, QImage //Implementation of subclasses QPixmap, QPicture and QPrinter. //To implement support for the new backend //You must derive from QPaintDevice and re implement the virtual paintEngine() function //To tell QPainter which drawing engine should be used to draw on this particular device. //Note that you must also create the appropriate drawing engine to draw on the device //That is, derive from QPaintEngine and re implement its virtual function. //Warning: Qt requires the existence of a QGuiApplication object before creating any drawing devices. //Draw device access window system resources //These resources are not initialized until the application object is created. //The QPaintDevice class provides several functions that return various device indicators //The depth() function returns its bit depth (the number of bit planes). //The height() function returns its height in default coordinate system units, such as the pixels of QPixmap and QWidget //Highmm () returns the height of the device in millimeters. //Similarly, the width() and widthMM() functions return the width of the device in default coordinate system units and millimeters, respectively. //Alternatively, the protected metric() function can be used to retrieve metric information by specifying the required PaintDeviceMetric as a parameter. //The logicalDpiX() and logicalDpiY() functions return the horizontal and vertical resolutions of the device in dots per inch. //The physicalDpiX() and physicalDpiY() functions also return the resolution of the device in dots per inch //Note, however, that if the logical and physical resolutions are different painter.setWindow(0, 0, 10, 10); int x = 0; int y = 0; int starWidth = image.width()/3; int starHeight = image.height()/3; QRect rect(x, y, starWidth, starHeight); for (int i = 0; i < 9; ++i) { painter.setViewport(rect); painter.drawPolygon(points1, 3); painter.drawPolygon(points2, 3); painter.drawPolygon(points3, 3); if (i % 3 == 2) { y = y + starHeight; rect.moveTop(y); x = 0; rect.moveLeft(x); } else { x = x + starWidth; rect.moveLeft(x); } } painter.end(); return image; } //! [main function] //! [main start] int main(int argc, char *argv[]) { QApplication app(argc, argv); //! [main start] //! [register meta-type for queued communications] qRegisterMetaType<Block>(); //! [register meta-type for queued communications] Window window; window.show(); window.loadImage(createImage(1024, 520)); //! [main finish] return app.exec(); } //! [main finish] //! [main function]
window.h
#ifndef WINDOW_H #define WINDOW_H #include <QWidget> #include "renderthread.h" QT_BEGIN_NAMESPACE class QLabel; class QPushButton; QT_END_NAMESPACE //! [Window class definition] class Window : public QWidget { Q_OBJECT public: Window(QWidget *parent = nullptr); void loadImage(const QImage &image); public slots: void addBlock(const Block &block); private slots: void loadImage(); void resetUi(); private: QLabel *label; QPixmap pixmap; QPushButton *loadButton; QPushButton *resetButton; QString path; RenderThread *thread; }; //! [Window class definition] #endif
#include "window.h" #include <QtWidgets> //! [Window constructor start] Window::Window(QWidget *parent) : QWidget(parent), thread(new RenderThread(this)) { //! [Window constructor start] //! [set up widgets and connections] label = new QLabel(this); label->setAlignment(Qt::AlignCenter); loadButton = new QPushButton(tr("&Load image..."), this); resetButton = new QPushButton(tr("&Stop"), this); resetButton->setEnabled(false); connect(loadButton, &QPushButton::clicked, this, QOverload<>::of(&Window::loadImage)); connect(resetButton, &QPushButton::clicked, thread, &RenderThread::stopProcess); connect(thread, &RenderThread::finished, this, &Window::resetUi); //! [set up widgets and connections] //! [connecting signal with custom type] connect(thread, &RenderThread::sendBlock, this, &Window::addBlock); //! [connecting signal with custom type] QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); buttonLayout->addWidget(loadButton); buttonLayout->addWidget(resetButton); buttonLayout->addStretch(); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(label); layout->addLayout(buttonLayout); //! [Window constructor finish] setWindowTitle(tr("Queued Custom Type")); } //! [Window constructor finish] void Window::loadImage() { QStringList formats; const QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats(); for (const QByteArray &format : supportedFormats) if (format.toLower() == format) formats.append(QLatin1String("*.") + QString::fromLatin1(format)); QString newPath = QFileDialog::getOpenFileName(this, tr("Open Image"), path, tr("Image files (%1)").arg(formats.join(' '))); if (newPath.isEmpty()) return; QImage image(newPath); if (!image.isNull()) { loadImage(image); path = newPath; } } void Window::loadImage(const QImage &image) { QImage useImage; QRect space = QGuiApplication::primaryScreen()->availableGeometry(); if (image.width() > 0.75*space.width() || image.height() > 0.75*space.height()) useImage = image.scaled(0.75*space.width(), 0.75*space.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation); else useImage = image; pixmap = QPixmap(useImage.width(), useImage.height()); pixmap.fill(qRgb(255, 255, 255)); label->setPixmap(pixmap); loadButton->setEnabled(false); resetButton->setEnabled(true); thread->processImage(useImage); } //! [Adding blocks to the display] void Window::addBlock(const Block &block) { QColor color = block.color(); color.setAlpha(64); QPainter painter; painter.begin(&pixmap); painter.fillRect(block.rect(), color); painter.end(); label->setPixmap(pixmap); } //! [Adding blocks to the display] void Window::resetUi() { loadButton->setEnabled(true); resetButton->setEnabled(false); }
renderthread.h
#ifndef RENDERTHREAD_H #define RENDERTHREAD_H #include <QImage> #include <QMutex> #include <QThread> #include "block.h" //A control thread in the QThread object manager //QThreads starts executing in run() //By default, run() starts the event loop by calling exec() and runs the Qt event loop within the thread. //You can use them by moving work objects to threads using QObject::moveToThread(). //Note: if QObject has no thread Association (that is, if thread() returns zero), or //If it is in a thread that does not run an event loop, it cannot receive queued signals or published events. //By default, QObject exists in the thread that created it. //You can use thread() to query the thread Association of an object and use moveToThread() to make changes. /* class Worker : public QObject { Q_OBJECT public slots: void doWork(const QString ¶meter) { QString result; emit resultReady(result); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workThread); connect(&workerThread,&QThread::finished,worker,&QObject::deleteLater); connect(this,&Controller::operate,worker,&Worker::doWork); connect(worker,&Worker::resultReady,this,&Controller::handleResults); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } public slots: void handleResults(const QString &); signals: void operate(const QString& ); }; */ //The code in the Worker slot will be executed in a separate thread. //However, you are free to connect the Worker's slot to any object //Any signal in any thread. //Because of a mechanism called queued connection, it is safe to connect signals and slots across different threads. //The code in the Worker slot will be executed in a separate thread //However, you are free to connect the Worker's slot to any object //Any signal in any thread. //Because of a mechanism called queued connection, it is safe to connect signals and slots across different threads. //Another way to make code run in a separate thread is to inherit QThread and re implement run(). for example /* class WorkerThread : public QThread { Q_OBJECT void run() override { QString result; //here is the expensive or blocking operation emit resultReady(result); } signals: void resultReady(const QString &s); }; void MyObject::startWorkInAThread() { WorkerThread *workerThread = new WorkerThread(this); connect(workerThread, &WorkerThread::resultReady,this,&MyObject::handleResults); connect(workerThread, &WorkerThread::finished,workerThread, &Object::deleteLater); workerThread->start(); } */ //In this example, the thread will exit after the run function returns. //No event loop will run in the thread unless you call exec(). //It is important to remember that the QThread instance exists in the old thread that instantiated it //Instead of a new thread that calls run(). //This means that all QThread queuing slots and called methods will be executed in the old thread. //Therefore, the developer who wants to call the slot in the new thread must use the working object method. //The new slot should not be implemented directly in the subclass QThread. //Unlike queued slots or calling methods, //The method called directly on the QThread object will be executed in the thread calling the method. //When inheriting QThread, remember that the constructor executes in the old thread and run() executes in the new thread. //If a member variable is accessed from two functions, it is accessed from two different threads. //Check whether it is safe to do so. //Note: you must be careful when interacting with objects across different threads. //As a general rule, unless otherwise stated in the document //Otherwise, the function can only be called from the thread that created the QThread object itself (for example, setPriority()). //For more information, see synchronizing threads. //Management thread //QThread signals you when the thread starts () and finishes () //Alternatively, you can use is Finished() and is Running() to query the status of threads. //You can stop the thread by calling exit() or quit(). //In extreme cases, you might want to force the thread () executing to terminate. //However, it is dangerous and frustrating to do so. //Please read the documentation for terminate() and setTerminationEnabled() for details. //Starting with Qt 4.8, you can connect the finished() signal to //QObject::deleteLater() to release the object in the thread that just ended. //Use wait() to block the calling thread until another thread completes execution (or until the specified time has elapsed) //QThread also provides static and platform independent sleep functions: sleep() //msleep() and usleep() allow full second, millisecond, and microsecond resolutions, respectively. These functions are exposed in Qt 5.0. //Note: in general, the wait() and sleep() functions should not be necessary //Because Qt is an event driven framework. //Instead of wait(), consider listening for the finished() signal. //Consider using QTimer instead of the sleep() function. //The static functions currentThreadId() and currentThread() return the identifier of the thread currently executing //The former returns the platform specific ID of the thread; The latter returns a QThread pointer. //To select the name of your thread (for example, identified by the command ps -L on Linux) //You can call setObjectName() before starting the thread. //If you do not call setObjectName() //The name specified for your thread will be the class name of the runtime type of your thread object //(for example, RenderThread in the Mandelbrot example, because this is a subclass of QThread) //Note that this does not currently apply to releases on Windows. //To select the name of your thread (for example, identified by the command ps -L on Linux) //You can call setObjectName() before starting the thread. //If you do not call setObjectName(), the name specified for your thread will be the class name of the runtime type of your thread object //! [RenderThread class definition] class RenderThread : public QThread { Q_OBJECT public: RenderThread(QObject *parent = nullptr); ~RenderThread(); void processImage(const QImage &image); signals: void sendBlock(const Block &block); public slots: void stopProcess(); protected: void run(); private: bool m_abort; QImage m_image; QMutex mutex; }; //! [RenderThread class definition] #endif
renderthread.cpp
#include "renderthread.h" #include <QRandomGenerator> RenderThread::RenderThread(QObject *parent) : QThread(parent) { m_abort = false; } RenderThread::~RenderThread() { mutex.lock(); m_abort = true; mutex.unlock(); wait(); } //![processing the image (start)] void RenderThread::processImage(const QImage &image) { if (image.isNull()) return; m_image = image; m_abort = false; start(); } void RenderThread::run() { int size = qMax(m_image.width()/20, m_image.height()/20); for (int s = size; s > 0; --s) { for (int c = 0; c < 400; ++c) { //![processing the image (start)] int x1 = qMax(0, QRandomGenerator::global()->bounded(m_image.width()) - s/2); int x2 = qMin(x1 + s/2 + 1, m_image.width()); int y1 = qMax(0, QRandomGenerator::global()->bounded(m_image.height()) - s/2); int y2 = qMin(y1 + s/2 + 1, m_image.height()); int n = 0; int red = 0; int green = 0; int blue = 0; for (int i = y1; i < y2; ++i) { for (int j = x1; j < x2; ++j) { QRgb pixel = m_image.pixel(j, i); red += qRed(pixel); green += qGreen(pixel); blue += qBlue(pixel); n += 1; } } //![processing the image (finish)] Block block(QRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1), QColor(red/n, green/n, blue/n)); emit sendBlock(block); if (m_abort) return; msleep(10); } } } //![processing the image (finish)] void RenderThread::stopProcess() { mutex.lock(); m_abort = true; mutex.unlock(); }
block.h
#ifndef BLOCK_H #define BLOCK_H #include <QColor> #include <QMetaType> #include <QRect> //! [custom type definition and meta-type declaration] class Block { public: Block(); Block(const Block &other); ~Block(); Block(const QRect &rect, const QColor &color); QColor color() const; QRect rect() const; private: QRect m_rect; QColor m_color; }; Q_DECLARE_METATYPE(Block); //! [custom type definition and meta-type declaration] #endif
block.cpp
#include "block.h" Block::Block() { } Block::Block(const Block &other) : m_rect(other.m_rect), m_color(other.m_color) { } Block::~Block() { } Block::Block(const QRect &rect, const QColor &color) : m_rect(rect), m_color(color) { } QColor Block::color() const { return m_color; } QRect Block::rect() const { return m_rect; }