GUIs con Haskell en entornos Windows (II)

10 marzo 2020 0 Por Juan Martin

Creando una Ventana, con algo de funcionalidad

Creamos una ventana, via codigo, con algunos componentes y manejamos algunos eventos

En la anterior entrada sobre el tema (Ver Aqui) vimos la estructura basica sobre como crear una ventana con Haskell y GTK3. En el presente vamos a profundizar acerca del tema y veremos como responder a eventos.


 main :: IO ()
    main = do
        -- inicializamos Gtk
        Gtk.init Nothing

        -- creamos una ventana y asignamos el proceso de cierre a la destruccion de la ventana
        win <- createWindow "Layouts Sample"
        on win #destroy Gtk.mainQuit

        -- creamos los widgets y los anadimos a la ventana
        box <- createWidgets win
        #add win box
        -- mostramos la ventana
        #showAll win

        -- Gtk main loop
        Gtk.main

Como ya hicimos en el anterior, inicializamos Gtk con init y procedemos a crear una ventana, para lo que nos hemos creado una funcion, createWindow a la que le pasamos el titulo que va tener la ventana en la barra de titulo.


-- creamos la ventana
createWindow ::  MonadIO m => Text -> m Gtk.Window
createWindow title = do
   win <- new Gtk.Window [ #title := title ]
   liftIO $ return win

Como vimos en la anterior entrada la creamos a traves de la funcion new a la que le pasamos el widget que queremos crear, en este caso Window.

La funcion on a traves de la cual se declaran los eventos reside en el modulo Data.GI.Base.Signals y tiene la siguiente firma


on :: forall object info m. (GObject object, MonadIO m, SignalInfo info) => object -> SignalProxy object info -> HaskellCallbackType info -> m SignalHandlerId

Ademas de esta, existe la funcion after que encola el evento para ejecutarse despues el manejador por defecto y que tiene la misma firma.

La documentacion especifica que su uso basico se realiza del modo que ya hemos visto


on #evento $ do ...

Es posible tener una funcion para realizar el manejo del evento


handleEvent :: IO ()
handleEvent = do
    
    return ()

on boton #clicked handleEvent

Para la creacion de widgets hemos creado una funcion a la que le pasamos un parametro del tipo Window con el unico proposito de dar el foco al boton Aceptar


-- creamos componentes
    createWidgets :: MonadIO m => Gtk.Window -> m Gtk.Box
    createWidgets win = do
        -- main box
        box <- createBox Enums.OrientationVertical 10

        -- box para botones
        boxbuttons <- createBox Enums.OrientationHorizontal 10
        -- botones
        aceptar <- new Gtk.Button [ #name := "btn_aceptar", #label := "Aceptar", #margin := 10 ]
        cancelar <- new Gtk.Button [ #name := "btn_cancelar", #label := "Cancelar", #margin := 10 ]
        -- box para componentes
        boxcompo <- createBox Enums.OrientationVertical 5
        label <- new Gtk.Label [ #label := "Sample Text"]
        textbox <- new Gtk.Entry [ #marginLeft := 10, #marginRight := 10, #placeholderText := "Teclee algo y pulse aceptar..."]

        -- Manejamos el evento clicked del boton aceptar
        on aceptar #clicked $ do
            -- Leemos el contenido del textbox para mostrarlo en la msgbox
            ebuffer <- Gtk.entryGetBuffer textbox
            text <- Gtk.entryBufferGetText ebuffer
            -- Creamos una caja de mensajes
            msg <- new Gtk.MessageDialog [ #text := text, #buttons := Enums.ButtonsTypeOk ]
            Dlg.dialogRun msg       -- la mostramos ...
            Gtk.windowClose msg     -- ... y la cerramos al hacer click en Ok
            return ()

        on cancelar #clicked $ do
            -- Eliminamos el contenido del textbox
            ebuffer <- Gtk.entryGetBuffer textbox
            Gtk.entryBufferSetText ebuffer "" (-1)
            return ()
    

        -- anadimos los botones
        #add boxbuttons aceptar
        #add boxbuttons cancelar
        -- anadimos el resto de widgets
        #add boxcompo label
        #add boxcompo textbox
        -- anadimos las cajas a la caja principal
        #add box boxcompo
        #add box boxbuttons
        
        Gtk.windowSetFocus win $ Just aceptar

        liftIO $ return box

Por aqui vemos que pasan muchas cosas. Por un lado llamamos a una funcion para crear las cajas que contendran los componentes dentro de la ventana. Estas cajas son muy similares a los div del HTML y podemos crearlas de varios tipos: Horizontales, Verticales, en Grid. Aqui he optado por crear una funcion que recibe su orientacion y el margen que las va a separar


-- creamos un Box
    createBox :: MonadIO m => Enums.Orientation -> Int32 -> m Gtk.Box
    createBox orientation spacing = do
        box <-  if orientation == Enums.OrientationHorizontal then
                    new Gtk.Box [ #orientation := orientation, #spacing := spacing, #hexpand := True ]
                else
                    new Gtk.Box [ #orientation := orientation, #spacing := spacing, #vexpand := True, #hexpand := True ]
            
        liftIO $ return box   

Lo unico de especial que tiene es que si la orientacion es Horizontal queremos que se expandan los componentes a todo el ancho de la caja.

Despues creamos los widgets, por un lado los botones y por otro lado una etiqueta de texto y una caja de texto.
A los botones de Aceptar y Cancelar les hemos dado algo de funcionalidad. Cuando pinchemos en Aceptar nos saldra un dialogo mostrandonos el texto insertado en la caja de texto y un boton para salir.
Cuando pichemos en Cancelar se borrara lo que hayamos insertado en la caja de texto.


        -- Manejamos el evento clicked del boton aceptar
        on aceptar #clicked $ do
            -- Leemos el contenido del textbox para mostrarlo en la msgbox
            ebuffer <- Gtk.entryGetBuffer textbox                            -- Se ha hecho asi por claridad
            text <- Gtk.entryBufferGetText ebuffer                           -- Tambien vale text <- Gtk.entryBufferGetText $ Gtk.entryGetBuffer textbox
            -- Creamos una caja de mensajes
            msg <- new Gtk.MessageDialog [ #text := text, #buttons := Enums.ButtonsTypeOk ]
            Dlg.dialogRun msg       -- la mostramos ...
            Gtk.windowClose msg     -- ... y la cerramos al hacer click en Ok
            return ()

Para leer el contenido de la caja de texto necesitamos, primero acceder a su buffer, cosa que hacemos a traves de la funcion entryGetBuffer, y, segundo, acceder al texto del buffer a traves de entryBufferGetText.
Despues creamos el dialogo, le asigamos propiedades y lo mostramos.

El evento para el boton Cancelar es similar


        on cancelar #clicked $ do
            -- Eliminamos el contenido del textbox
            ebuffer <- Gtk.entryGetBuffer textbox
            Gtk.entryBufferSetText ebuffer "" (-1)
            return ()

Por ultimo anadimos los componentes a sus cajas y las cajas individuales a la caja principal, asignando el foco al boton Aceptar.

Conclusiones y proximos pasos

Para pequenos dialogos la opcion de crearlo todo a traves de codigo puede resultar la opcion mas comoda.
En la proxima entrega veremos como cargar componentes a traves disenados por GtkBuilder.

El ejemplo lo podeis encontrar en https://github.com/juanan-martin-lpz/gtklayoutsblog.

Un saludo y hasta la proxima.