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
Line 14 creates a
Behavior from that specific
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
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 is turned into a
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