Communicating with the GUI Thread

When a Qt application starts, only one thread is runningthe initial thread. This is the only thread that is allowed to create the QApplication object and call exec() on it. For this reason, we normally refer to this thread as the GUI thread. After the call to exec(), this thread is either waiting for an event or processing an event.

The GUI thread can start new threads by creating objects of a QThread subclass, as we did in the previous section. If these new threads need to communicate among themselves, they can use shared variables together with mutexes, semaphores, or wait conditions. But none of these techniques can be used to communicate with the GUI thread, since they would lock the event loop and freeze the user interface.

The solution for communicating from a non-GUI thread to the GUI thread is to use custom events. Qt's event mechanism allows us to define custom event types in addition to the built-in types, and allows us to post events of these types using QApplication::postEvent(). Furthermore, since postEvent() is thread-safe, we can use it from any thread to post events to the GUI thread.

Figure 17.3. The Image Pro application

graphics/17fig03.jpg

To illustrate how this works, we will review the code of the Image Pro application, a basic image processing application that allows the user to rotate, resize, and change the color depth of an image. The application uses one non-GUI thread to perform operations on images without locking the event loop. This makes a significant difference when processing large images. The non-GUI thread has a list of tasks, or "transactions", to accomplish, and sends events to the main window to report progress.

ImageWindow::ImageWindow(QWidget *parent, const char *name)
 : QMainWindow(parent, name)
{
 thread.setTargetWidget(this);
 ...
}

In the ImageWindow constructor, we set the "target widget" of the non-GUI thread to be the ImageWindow. The thread will post progress events to that widget. The thread variable is of type TransactionThread, which we will cover in a moment.

void ImageWindow::flipHorizontally()
{
 addTransaction(new FlipTransaction(Horizontal));
}

The flipHorizontally() slot creates a "flip" transaction and registers it using the private function addTransaction(). The flipVertical(), resizeImage(), convertTo32Bit(), convertTo8Bit(), and convertTo1Bit() functions are similar.

void ImageWindow::addTransaction(Transaction *transact)
{
 thread.addTransaction(transact);
 openAct->setEnabled(false);
 saveAct->setEnabled(false);
 saveAsAct->setEnabled(false);
}

The addTransaction() function adds a transaction to the non-GUI thread's transaction queue and disables the Open, Save, and Save As actions while transactions are being processed.

void ImageWindow::customEvent(QCustomEvent *event)
{
 if ((int)event->type() == TransactionStart) {
 TransactionStartEvent *startEvent =
 (TransactionStartEvent *)event;
 infoLabel->setText(startEvent->message);
 } else if ((int)event->type() == AllTransactionsDone) {
 openAct->setEnabled(true);
 saveAct->setEnabled(true);
 saveAsAct->setEnabled(true);
 imageLabel->setPixmap(QPixmap(thread.image()));
 infoLabel->setText(tr("Ready"));
 modLabel->setText(tr("MOD"));
 modified = true;
 statusBar()->message(tr("Done"), 2000);
 } else {
 QMainWindow::customEvent(event);
 }
}

The customEvent() function is reimplemented from QObject to handle custom events. The TransactionStart and AllTransactionsDone constants are defined in transactionthread.h as

enum { TransactionStart = 1001, AllTransactionsDone = 1002 };

Qt's built-in events have values below 1000. Higher values can be used for custom events.

The data type for custom events is QCustomEvent, a QEvent subclass that stores a void pointer in addition to the event type. For TransactionStart events, we use a QCustomEvent subclass that stores an additional data member:

class TransactionStartEvent : public QCustomEvent
{
public:
 TransactionStartEvent();

 QString message;
};

TransactionStartEvent::TransactionStartEvent()
 : QCustomEvent(TransactionStart)
{
}

In the constructor, we pass the TransactionStart constant to the base class constructor.

Now, let's turn to the TransactionThread class:

class TransactionThread : public QThread
{
public:
 void run();
 void setTargetWidget(QWidget *widget);
 void addTransaction(Transaction *transact);
 void setImage(const QImage &image);
 QImage image();

private:
 QWidget *targetWidget;
 QMutex mutex;
 QImage currentImage;
 std::list transactions;
};

The TransactionThread class maintains a list of transactions to process and executes them one after the other in the background.

void TransactionThread::addTransaction(Transaction *transact)
{
 QMutexLocker locker(&mutex);
 transactions.push_back(transact);
 if (!running())
 start();
}

The addTransaction() function adds a transaction to the transaction queue and starts the transaction thread if it isn't already running.

void TransactionThread::run()
{
 Transaction *transact;

 for (;;) {
 mutex.lock();
 if (transactions.empty()) {
 mutex.unlock();
 break;
 }
 QImage oldImage = currentImage;
 transact = *transactions.begin();
 transactions.pop_front();
 mutex.unlock();

 TransactionStartEvent *event = new TransactionStartEvent;
 event->message = transact->messageStr();
 QApplication::postEvent(targetWidget, event);

 QImage newImage = transact->apply(oldImage);
 delete transact;

 mutex.lock();
 currentImage = newImage;
 mutex.unlock();
 }
 QApplication::postEvent(targetWidget,
 new QCustomEvent(AllTransactionsDone));
}

The run() function goes through the transaction queue and executes each transaction in turn (by calling apply() on them). All accesses to the transactions and currentImage member variables are protected with a mutex.

When a transaction is started, we post a TransactionStart event to the target widget (the ImageWindow). When all the transactions have finished processing, we post an AllTransactionsDone event.

class Transaction
{
public:
 virtual QImage apply(const QImage &image) = 0;
 virtual QString messageStr() = 0;
};

The Transaction class is an abstract base class for operations that the user can perform on an image. It has three concrete subclasses: FlipTransaction, ResizeTransaction, and ConvertDepthTransaction. We will only review FlipTransaction; the other two classes are similar.

class FlipTransaction : public Transaction
{
public:
 FlipTransaction(Qt::Orientation orient);

 QImage apply(const QImage &image);
 QString messageStr();

private:
 Qt::Orientation orientation;
};

The FlipTransaction constructor takes one parameter that specifies the orientation of the flip (Horizontal or Vertical).

QImage FlipTransaction::apply(const QImage &image)
{
 return image.mirror(orientation == Qt::Horizontal,
 orientation == Qt::Vertical);
}

The apply() function calls QImage::mirror() on the QImage it receives as parameter and returns the resulting QImage.

QString FlipTransaction::messageStr()
{
 if (orientation == Qt::Horizontal)
 return QObject::tr("Flipping image horizontally...");
 else
 return QObject::tr("Flipping image vertically...");
}

The messageStr() returns the message to display in the status bar while the operation is in progress. This function is called in ImageWindow::customEvent(), in the GUI thread.

For long-running operations, it might be desirable to report fine-grained progress. We can achieve this by creating an additional custom event and posting it when a certain percentage of the processing is completed.





C++ GUI Programming with Qt 3
C++ GUI Programming with Qt 3
ISBN: 0131240722
EAN: 2147483647
Year: 2006
Pages: 140
Simiral book on Amazon

Flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net