4.7 Botones aumentar/disminuir

El widget SpinButton se usa normalmente para permitir al usuario seleccionar un valor dentro de un rango de valores numéricos. Consta de una caja de entrada de texto con dos botones flecha (arriba y abajo) enganchados en el lateral. Seleccionando alguna de las flechas el valor se modifica subiendo o bajando dentro del rango especificado. La caja de entrada de texto también puede ser editada directamente para introducir un valor. SpinButton es una instancia de EditableClass, por lo que los atributos y funciones definidas allí también están disponibles.

Los botones aumentar/disminuir permiten definir un valor con cero o 'n' decimales. El incremento, (el paso), se define por el programador. Si mantenemos pulsado uno de los botones se puede originar una aceleración en el cambio del valor, en función del tiempo en que esté pulsado.

SpinButton usa un objeto de Adjustment para almacenar la información sobre el rango de valores que puede tomar. Recuerda que el widget Adjustment se crea con la siguiente función:

adjustmentNew :: Double        -- valor            - El valor inicial del rango
              -> Double        -- mínimo           - Valor mínimo del rango
              -> Double        -- máximo           - Valor máximo del rango
              -> Double        -- incrementoPaso   - El menor de dos posibles incrementos
              -> Double        -- incrementoPágina - El mayor de dos posibles incrementos
              -> Double        -- tamañoPágina     - El tamaño del área visible
              -> IO Adjustment

Los atributos del objeto Adjustment asociado al SpinButton tienen el siguiente uso:

Además, el pusador 3 del ratón se puede usar para ir directamente a los valores máximo y mínimo al seleccionar alguno de los botones. Ten en cuenta que esto puede depender de la configuración del ratón en tu sistema.

Vamos a ver como podemos crear un botón aumentar/disminuir:

spinButtonNew :: Adjustment -> Double -> Int -> IO SpinButton

El segundo argumento (climbRate) (tasa de escalado) es un valor entre 0.0 y 1.0 e indica la velocidad de cambio del botón aumentar/disminuir cuando se pulsa una tecla. El tercer argumento indica el número de posiciones decimales con que será mostrado el valor.

También hay un constructor que permite crear un botón aumentar/disminuir sin necesidad de crear un adjustment.

spinButtonNewWithRange :: Double -> Double -> Double -> IO SpinButton

Los tres argumentos, todos de tipo Double, corresponden al valor mínimo posible, al máximo, y al incremento añadido o sustraído al pulsar los botones del widget.

Una vez creado, el SpinButton se puede reconfigurar mediante la siguiente función:

spinButtonConfigure :: SpinButtonClass self => self -. Adjustment -> Double -> Int

El primer argumento indica el widget SpinButton que debe ser reconfigurado. los otros argumentos son, el climbRate y el número de decimales a mostrar.

Los atributos del SpinButton que pueden ser modificados o consultados con las funciones genéricas get y set son:

spinButtonAdjustment :: SpinButtonClass self => Attr self Adjustment
spinButtonClimbRate  :: SpinButtonClass self => Attr self Double
spinButtonDigits     :: SpinButtonClass self => Attr self Int

spinButtonSnapToTicks :: SpinButtonClass self => Attr self Bool
spinButtonNumeric     :: SpinButtonClass self => Attr self Bool
spinButtonWrap        :: SpinButtonClass self => Attr self Bool

spinButtonValue :: SpinButtonClass self => Attr self Double

Los primeros tres valores ya los hemos analizado. El atributo spinButtonSnapToTicks determina si los valores erróneos se cambian automáticamente a valores válidos del botón aumentar/disminuir (por defecto es False). El attributo spinButtonNumeric determina si los valores no-numéricos deben ser ignorados (por defecto es False), y spinButtonWrap se usa si un botón aumentar/disminuir debe funcionar de una manera circular. (por defecto es False).

El atributo spinButtonValue se usa para leer el valor actual o establecer un nuevo valor (por defecto es 0).

También puedes usar:

spinButtonSpin :: SpinButtonClass self => self -> SpinType -> Double -> IO ()

donde SpinType determina el tipo de cambio y Double (incremento) determina el valor.

SpinType tiene los siguientes constructores:

muchos de estos constructores usan los valores del objeto Adjustment que está asociado con el botón aumentar/disminuir. SpinStepForward y SpinStepBackward cambian el valor del botón aumentar/disminuir en la cantidad indicada por el incremento, a no ser que valga 0, en cuyo caso el valor será cambiado por el stepIncrement del ajuste. SpinPageForward y SpinPageBackward cambian el valor del SpinButton en el incremento. SpinPageHome y SpinPageEnd establecen el valor mínimo y máximo respectivamente del rango del Adjustment. SpinUserDefined modifica el valor del botón aumentar/disminuir en la cantidad especificada.

Los botones aumentar/disminuir tienen una política de actualizaciones:

spinButtonUpdatePolicy :: SpinButtonClass self => Attr self SpinButtonUpdatePolicy

Los constructores de SpinButtonUpdatePolicy pueden ser UdateAlways o UpdateIfValid. Estas políticas afectan al comportamiento del SpinButton al analizar (procesar) el texto insertado y sincronizar su valor con los valores del Adjustment. En el caso de UpdateIfValid (actualiza si es válido) el valor del botón aumentar/disminuir sólo se cambia si la entrada de texto es un valor numérico dentro del rango especificado en el Adjustment. Si no es así se mantiene el valor actual. En caso de UpdateAlways ignoramos los errores de conversión del "texto" en un valor numérico.

Finalmente, puedes solicitar que el propio SpinButton se actualice:

spinButtonUpdate :: SpinButtonClass self => self -> IO ()

Es el momento de volver a los ejemplos. Esta es una imagen después de juguetear con algunos de los atributos:

botón aumentar/disminuir example

Todos los botones aumentar/disminuir han sido creados con la siguiente función. Usa la función spinButtonNewWithRange. Como el stepIncrement será 1.0 en todos los casos, no es un parámetro de la función myAddSpinButton.

myAddSpinButton :: HBox -> String -> Double -> Double -> IO SpinButton
myAddSpinButton box name min max = do
    vbox  <- vBoxNew False 0
    boxPackStart box vbox PackRepel 0
    label <- labelNew (Just name)
    miscSetAlignment label 0.0 0.5
    boxPackStart vbox label PackNatural 0
    spinb <- spinButtonNewWithRange min max 1.0
    boxPackStart vbox spinb PackNatural 0
    return spinb

En la función main usamos uno de los botones aumentar/disminuir existentes, pero le damos un nuevo adjustment con spinButtonConfigure. Los límites antiguos de -1000.0 y 1000.0 se reemplazan por -100.0 y 100.0. Fíjate en los paréntesis que rodean a los números negativos. El valor inicial se establece en 0.0 y el incremento de paso es de 0.25. El incremento de página, que es el que consigues al pulsar el segundo botón del ratón en la flecha del botón aumentar/disminuir, se establece en 10.0. El tamaño de página, que no se usa, es 0 aquí. Pulsando el tercer botón del ratón en una de las flechas, salta hacia el límite correspondiente, -100.0 o 100.0.

Aquí, la nueva señal es onValueSpinned, que se emite siempre que el usuario cambie el valor de un botón aumentar/disminuir. Aquí se usa para controlar el número de dígitos decimales mostrados en el botón aumentar/disminuir spinLarge. Fíjate en el redondeo del valor, necesario para convertir el tipo Double a un tipo Integral.

En el ejemplo usamos las funciones genéricas get y set en los atributos (hay funciones específicas para ello). Es un estilo de programación recomendado en Gtk2Hs, ya que en el futuro las funciones específicas serán eliminadas.

import Graphics.UI.Gtk

main:: IO ()
main = do
    initGUI
    window  <- windowNew
    mainbox <- vBoxNew False 0
    set window [windowTitle := "Spin buttons", containerBorderWidth := 10,
                windowDefaultWidth := 250, windowDefaultHeight := 200,
                containerChild := mainbox]
    hbox1   <- hBoxNew False 0
    frame1  <- frameNew
    set frame1 [frameLabel := "Simple SpinButtons", containerChild := hbox1,
                frameLabelYAlign := 0.8, frameShadowType := ShadowOut]
    boxPackStart mainbox frame1 PackNatural 5

    spinD <- myAddSpinButton hbox1 "Day:" 1.0 31.0
    spinM <- myAddSpinButton hbox1 "Month:" 1.0 12.0
    spinY <- myAddSpinButton hbox1 "Year:" 2000.0 2100.0
    set spinY [spinButtonValue := 2007]

    vbox1  <- vBoxNew False 5
    frame2 <- frameNew
    set frame2 [frameLabel := "More Features", containerChild := vbox1,
                frameLabelYAlign := 0.8, frameShadowType:= ShadowOut]
    boxPackStart mainbox frame2 PackNatural 5

    hbox2 <- hBoxNew False 0
    boxPackStart vbox1 hbox2 PackNatural 0

    spinLarge <- myAddSpinButton hbox2 "Value:" (-1000.0) 1000.0
    adj       <- adjustmentNew 0.0 (-100.0) 100.0 0.25 10.0 0.0
    spinButtonConfigure spinLarge adj 0.0 2
    spnctl    <- myAddSpinButton hbox2 "Decimal:" 0.0 10.0
    set spnctl [spinButtonValue := 2.0]

    tsnap <- checkButtonNewWithLabel "Snap to 0.25-ticks"
    boxPackStart vbox1 tsnap PackNatural 0

    tnumr <- checkButtonNewWithLabel "Numeric only input mode"
    boxPackStart vbox1 tnumr PackNatural 0

    twrap <- checkButtonNewWithLabel "Wraparound at limits"
    boxPackStart vbox1 twrap PackNatural 0

    widgetShowAll window

    onValueSpinned spnctl $ do newdig <- get spnctl spinButtonValue
                               set spinLarge [spinButtonDigits := (round newdig)]

    onToggled tsnap $ do st <- get tsnap toggleButtonActive
                         set spinLarge [spinButtonSnapToTicks := st]

    onToggled tnumr $ do st <- get tnumr toggleButtonActive
                         set spinLarge [spinButtonNumeric := st]

    onToggled twrap $ do st <- get twrap toggleButtonActive
                         set spinLarge [spinButtonWrap := st]

    onDestroy window mainQuit
    mainGUI