19 de mayo de 2016

54. GTK: señales, llamadas y botones

Señales y llamadas

Visión general (literal de la página Señales y retornos de llamada)
Al igual que la mayoría de los kits de herramientas de GUI, GTK+ usa un modelo de programación dirigida por eventos. Cuando el usuario no hace nada, GTK+ se queda en el bucle principal y espera la entrada. Si el usuario realiza alguna acción (como una pulsación del ratón), entonces el bucle principal «despierta» y le envía un evento a GTK+.
Cuando los widgets reciben un evento, frecuentemente emiten una o más señales. Las señales le notifican a su programa que «algo interesante sucedió» invocando funciones que les ha conectado. Estas funciones se conocen comúnmente como retornos de llamada. Cuando sus retornos de llamada se invoquen, típicamente realizaría alguna acción. Después de que un retorno de llamada termina, GTK+ retorna al bucle principal y espera más entradas del usuario.
Un ejemplo genérico es: id_de_manejador = widget.connect("evento", retorno_de_llamada, datos). widget es una instancia de un widget que creó anteriormente. A continuación, el evento en el que está interesado. Cada widget tiene sus propios eventos particulares que pueden ocurrir. Por ejemplo, si tiene un «Gtk.Button» generalmente querrá conectar el evento «clicked»: esto significa que cuando el botón se pulsa, la señal se envía. Otro ejemplo es la señal notify::property: cada vez que una propiedad se modifica en un «GObject», en lugar de sólo emitir la señal notify, GObject asocia como un detalle a la emisión de la señal el nombre de la propiedad modificada. Esto permite clientes que quieran notificarse de cambios de una sola propiedad para filtrar la mayoría de los eventos antes de recibirlos. Tercero, el argumento «retorno_de_llamada» es el nombre de la función de retorno de llamada, que contiene el código que se ejecuta cuando se emiten las señales del tipo especificado. Finalmente, el argumento opcional «datos» incluye cualquier dato que debe pasarse cuando se emita la señal.
La función devuelve un número (el id_de_manejador) que identifica este par «señal-retorno de llamada» particular. Este número se requiere para desconectar una señal para que la función de retorno de llamada no se llame durante emisiones de la señal a la que se ha conectado futuras o en progreso, como en widget.disconnect(id_de_manejador).
Referencias
Señales en la documentación de GObject
Conceptos básicos: bucle principal y señales en el tutorial de GTK+ en Python 3

Muchos, muchos botones...

Ejemplo con muchos botones
En el siguiente programa presentamos una pequeña funcionalidad: tres bombillas apagadas, que se pueden encender/apagar mediante una serie de botones. Simplemente, copia el código, prueba y estudia cómo se han implantado cada uno de los botones.
# coding: utf-8
from gi.repository import Gtk
import sys, random

class miventana(Gtk.ApplicationWindow):

    def __init__(self, app):
        Gtk.Window.__init__(self, title="Ejemplo con botones", application=app)
        self.set_default_size(450, 350)
    self.set_border_width(10)
 
        #2.- definir objetos separadores, y las imágenes
        sepHorizontal = [] 
    imagen = []
    chbtn = [] # checkbuttons
    for i in xrange(0,3):
        sepHorizontal.append(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
        imagen.append(Gtk.Image())
        imagen[i].set_from_file("bombilla_off.png")
        chbtn.append(Gtk.CheckButton())
        chbtn[i].set_label("Botón nº "+str(i+1))
        chbtn[i].set_active(False)
        chbtn[i].connect("toggled", self.si_check, i, imagen) # detectar señal de cambio en los checkbuttons
    
    # 3.- Tres radio buttons, conectados en grupo con new_from_widget
    rdbtn1 = Gtk.RadioButton(label="Botón nº 1")
    rdbtn1.connect("toggled",self.si_rdcheck, imagen)
    rdbtn1.set_active(False)
    rdbtn2 = Gtk.RadioButton.new_from_widget(rdbtn1)
    rdbtn2.set_label("Botón nº 2")
    rdbtn2.connect("toggled",self.si_rdcheck, imagen)
    rdbtn2.set_active(False)
    rdbtn3 = Gtk.RadioButton.new_with_label_from_widget(rdbtn1, "Botón nº 3")
    rdbtn3.connect("toggled",self.si_rdcheck, imagen)
    rdbtn3.set_active(False)
    
    # 4.- Un toggle - button para encenderlos a todos
    tglbtn = Gtk.ToggleButton(label="Enciende")
    tglbtn.set_active(False)
    tglbtn.connect("toggled",self.tglconnect, imagen)
    
    # 5.- Un interruptor, que enciende aleatoriamente una de ellas
    sw = Gtk.Switch()
    sw.connect("notify::active", self.swconnect, imagen)
    
    # 6.- Y se me olvida..., ¡un botón normal...!
    icono = Gtk.Image() # un objeto imagen nuevo
    icono.set_from_file("bombilla_on.png") # una imagen del almacén
    btn = Gtk.Button()
    btn.set_image(icono)
    # btn.set_label("Todas")
    btn.connect("clicked",self.conectar,imagen)
    
    #1.- definir objeto rejilla
        rejilla = Gtk.Grid()
        rejilla.set_column_spacing(20)
    
    #3.- definir los grid attach
    for i in xrange(0,3):
        rejilla.attach(imagen[i],i,0,1,1)
    rejilla.attach(sepHorizontal[0],0,1,3,1) # una separación
    for i in xrange(0,3):
        rejilla.attach(chbtn[i],i,2,1,1) # check buttons
    rejilla.attach(sepHorizontal[1],0,3,3,1) # una separación
    rejilla.attach(rdbtn1,0,4,1,1)
    rejilla.attach(rdbtn2,1,4,1,1)
    rejilla.attach(rdbtn3,2,4,1,1)
    rejilla.attach(sepHorizontal[2],0,5,3,1) # una separación
    rejilla.attach(tglbtn,0,6,1,1) # toggle
    rejilla.attach(sw,1,6,1,1) # switch
    rejilla.attach(btn,2,6,1,1) # botón normal
    
    rejilla.set_column_homogeneous(True) # para centrar los objetos en la columna
    rejilla.set_row_homogeneous(True) # para centrar los objetos en las filas
        self.add(rejilla)
    
    # =================================================================
    # Funciones de llamada
    # =================================================================
    
    # llamada si activan/desactivan los checkbuttons    
    def si_check(self, button, i, imagen):
    # importante, pasarle la lista imagen...
    if button.get_active():
        print "Encendido", i
        imagen[i].set_from_file("bombilla_on.png")
    else:
        print "Apagado", i
        imagen[i].set_from_file("bombilla_off.png")
        
    # llamada si activan/desactivan los radiobuttons   
    def si_rdcheck(self, button, imagen):
      if button.get_active(): # la que está activa
          valor = button.get_label()
          k = int(valor[-1])
          self.borrar(imagen) # borra el resto de imágenes
          imagen[k-1].set_from_file("bombilla_on.png")
          
    def tglconnect(self, button, imagen):
    if button.get_active():
        button.set_label("Apaga")
        self.todos(imagen)
    else:
        button.set_label("Enciende")
        self.borrar(imagen)
        
    def swconnect(self, button, active, imagen): 
    # switch button pasa ya tres atributos self, button y active
    i = random.randint(0,2)
    # print i, button.get_active()
    self.borrar(imagen)
    if button.get_active():
        imagen[i].set_from_file("bombilla_on.png")
          
    def conectar(self,button,imagen):
    self.todos(imagen)

    # =================================================================
    # Otras llamadas
    # =================================================================

    def borrar(self, imagen):
    for i in xrange(0,3):
        imagen[i].set_from_file("bombilla_off.png")

    def todos(self, imagen):
    for i in xrange(0,3):
        imagen[i].set_from_file("bombilla_on.png")

# =================================================================
# Mi aplicación
# =================================================================

class miaplicacion(Gtk.Application):

    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        win = miventana(self)
        win.show_all()

    def do_startup(self):
        Gtk.Application.do_startup(self)

app = miaplicacion()
exit_status = app.run(sys.argv)
sys.exit(exit_status)
= = =
Ampliando...
  • De la clase GtkButton
    • set_relief(Gtk.ReliefStyle.NONE) o bien, Gtk.ReliefStyle.NORMAL para cambiar estilo del relieve del botón.
    • Imagen desde el almacén
      • image = Gtk.Image()
        image.set_from_stock(Gtk.STOCK_ABOUT, Gtk.IconSize.BUTTON)
        button.set_image(image)
    • set_focus_on_click(False), para que cuando se pulse el botón no atrape al foco. Así se puede pulsar sin abandonar, por ejemplo, un área de texto.
  • GtkRadioButton
  • GtkSwitch
El último tipo de botón que nos queda es el que permite acceder a una página web (GtkLinkButton). Completar el programa con...
...
    enlace = Gtk.LinkButton(uri="https://es.wikipedia.org/wiki/Digital")
    enlace.set_label("Enlace a una web")
...
    rejilla.attach(enlace,0,7,3,1) # enlace
...
= = =

Enlace a carpeta para el programa botones.


No hay comentarios:

Publicar un comentario