4.1 El Widget Button (Botón)

Los botones normales

Ya casi hemos visto todo lo que hay que ver del widget botón. Es muy sencillo. Sin embargo hay más de una manera de crear un botón. Puedes usar las funciones buttonNewWithLabel o buttonNewWithMnemonic para crear un botón con una etiqueta, usar buttonNewFromStock para crear un botón que contenga una imagen y un texto de un "stock item" o usar buttonNew para crear un botón vacío.Es cosa tuya colocarle una etiqueta o un pixmap en este nuevo botón. Para hacer esto, crea una nueva caja y empaqueta los objetos dentro de ella usando la función boxPackStart (o boxPackEnd para empezar desde el final), que ya conoces, y después usa containerAdd para empaquetar la caja en el botón.

Las funciones buttonNewWithMnemonic y buttonNewFromStock tienen un string como primer argumento. Usa el subrayado para marcar un caracter como el "mnemonico", que funcionará como un acelerador vía teclado. Pulsando Alt y esa tecla se activará dicho botón, sin necesidad de pinchar con el ratón. En la segunda función, la cadena es un stockId, un identificador de una lista de imágenes predefinidas con etiquetas.

Aquí tenemos un ejemplo de uso de buttonNew para crear un botón con una imagen y una etiqueta.

Button with image

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI
  window <- windowNew
  set window [windowTitle := "Pix",
              containerBorderWidth := 10]
  button <- buttonNew
  onClicked button (putStrLn "button clicked")
  box    <- labelBox "info.xpm" "cool button"
  containerAdd button box
  containerAdd window button
  widgetShowAll window
  onDestroy window mainQuit
  mainGUI

labelBox :: FilePath -> String -> IO HBox
labelBox fn txt = do
  box   <- hBoxNew False 0
  set box [containerBorderWidth := 2]
  image <- imageNewFromFile fn
  label <- labelNew (Just txt)
  boxPackStart box image PackNatural 3
  boxPackStart box label PackNatural 3
  return box

La función labelBox se puede usar para meter imágenes y etiquetas en cualquier widget que sea simultáneamente un contenedor (container). La imagen procede de un fichero, usando imageNewFromFile y la etiqueta viene de labelNew, que utiliza un Maybe String como argumento. Nothing indica que no hay etiqueta.

El widget Button tiene las siguientes señales básicas, que casi se autoexplican (si sabes inglés claro!):

Nota: Normalmente el dispositivo señalador es el ratón, por eso lo indico. Para cualquier dispositivo señalador, el funcionamiento sería el mismo, así ratón = dispositivo señalador.

Botones Toggle (Alternativos)

Los botones Toggle se derivan de los botones normales y son muy parecidos. Su única diferencia estriba en que siempre alternan entre dos estados. La alternancia se produce al ser pulsados.

Los botones Toggle son la base de de los botones check y radio, por tanto muchas de las llamadas usadas por los botones toggle son heredadas por los botones check y radio. Ya lo recordaremos cuando lleguemos a ellos.

La creación de un boton toggle:

toggleButtonNew :: IO ToggleButton

toggleButtonNewWithLabel :: String -> IO Togglebutton

toggleButtonNewWithMnemonic :: String -> IO ToggleButton

Como puedes imaginar, funcionan exactamente igual que con los widget botón. El primero crea un botón toggle vacío, y los dos últimos, un botón con un widget de tipo etiqueta(label) ya empaquetado con él. La variante "mnemonica" analiza la etiqueta buscando el caracter prefijado con un "_".

Para conocer el estado del botón toggle (pulsado o no pulsado), incluyendo los botones radio y check, se emplea:

toggleButtonGetActive :: ToggleButtonClass self => self -> IO Bool

Devuelve True si el botón toggle está pulsado y False si no lo está.

Para forzar el estado de un botón toggle, o sus hijos, los botones radio y check, debes emplear:

toggleButtonSetActive :: ToggleButtonClass self => self -> Bool -> IO ()

La llamada anterior se puede usar para establecer el estado del botón toggle, y sus hijos los botones radio y check. Hay que pasarle el botón creado como primer argumento y True o False como segundo argumento, para especificar el estado an el que queremos dejar el botón. El valor por defecto es "no pulsado" o False.

Fíjate que cuando usas la función toggleButtonSetActive, y se cambia el estado, origina que se emitan las señales onClicked y onToggled del botón.

Botones Check

Los botones Check heredan muchas propiedades y funciones de los botones toggle, bero su apariencia es diferente. Más que ser botones con un texto dentro de ellos, son pequeños cuadrados con el texto a su derecha. A menudo se usan para marcar las opciones de una aplicación como activadas o desactivadas.

Las funciones de creación son las mismas que las de los botones normales.

checkButtonNew :: IO CheckButton

checkButtonNewWithLabel :: String -> IO Checkbutton

checkButtonNewWithMnemonic :: String -> IO CheckButton

La función checkButtonNewWithLabel crea un botón check con una etiqueta junto a él.

CheckButton es una instancia de ToggleButtonClass y la señal onToggled se usa cuando un CheckButton se marca o se desmarca, como en el botón toggle.

Botones Radio

Los botones Radio son parecidos a los botones check excepto que están agrupados de modo que sólo uno puede seleccionarse en cada momento. Esto es útil para los lugares del programa donde se debe seleccionar una opción de entre varias posibles. Para crear un boton radio utilizo una de estas funciones:

radioButtonNew :: IO RadioButton

radioButtonNewWithLabel :: String -> IO RadioButton

radioButtonNewWithMnemonic :: String -> IO RadioButton

radioButtonNewFromWidget :: RadioButton -> IO RadioButton

radioButtonNewWithLabelFromWidget :: RadioButton -> String -> IO RadioButton

radioButtonNewWithMnemonicFromWidget :: RadioButton -> String -> IO RadioButton

Como has visto, las últimas tres funciones tienen un argumento extra. Se usa para unir los botones nuevos a los ya construidos en un grupo.

Es una buena opción seleccionar el botón que presenta el valor por defecto con:

toggleButtonSetActive :: ToggleButtonClass self => self -> Bool -> IO ()

Esta función se describió en los botones toggle, y funciona exactamente del mismo modo. Una vez que los botones radio se agrupan, sólo uno del grupo puede estar activo en un momento determinado. Si el usuario pulsa en uno de los botones, y después en otro, el primer botón radio emitirá en primer lugar una señal onToggled (para indicar el cambio a inactivo), y después el segundo emitirá su señal onToggled (para indicar su cambio a activo).

El siguiente ejemplo crea un grupo de botones radio con tres botones, y cuando el usuario pulsa uno de los botones radio, el desactivado y el activado lo indicarán a stdout, mediante putStrLn en la función setRadioState definida al final.

Radio buttons

import Graphics.UI.Gtk

main :: IO ()
main = do
  initGUI
  window  <- windowNew
  set window [windowTitle := "Radio Button", containerBorderWidth := 5,
              windowDefaultWidth := 200, windowDefaultHeight := 150]
  box1    <- vBoxNew False 0
  containerAdd window box1
  box2    <- vBoxNew False 10
  containerSetBorderWidth box2 10
  boxPackStart box1 box2 PackNatural 0
  button1 <- radioButtonNewWithLabel "button 1"
  boxPackStart box2 button1 PackNatural 0
  button2 <- radioButtonNewWithLabelFromWidget button1 "button 2"
  boxPackStart box2 button2 PackNatural 0
  button3 <- radioButtonNewWithLabelFromWidget button2 "button 3"
  boxPackStart box2 button3 PackNatural 0
  toggleButtonSetActive button2 True
  onToggled button1 (setRadioState button1)
  onToggled button2 (setRadioState button2)
  onToggled button3 (setRadioState button3)
  sep     <- hSeparatorNew
  boxPackStart box1 sep PackNatural 0
  box3    <- vBoxNew False 10
  containerSetBorderWidth box3 10
  boxPackStart box1 box3 PackNatural 0
  closeb  <- buttonNewWithLabel "close"
  boxPackStart box3 closeb PackNatural 0
  onClicked closeb mainQuit
  widgetShowAll window
  onDestroy window mainQuit
  mainGUI

setRadioState :: RadioButton -> IO ()
setRadioState b = do
  state <- toggleButtonGetActive b
  label <- get b buttonLabel
  putStrLn ("State " ++ label ++ " now is " ++ (show state))