Wednesday 21 December 2011

Qt Jambi Event Tutorial


This tutorial / example will show you how to use QT Events (not slots-and-signals). We'll start with the following "hello world" (remember to use command line arguments -d32 -XstartOnFirstThread if you're running it on a mac):

public static void main(String[] args) {
 QApplication.initialize(args);
 QPushButton hello = new QPushButton("Hello World!");
 hello.resize(120, 40);
 hello.setWindowTitle("Hello World");
 hello.show();
 Thread.currentThread().setName("MAIN"); // let's label it for clarity
 QApplication.exec(); // jump into the event loop
}

The last line is the most relevent - it makes the main thread listen to the event queue and dispatch events to the relevent QObjects. When the event loop sends an event to a QObject it calls all the event filters that object has (see later), then invokes public boolean event(QEvent e) with the event. We'll create our own event class:

public class MyEvent extends QEvent {
 private final long time; // our "business logic"
 public MyEvent() {
  super(Type.CustomEnum);
  this.time = System.currentTimeMillis();
 }
}

The Type enum is required by the native code as a faster way of accessing the type of the object. This isn't really needed in Java code - most examples use Java's instanceof keyword instead. We'll make a thread to periodically send instances of these events to our hello-world button:

Thread t = new Thread(new Runnable() {
 public void run() {
  while (true) {
   try {
    Thread.Sleep(1000);
    QApplication.postEvent(hello, new MyEvent());
   } catch (InterruptedException e) {}
  }
 }, "event-firer");
t.setDaemon(true); // so the JVM exits when the window is closed
t.start();

Make sure you instantiate this before putting the main thread into QT's event loop! The most important line is QApplication.postEvent(hello, new MyEvent()) - this creates an event and puts it on the event loop's queue. The first argument tells the event loop which QObject to dispatch this event to. We could have used QApplication.sendEvent(hello, new MyEvent()) instead; this would still evaluate the event filters and call event, but would do so synchronously, without placing the QEvent on the event loop's queue.

Unfortunately it's not obvious if this is working. We'll add the Qt Event API's equivalent of a println statement, a filter that just prints all the events it receives:

private static void printEvents(final QObject obj) {
 obj.installEventFilter(new QObject() {
  @Override
  public boolean eventFilter(QObject o, QEvent e) {
   System.out.println(
    o + 
    ": " + 
    e + 
    " on thread " + 
    Thread.currentThread().getName());
   return super.eventFilter(o, e);
  }
 });
}
// and in the main method:
printEvents(hello);
// or instead, install a special global event filter:
printEvents(QApplication.instance());

This filter will be evaluated before the QEvent gets send to the button's event method. You should see a stream of events on the console (especially if you click the button) like the below - note that the filter is executed on the MAIN thread (the one running the event loop), not the thread that posted the event.

QPushButton(0x1dc9d0) : MyEvent 1324477542957 on thread MAIN
QPushButton(0x1dc9d0) : QEvent(type=ZOrderChange) on thread MAIN
QPushButton(0x1dc9d0) : QMouseEvent(MouseButtonPress, 1, 1, 0) on thread MAIN

If you return true from the eventFilter method you're telling the event loop that that event has been processed completely; the remaining filters and the QObject's event method won't be called. You can try it in the example: if you make the eventFilter method of the filter installed by printEvents return true you'll see that the button doesn't get painted properly (as the QPaintEvents are not getting to the QPressButton) and the button doesn't react if you press it.

The QAppication's notify method is similar to sendEvent (so must be called on the same thread as the QObject was created on), but returns true if any filters succesfully processed the event, and the return value of event on the target QObject if not.

While some QT documentation refers to QEvents being passed to parent QObjects if the QObject they are sent to returns false from event it won't work out of the box for our MyEvent class as it's a custom event type. You'll have to add that functionality yourself, either by overriding the implementation of notify or invoking the parent QObject's event method in the child's event method. You can see the native code implementation of notify here.