A random note on programming with Gtk2Hs.

Written by Jeff Heard on January 19th, 2009

Gtk has quite a few conventions that are entirely unintuitive and make for some very interesting bugs in your app if you’re not aware of them.  For instance, calling a redraw function inside an event handler is wrong.  You wouldn’t know that, because no-one really says it anywhere, but let’s say you have a Gtk window that holds an OpenGL scene or a Cairo scene.  If you call your render function at the end of a Gtk mouse button handler, then you stand a small chance, especially if your app is multithreaded of the whole thing coming crashing down with an unintelligible error.  Instead, you should so something like this:

mouseMotionHandler :: UIState a => MVar a -> Gtk.Event -> IO Bool
mouseMotionHandler uistate_mvar event = do
    state <- takeMVar uistate_mvar
    putMVar uistate_mvar
        $ setMousePosition (Point (Gtk.eventX event) (Gtk.eventY event)) state
    dwin <- Gtk.widgetGetDrawWindow . fromJust . drawing $ state
    Gtk.drawWindowGetPointer dwin
    Gtk.drawWindowInvalidateRect dwin (Gtk.Rectangle 0 0 (round . sizeX $ state) (round . sizeY $ state)) False
    return False

The important code here is in the next to last line, Gtk.drawWindowInvalidateRect.  This code tells the Gtk main loop that the window area in dwin is dirty and that an “expose” event needs to occur.  The sizeX and sizeY of the rectangle should be the width and height of the window.  The dwin is obtained by calling Gtk.widgetGetDrawWindow on any widget with a drawing window (this is a Window, a DrawingArea, or a GLDrawingArea). Then your expose handler will look like this:

Gtk.onExpose canvas $ evt -> renderer state scene >> return True

where renderer is the name of the function that actually draws your scene on the drawing window.  Once you’ve setup your event handlers like this, you can switch to a multithreaded app, and at least your event handlers won’t cause your program to die.

2 Comments so far ↓

  1. narasim_7 says:

    Afaik, invalidate the rectangle so that GTK will initiate possible redraws is something similar to what MFC encourages to do different portions of the windo when some business logic has updated the data. Thanks for the tip.. I didn’t know that something Invalidaterect can be used in GTK.

  2. Duncan says:

    Yes, this should be stated more up-front in the Gtk+/Gtk2Hs documentation.

    It’s the right thing to do for several reasons. One, as you say, is multi-threading. But it’s also a good design from the point of view of code organisation and it’s also the only way to take advantage of the automatic double-buffering that Gtk+ provides.

    It simplifies code organisation because it means there is only one code path for redrawing. You have to be able to redraw anything and everything from the expose handler anyway simply for correctness, so why duplicate that code elsewhere too? It also means that multiple expose events get unioned together and done at the same time.

    BTW, for the case of invalidating the whole draw window rather than just a small region you can use widgetQueueDraw. Conversely if you know more precisely what area needs redrawing you can invalidate a (possibly non-rectangular) region using drawWindowInvalidateRegion.

    Inside the expose handler you can get at the region that needs redrawing which in some applications enables quicker redrawing by only having to consider part of the image. In the case of OpenGL that’s usually not possible, except for setting a clipping mask which Gtk+ does automagically.

Leave a Comment