haroldcarr.[com|org]

how to connect threepenny-gui to external events

Over the years I occasionally reimplemented my rdf-triple-browser, first in GWT, then Java/Swing and then last summer in Haskell using Threepenny-gui (which I will call ‘TPG’ for the rest of this article). The hardest part for me was understanding how to connect to external events.

The documentation says newEvent :: IO (Event a, Handler a) “Create a new event. Also returns a function that triggers an event occurrence.” What is doesn’t say it that calling the returned Handler causes the specific returned Event to happen.

I got lost in the documentation about Handler, going off on dead ends with register. Even the TAs at last summer’s Utrecht Haskell Summer School could not figure it out (although, to be fair, they did not spend that much time on it, they were concentrating on course questions).

Fortunately, Max Taldykin, on stackoverflow, provided a small program that lead me to discover the “magic” of how to use what is returned via newEvent. Below I show an even smaller program that shows how it is wired.

 1  module ThreepennyExternalNewEventDemo where
 2  
 3  import           Control.Concurrent     (forkIO)
 4  import           Graphics.UI.Threepenny
 5  import           Network
 6  import           System.IO              (hClose)
 7  
 8  main :: IO ()
 9  main = do
10      (eAccept, hAccept) <- newEvent
11      forkIO (acceptLoop hAccept 6789)
12      forkIO (acceptLoop hAccept 9876)
13      startGUI defaultConfig $ \win -> do
14          bAccept <- stepper "" eAccept
15          entree <- entry bAccept
16          element entree # set (attr "size") "10" # set style [("width","200px")]
17          getBody win #+ [element entree]
18          return ()
19  
20  acceptLoop :: (String -> IO a) -> PortNumber -> IO b
21  acceptLoop hAccept bindAddr = do
22      s <- listenOn $ PortNumber bindAddr
23      loop s
24    where
25      loop s = do
26          (h, hostname, portNumber) <- accept s
27          hClose h
28          hAccept $ show bindAddr ++ " " ++ hostname ++ " " ++ show portNumber
29          loop s

Line 10 uses newEvent to create an Event a and Handler a. The “magic” is that behind the scenes TPG has wired the return Handler such that when it is called it generates an Event specific to the returned Event.

Line 14 creates a Behavior from that specific Event. That Behavior is then given to the entry widget. Whenever the code calls the Handler it triggers that Event that causes the Behavior to change. The widget shows the Behavior change.

Here is the wiring for the program:

 
 

Line 11 starts two threads that accept connection at two different listening ports. Whenever a connection is accepted, the loops call the given Handler at line 28. The Handler “magically” triggers an Event. The Event is turned into a Behavior via stepper. The Behavior was previously wired to the entry widget. So, whenever a connection is accepted the text field shows the info. That’s it!

Hopefully this small example will save someone some time.

comments powered by Disqus