2011-03-14 12 views
9

Estoy tratando de hacer una aplicación de juguete, solo para entender cómo escribir programas impulsados ​​por eventos en Haskell. Lo que trato de hacer es dibujar una línea en un lienzo que se mueve hacia adelante cada vez que se presiona una tecla (por lo que es una especie de cursor primordial en un editor de texto).Cómo pasar el estado entre los manejadores de eventos en gtk2hs

Mi problema es que no puedo determinar cuál es la mejor manera de contar la cantidad de veces que el usuario ha presionado una tecla. Obviamente no puedo usar una variable global como lo haría en un programa imperativo, así que presumiblemente necesito pasar el estado en la pila de llamadas, pero en la ejecución de GTK desciende al ciclo principal después de que devuelva cada controlador de eventos y ya que no lo hago controlar el ciclo principal No veo cómo puedo pasar el estado global modificado desde un manejador de eventos. Entonces, ¿cómo puede un manejador de eventos pasar el estado a otro manejador de eventos?

Tengo una especie de solución parcial aquí, donde el evento del teclado re-curry myDraw y lo establece como un nuevo controlador de eventos. No estoy seguro de si esta solución se puede extender, o incluso si es una buena idea.

¿Cuál es la mejor solución de partículas para este problema?

import Graphics.UI.Gtk 
import Graphics.Rendering.Cairo 

main :: IO() 
main= do 
    initGUI 
    window <- windowNew 
    set window [windowTitle := "Hello World", 
       windowDefaultWidth := 300, windowDefaultHeight := 200] 

    canvas <- drawingAreaNew 
    containerAdd window canvas 

    widgetShowAll window 
    draWin <- widgetGetDrawWindow canvas 
    canvas `on` exposeEvent $ do liftIO $ renderWithDrawable draWin (myDraw 10) 
            return False 

    window `on` keyPressEvent $ onKeyboard canvas 
    window `on` destroyEvent $ do liftIO mainQuit 
            return False 

    mainGUI 

onKeyboard :: DrawingArea -> EventM EKey Bool 
onKeyboard canvas = do 
    liftIO $ do drawWin <- widgetGetDrawWindow canvas 
       canvas `on` exposeEvent $ do liftIO $renderWithDrawable drawWin (myDraw 20) 
              return False 
       widgetQueueDraw canvas 
    return False 



myDraw :: Double -> Render() 
myDraw pos = do 
    setSourceRGB 1 1 1 
    paint 
    setSourceRGB 0 0 0 

    moveTo pos 0 
    lineTo pos 20 
    stroke 

Respuesta

7

Primero, podrías tener un global. Ignorando esa solución como mala forma, parece un trabajo para un MVar. En main que acaba de hacer un nuevo MVar que se puede actualizar en onKeyboard y comprobar en las myDraw:

... 
import Control.Concurrent.MVar 

main = do 
    ... 
    mv <- newMVar 0 
    .... 
    canvas `on` exposeEvent $ do liftIO $ renderWithDrawable draWin (myDraw mv 10) 
    ... 
    window `on` keyPressEvent $ onKeyboard canvas mv 

onKeyboard canvas mv = do 
    modifyMVar_ mv (\x -> return (x + 1)) 
    .... 

myDraw mv pos = do 
    val <- readMVar mv 
    ... 

Tenga en cuenta que compartir el estado mutable también es a menudo útil para pasar funciones como argumentos usando primero la aplicación parcial para proporcionar la MVar (o TVar, IORef, lo que sea).

Ah, y una advertencia: MVars no son estrictos: si la aplicación tiene el potencial de escribir lotes sin forzar el valor (es decir, leer y comparar el contenido Int), debe forzar el valor antes de escribir para evitar la construcción un gran golpe.

Cuestiones relacionadas