Libro para desarrolladores en Mono:Capítulo 4
De Mono Hispano
Contents |
¿Qué es Gtk#?
Gtk# es un binding en .Net para las librerias gráficas Gtk+, el corazón de GNOME. Esto es, una implementación en la plataforma .Net de todas las clases soportadas por Gtk+. Si ya has programado en Gtk+, la transición no te será difícil, incluso puede que encuentres algunas ventajas en Gtk# en cuanto a la nomenclatura.
Si no has programado con Gtk+, y no has utilizado otro tipo de librerias o entornos gráficos (VB, Qt, etc) necesitaras comprender el concepto de widget, pues será de los términos mas escuchados en tu vida en cuanto a programación gráfica se refiere ;). Podrás olvidar tu nombre, pero no el de widget.
Un widget no es nada mas que un componente gráfico, como un botón, una lista desplegable o una ventana, con la que el usuario puede interactuar. Los widgets pueden ser tan simples como una imagen o tan complejos como una vista de árbol (treeview). En realidad los widgets mas complejos se derivan/componen de otros mas simples. Por ejemplo, un botón con el texto "Aceptar", en realidad tiene un widget "etiqueta" (label) dentro de un contenedor. Estos widgets que contienen a otros widgets (como una ventana) se le llaman contenedores (container).
A medida que avances en el libro, encontrarás muchos widgets, sin embargo existen aún muchos para documentarlos todos. MonoDoc puede ayudar tambien en tu tarea, y sobre todo la lectura de código, igualmente puedes ver la referencia de clases de Gtk# en la documentación de gotmono.
Versiones de Gtk#
Introducción a Gtk#
Escribir un programa básico en Gtk# y manejo de eventos
Al escribir una aplicación usando Gtk# hay que tener en cuenta que esta se ejecuta dentro de un bucle continuo a la espera de eventos, los cuales, normalmente, son generados cuando el usuario interactua con el programa.
Cuando ocurre un evento para el cual se haya creado un manejador, el control del programa pasará a la función especificada, y una vez procesado dicho evento, se devolverá el control al programa principal.
La forma de crear un manejador es añadiendo un método al evento que se quiera controlar, por ejemplo:
objeto.Event += new EventHandler(MetodoDelManejador);
Así, objeto, es una instancia del control que emite la señal (o que genera el evento), por ejemplo un botón, Event será el tipo de evento que se quiere capturar sobre dicho control, como un click de ratón, EventHandler es un delegado que se usa para la mayoría de los eventos, y por último, MetodoDelManejador es el método al que se llamará cuando ocurra el evento.
Una vez hayamos creado el manejador, queda programarlo:
static void MetodoDelManejador(object o, EventArgs args)
{
//Código
}
El primer parámetro (object o) del método hace referencia al control que ha generado el evento, y el segundo parámetro (EventArgs args) contiene una serie de datos que se pueden usar para el manejador.
Como se puede observar, el control que se le pasa es de tipo object, por lo que si queremos acceder a las propiedades del control original habrá que convertirlo al tipo que le corresponda, como puede ser un botón. Así para accerder a la propiedad Label, en caso de que haya sido un botón quién ha lanzado el evento, el código quedaría de la siguiente forma:
((Button)o).Label = "El texto";
En el siguiente ejemplo se muestra los conceptos básicos de un programa escrito usando GTK# y manejando dos de los eventos más comunes.
//Compilar: gmcs -pkg:gtk-sharp-2.0 GtkBasicSample.cs -out:GtkBasicSample.exe
//Ejecutar: mono GtkBasicSample.exe
using System;
using Gtk;
public class GtkBasicSample
{
public static void Main()
{
//Se inicializa GTK#
Application.Init();
//Se crea una ventana y un boton
Window win = new Window("Programa GTK# Basico");
Button boton = new Button("Pulsame!!");
//Se añade el botón a la ventana
win.Add(boton);
//Se redimensiona el tamaño de la ventana
win.Resize(300,300);
//Muestra la ventana y todo el contenido de esta (el botón)
win.ShowAll();
//Asignamos los eventos que nos interesen para cada control
win.DeleteEvent += new DeleteEventHandler(onDeleteEvent);
boton.Clicked += new EventHandler(onClickEvent);
//La aplicación entra en un bucle a la espera de eventos
Application.Run();
}
//Se programan los eventos asignados anteriormente a los controles
static void onDeleteEvent(object o, DeleteEventArgs args)
{
Application.Quit();
}
static void onClickEvent(object o, EventArgs args)
{
Console.WriteLine("Has pulsado el boton!!");
}
}
Lo primero de todo es hacer uso de Gtk, para ello usamos la instrucción:
using Gtk;
Esto nos permite usar las clases de GTK# de forma más comoda. Por otro lado están las llamadas a los métodos Init(), Run() y Quit() de la clase Application.
El método Init() ha de llamarse antes de hacer uso de cualquier función de GTK#, lo que hace es inicializar todo lo necesario para poder trabajar con GTK#. A continuación se utiliza el método Run(), este método ejecuta la aplicación dentro de un bucle continuo a la espera de que se produzcan eventos o que se termine la aplicación, usando para esto último el método Quit().
Otro punto clave son los eventos, en el ejemplo se usan dos tipos de eventos, aunque evidentemente hay muchos más:
win.DeleteEvent += new DeleteEventHandler(onDeleteEvent);
boton.Clicked += new EventHandler(onClickEvent);
La mayoría de eventos derivan de la clase Widget, así, habrá muchos controles que tengan los mismos eventos, pero otros eventos serán específicos de unos controles concretos.
En el caso del ejemplo se usa el evento "DeleteEvent" que corresponde con la ventana creada, y el evento "Clicked" que corresponde al botón. A continuación se usa el operador "+=" para agregar el método correspondiente al evento generado por cada uno de los controles, y por último, se especifica el tipo de método que vamos a manejar, pasándole como parámetro el nombre de la función que se usará para programarlo.
Acomodar widgets usando cajas
Hacer que los widgets interactúen
Hacer diálogos mediante subclases
Escribir subclases que se encarguen de mostrar cuadros de dialogos es sencillo. A veces necesitamos cosas como esta para pedir al usuario cierto tipo de informacion. Para escribir un cuadro de dialogo personalizado primero tenemos que considerar la clase Gtk.Dialog.
public class Dialog : Window, IDisposable, GLib.IWrapper, Atk.Implementor {
protected Dialog (GLib.GType gtype);
public Dialog (IntPtr raw);
public Dialog ();
public Dialog (string title, Window parent, DialogFlags flags);
public void AddActionWidget (Widget child, int response_id);
public void AddActionWidget (Widget child, ResponseType response);
public void AddButton (string button_text, ResponseType response);
public Widget AddButton (string button_text, int response_id);
protected override void Finalize ();
protected virtual void OnClose ();
protected virtual void OnResponse (ResponseType response_id);
public void Respond (ResponseType response);
public void Respond (int response_id);
public int Run ();
public void SetResponseSensitive (ResponseType response_id, bool setting);
public static GLib.GType GType {
get;
}
public HButtonBox ActionArea {
get;
}
public ResponseType DefaultResponse {
set;
}
public bool HasSeparator {
get;
set;
}
public VBox VBox {
get;
}
public event EventHandler Close;
public event ResponseHandler Response;
}
En negritas tenemos los componentes de esta clase que usaremos mas comunmente. vamos a definir algunos elementos.
El metodo...
public void AddActionWidget (Widget child, int response_id);
Agrega un widget personalizado en el area de acciones del dialogo (el area donde van los botones)
El metodo...
public void AddButton ( ... ) public Widget AddButton ( ... ) /* Segunda variante. Nos devuelve el la instancia Gtk.Widget que representa al boton, y con ello poder manipularlo a nuestro antojo */
Agrega un Boton de accion al dialogo, estos botones pueden ser agregados con imagenes de Stock, pasando el valor de Stock correspondiente para nuestro boton
Tambien debemos notar que cada uno de los metodos anteriores tiene 2 variantes.
ResponseType es una enumeracion definida de la siguiente manera
public enum ResponseType {
None,
Reject,
Accept,
DeleteEvent,
Ok,
Cancel,
Close,
Yes,
No,
Apply,
Help
}
y podemos dar uno de estos valores a cualquier boton de accion de nuestro dialogo
La propiedad...
public VBox VBox {
El campo VBox es una caja que representa el area donde se anexara el contenido del dialogo.
Con solo ver la definicion de la clase podemos pensar en como implementar nuestro dialogo personalizado. A continuacion haremos un dialogo para iniciar sesion en una aplicacion.
Ahora Veamos un ejemplo ilustrativo. Imaginemos que deseamos implementar un dialogo para obtencion de datos como usuario y contraseña. pues este es un ejemplo sencillo de como hacerlo.
using System;
using Gtk;
public class DialogoPersonalizado : Gtk.Dialog { // aprovechamos la herencia para crear nuestro dialogo
/* Definimos elementos personalizados */
Gtk.Entry _entry_usuario;
Gtk.Entry _entry_contrasena;
Gtk.HBox _hbox1; // Empaqueta usuario con su entry
Gtk.HBox _hbox2; // Empaqueta contrasena con su entry
Gtk.Button _button_validar;
public DialogoPersonalizado (string title) {
base.Title = title;
_entry_usuario = new Entry ();
_entry_usuario.Changed += new EventHandler (_entry_validar);
_entry_contrasena = new Entry ();
_entry_contrasena.Visibility = false;
_entry_contrasena.Changed += new EventHandler (_entry_validar);
_hbox1 = new HBox (true, 0);
_hbox2 = new HBox (true, 0);
_hbox1.PackStart (new Gtk.Label ("Usuario: "), false, false, 0);
_hbox1.PackStart (_entry_usuario);
_hbox2.PackStart (new Gtk.Label ("Contrasena: "), false, false, 0);
_hbox2.PackStart (_entry_contrasena);
base.VBox.PackStart (_hbox1, false, false, 0);
base.VBox.PackStart (_hbox2, false, false, 10);
base.VBox.ShowAll ();
// hasta aqui tenemos la interfaz creada y anexadda a nuestro dialog, solo faltan los botones de acción
// agregamos los botones usando la sobrecarga al metodo AddButton que mejor convenga
_button_validar = (Gtk.Button) base.AddButton (Stock.Ok, (int) ResponseType.Ok); // hacemos cast a int para satisfacer la sobrecarga al metodo que nos devuelve el Widget
base.AddButton (Stock.Cancel, ResponseType.Cancel);
_button_validar.Clicked += new EventHandler (_button_validar_Clicked);
_entry_validar (_entry_usuario, EventArgs.Empty);
base.DefaultResponse = ResponseType.Ok;
}
void _entry_validar (object sender, EventArgs args) {
bool state = !( (_entry_usuario.Text.Trim ().Length == 0) ||
(_entry_contrasena.Text.Trim ().Length == 0));
Console.WriteLine (state);
SetResponseSensitive (ResponseType.Ok, state);
}
void _button_validar_Clicked (object sender, EventArgs args) {
// Aqui podemos validar con acceso a base de datos o como sea, o simplemente hacer
// lo siguiente :)
Gtk.MessageDialog dialog = new MessageDialog ( this,
DialogFlags.Modal,
MessageType.Info,
ButtonsType.Ok,
"Clickeaste el boton Ok del dialogo Iniciar sesion",
"Informacion"
);
dialog.Run ();
dialog.Destroy ();
}
// Sobre ponemos el metodo OnResponse
// que se dispara cuando se clickea algun
// boton o se cierra el cuadro de dialogo y no hacemos nada productivo
protected override void OnResponse (ResponseType response) {
// Si la respuesta a devolver es...
switch (response) {
case ResponseType.Ok:
Console.WriteLine ("Este mensaje sucede antes de devolver el valor ResponseType.Ok");
break;
// Este valor es el que se devuelve
// cuando se hace click en el boton
// superior derecho para cerrar la ventana (la X)
// y solo cambiaremos el valor de respuesta
case ResponseType.DeleteEvent:
//El sig. cambio provoca una nueva llamada
// a este mismo metodo pero con el valor
// ResponseType.Cancel
Respond (ResponseType.Cancel);
break;
case ResponseType.Cancel:
Console.WriteLine ("Este mensaje sucede antes de devolver el valor ResponseType.Cancel");
break;
}
}
// Unas propiedades para devolver los valores
// de manera mas elegante
public string Usuario {
get {
return _entry_usuario.Text;
}
}
public string Contrasena {
get {
return _entry_contrasena.Text;
}
}
static void Main () {
Application.Init ();
// Creamos nuestroi dialogo pero no se muestra
DialogoPersonalizado dialogo = new DialogoPersonalizado ("Iniciar Sesion");//.Run ();
//la llamada dialogo.Run muestra el dialogo y espera por una respuesta
// que se evalua
if ((ResponseType) dialogo.Run () == ResponseType.Ok) {// Click en Ok ?
Console.WriteLine ("Acceso Concedido a usuario '{0}' con contraseña '{1}'", dialogo.Usuario, dialogo.Contrasena);
}
else
Console.WriteLine ("Error. realizando acción cancelar...");
dialogo.Destroy (); // Destruimos el dialogo
while (Application.EventsPending ()) // Esto va mas alla del objetivo de este ejemplo
Application.RunIteration ();
}
}
Dibujar gráficos
Creación de menús
A continuación se muestra un sencillo ejemplo para crear una barra de menú con sus correspondientes eventos.
using System;
using Gtk;
class MenuSample
{
public static void Main(string[] args)
{
Application.Init();
new MenuSample();
Application.Run();
}
public MenuSample()
{
// Ventana Principal
Window win = new Window("Title");
VBox vb1 = new VBox(false, 0);
// Crea una barra de menu
MenuBar mb = new MenuBar();
// Crea el menu para la barra
Menu mFile = new Menu();
// Los items que contiene el menu
MenuItem iFile = new MenuItem("_File");
MenuItem iNew = new MenuItem("_New");
MenuItem iOpen = new MenuItem("_Open");
MenuItem iSave = new MenuItem("_Save");
MenuItem iSaveAs = new MenuItem("Save _As...");
SeparatorMenuItem iFileSeparator = new SeparatorMenuItem();
MenuItem iQuit = new MenuItem("_Quit");
// Se añaden los items al menu
iFile.Submenu = mFile;
mFile.Append(iNew);
mFile.Append(iOpen);
mFile.Append(iSave);
mFile.Append(iSaveAs);
mFile.Append(iFileSeparator);
mFile.Append(iQuit);
// Se añade el menu a la barra
mb.Append(iFile);
// Se añade la barra al VerticalBox y este a la ventana
vb1.PackStart(mb, false, false, 0);
win.Add(vb1);
win.Resize(400, 400);
win.ShowAll();
// Evento para cerrar la aplicacion
win.DeleteEvent += new DeleteEventHandler(closeApplication);
// Eventos de cada elemento del menu
iNew.Activated += new EventHandler(iNew_Activated);
iOpen.Activated += new EventHandler(iOpen_Activated);
iSave.Activated += new EventHandler(iSave_Activated);
iSaveAs.Activated += new EventHandler(iSaveAs_Activated);
iQuit.Activated += new EventHandler(iQuit_Activated);
}
// A continucacion se programan los eventos
void closeApplication(object o, DeleteEventArgs args)
{
Application.Quit();
}
void iNew_Activated(object o, EventArgs args)
{
Console.WriteLine("New Activated");
}
void iOpen_Activated(object o, EventArgs args)
{
Console.WriteLine("Open Activated");
}
void iSave_Activated(object o, EventArgs args)
{
Console.WriteLine("Save Activated");
}
void iSaveAs_Activated(object o, EventArgs args)
{
Console.WriteLine("Save As Activated");
}
void iQuit_Activated(object o, EventArgs args)
{
Console.WriteLine("Bye bye !!");
Application.Quit();
}
}
El ejemplo anterior crea un menú muy simple, tan solo muestra los elementos. Ahora vamos a ver como "decorarlo" un poco añadiendole iconos que representen la función del elemento y también vamos a añadirle aceleradores, que son combinaciones de teclas que tienen el mismo efecto que si seleccionaramos un elemento con el ratón.
Para este caso modificaremos el elemento "Quit" del menú.
Antes que nada hay que añadir un "AccelGroup" para relacionar los eventos de los aceleradores con la ventana donde se generan.
AccelGroup grupo = new AccelGroup(); win.AddAccelGroup(grupo);
Una vez añadido el "AccelGroup" hay dos formas de añadir el icono y la combinación de teclas, una es la forma rápida, y la otra, menos rápida pero más personalizada.
La primera forma, la rápida, quedaria de esta manera:
ImageMenuItem iQuit = new ImageMenuItem(Stock.Quit, grupo);
Como se puede ver, ya no se usa un MenuItem como antes, sino un ImageMenuItem, que recibe como parámetros el tipo de icono y la combinación de teclas predeterminada y lo asocia con el "AccelGroup" creado.
La otra forma de hacer lo mismo pero paso a paso:
ImageMenuItem iQuit = new ImageMenuItem("Salir");
iQuit.Image = new Image("salir.svg");
iQuit.AddAccelerator("activate", grupo, new AccelKey(Gdk.Key.q, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
De esta forma lo primero es crear el ImageMenuItem con el texto que aparecerá, después se le indica la imagen que queremos que aparezca, y por último le añadimos el acelerador. Si quisieramos prescindir de la imagen, tan solo bastaría con utilizar un MenuItem.
Otro tipo de elementos que se pueden poner en el menú son las casillas de estado o activación (check box) y los botones de opciones (radio button). Las casillas de estado no tienen ninguna dificultad para usarlas, tan solo cambia el tipo de control por CheckMenuItem, pero para las opciones es un poco diferente.
El codigo para el CheckMenuItem quedaria de esta forma:
CheckMenuItem iChActivar1 = new CheckMenuItem("Activar 1");
El estado del CheckMenuItem se captura mediante la propiedad "Active". La programación del evento, por ejemplo, puede quedar de la siguiente forma:
void iChActivar1_Activated(object o, EventArgs args)
{
Console.WriteLine("Activar 1");
if(((CheckMenuItem)o).Active)
Console.WriteLine("ACTIVO");
else
Console.WriteLine("INACTIVO");
}
Para el caso de los RadioMenuItem hay que tener en cuenta que funcionan por grupos, es decir, si se quiere dar a elegir entre dos opciones, estas dos tienen que formar parte de un mismo grupo, ya que sino, se podría elegir las dos opciones a la vez.
Lo primero será crear el RadioMenuItem que se usará para agrupar a los demás:
RadioMenuItem iRdOpcion1 = new RadioMenuItem("Opcion 1");
A continuación se crean los demás elementos del grupo entre las que poder elegir:
RadioMenuItem iRdOpcion2 = new RadioMenuItem(iRdOpcion1, "Opcion 2"); RadioMenuItem iRdOpcion3 = new RadioMenuItem(iRdOpcion1, "Opcion 3")
Por último, para saber que opcion se ha escogido se utiliza el evento Activated y la propiedad Active del RadioMenuItem para cada uno:
iRdOpcion1.Activated += new EventHandler(iRdOpcion1_Activated); iRdOpcion2.Activated += new EventHandler(iRdOpcion2_Activated); iRdOpcion3.Activated += new EventHandler(iRdOpcion3_Activated);
void iRdOpcion1_Activated(object o, EventArgs args)
{
if(((RadioButtonItem)o).Active)
Console.WriteLine("Opcion 1 seleccionada");
}
//Lo mismo para las demas opciones.
Para acabar se muestra un ejemplo completo usando los nuevos elementos.
using System;
using Gtk;
class MenuSample
{
public static void Main(string[] args)
{
Application.Init();
new MenuSample();
Application.Run();
}
public MenuSample()
{
Window win = new Window("Menús ampliados");
// Se crea el AccelGroup y se añade a la ventana
AccelGroup grupo = new AccelGroup();
win.AddAccelGroup(grupo);
VBox vb1 = new VBox(false, 0);
MenuBar mb = new MenuBar();
Menu mFile = new Menu();
// Los items que contiene el menu
MenuItem iFile = new MenuItem("_File");
// Elemento simple
MenuItem iSalir = new MenuItem("Salir");
// Elementos ImageMenuItem
ImageMenuItem iNuevo = new ImageMenuItem(Stock.New, grupo);
ImageMenuItem iAbrir = new ImageMenuItem("Abrir");
iAbrir.Image = new Image("abrir.png");
iAbrir.AddAccelerator("activate", grupo, new AccelKey(Gdk.Key.o,
Gdk.ModifierType.ControlMask,
AccelFlags.Visible));
// Elementos CheckMenuItem
CheckMenuItem iChActivar1 = new CheckMenuItem("Activar 1");
CheckMenuItem iChActivar2 = new CheckMenuItem("Activar 2");
iChActivar2.AddAccelerator("activate", grupo, new AccelKey(Gdk.Key.Key_2,
Gdk.ModifierType.ControlMask,
AccelFlags.Visible)););
// Elementos RadioMenuItem
RadioMenuItem iRdOpcion1 = new RadioMenuItem("Opcion 1");
RadioMenuItem iRdOpcion2 = new RadioMenuItem(iRdOpcion1, "Opcion 2");
iRdOpcion2.Active = true;
RadioMenuItem iRdOpcion3 = new RadioMenuItem(iRdOpcion1, "Opcion 3");
// Se añaden los items al menú
iFile.Submenu = mFile;
mFile.Append(iNuevo);
mFile.Append(iAbrir);
mFile.Append(new SeparatorMenuItem());
mFile.Append(iChActivar1);
mFile.Append(iChActivar2);
mFile.Append(new SeparatorMenuItem());
mFile.Append(iRdOpcion1);
mFile.Append(iRdOpcion2);
mFile.Append(iRdOpcion3);
mFile.Append(new SeparatorMenuItem());
mFile.Append(iSalir);
mb.Append(iFile);
vb1.PackStart(mb, false, false, 0);
win.Add(vb1);
win.Resize(400, 400);
win.ShowAll();
// Evento para cerrar la aplicacion
win.DeleteEvent += new DeleteEventHandler(closeApplication);
// Eventos de cada elemento del menu
iNuevo.Activated += new EventHandler(iNuevo_Activated);
iAbrir.Activated += new EventHandler(iAbrir_Activated);
iChActivar1.Activated += new EventHandler(iChActivar1_Activated);
iChActivar2.Activated += new EventHandler(iChActivar2_Activated);
iRdOpcion1.Activated += new EventHandler(iRdOpcion1_Activated);
iRdOpcion2.Activated += new EventHandler(iRdOpcion2_Activated);
iRdOpcion3.Activated += new EventHandler(iRdOpcion3_Activated);
iSalir.Activated += new EventHandler(iSalir_Activated);
}
// A continucacion se programan los eventos
void closeApplication(object o, DeleteEventArgs args)
{
Application.Quit();
}
void iSalir_Activated(object o, EventArgs args)
{
Console.WriteLine("Hasta la proxima");
Application.Quit();
}
void iNuevo_Activated(object o, EventArgs args)
{
Console.WriteLine("Nuevo activated");
}
void iAbrir_Activated(object o, EventArgs args)
{
Console.WriteLine("Abrir activated");
}
void iChActivar1_Activated(object o, EventArgs args)
{
if(((CheckMenuItem)o).Active)
Console.WriteLine("Activar 1: ACTIVO");
else
Console.WriteLine("Activar 1: INACTIVO");
}
void iChActivar2_Activated(object o, EventArgs args)
{
if(((CheckMenuItem)o).Active)
Console.WriteLine("Activar 2: ACTIVO");
else
Console.WriteLine("Activar 3: INACTIVO");
}
void iRdOpcion1_Activated(object o, EventArgs args)
{
if(((RadioMenuItem)o).Active)
Console.WriteLine("Opcion 1 seleccionada");
}
void iRdOpcion2_Activated(object o, EventArgs args)
{
if(((RadioMenuItem)o).Active)
Console.WriteLine("Opcion 2 seleccionada");
}
void iRdOpcion3_Activated(object o, EventArgs args)
{
if(((RadioMenuItem)o).Active)
Console.WriteLine("Opcion 3 seleccionada");
}
}

Powered by MediaWiki