lunes, 19 de julio de 2010

GTK# - GtkTextView y el evento KeyPressEvent

Como ya dije en una entrada anterior, mi opinión de C# y el código gestionado ha ido cambiando con el tiempo. Ahora me parece que compilar una aplicación como código gestionado es una gran idea por varias razones. Una de ellas es que el resultado de la compilación es independiente de la plataforma en que correrá, aunque este beneficio se pierde si hacemos llamadas a librerías compiladas como código nativo, ya que deberemos tomarnos el trabajo de compilar esas librerías para cada plataforma y algunas librerías simplemente no estarán disponibles para ciertas plataformas.

Estoy escribiendo un mensajero instantáneo para comunicar las PC de mi hogar, ya sea que estén corriendo Windows o Linux. Uso un GtkTextView como entrada de texto y necesitaba interceptar la pulsación de la tecla Enter pero resulta que el evento KeyPressEvent no se disparaba nunca, aún si el widget estaba conectado a la señal KeyPress. Más de uno debe pensar en este punto que es una falla de GTK# pero no es así. La solución es tan simple como usar el atributo [GLib.ConnectBefore].

A veces, porque no encontramos documentación sobre un widget o método específico de GTK# terminamos leyendo la documentación de GTK+, o PyGTK y asumimos que el comportamiento en GTK# será igual o muy similar.

Pero, en el caso de widgets como GtkEntry o GtkTextView, si escribimos un manejador para, por ejemplo, KeyPressEvent, el mismo se disparará después de que el widget haya procesado la entrada del teclado. Es decir, que nuestro manejador no va a ejecutarse al menos que el widget permita que la señal KeyPress se siga propagando, pero para estos dos widgets, en GTK# esto no va a ocurrir nunca.

Según las listas de correo del proyecto mono, para GTK# lo esperado es que cuando queremos cambiar la forma en que un GtkTextView procesa la entrada desde el teclado lo que debemos hacer es sobrecargar el método de la clase que representa al widget en GTK# que se encarga de manejar el evento KeyPress. No obstante, al parecer, yo no soy el único que hallé esta situación algo incómoda. Por lo tanto, desde hace algún tiempo existe este atributo [GLib.ConnectBefore] y que se usa de esta manera:

    [GLib.ConnectBefore]
    protected virtual void OnInputTextViewKeyPressEvent (object o, Gtk.KeyPressEventArgs args)
    {
        if (args.Event.Key == Gdk.Key.Return)
        {
            ComunicationManager.Instance.SendMessageToAll(InputTextView.Buffer.Text + "\n");
            InputTextView.Buffer.Text = "";
            args.RetVal = true;
        } else
        if (InputTextView.Buffer.CharCount * 2 >= ConfigurationManager.MaxMsgBufLen - 6)
        {
            if (args.Event.Key != Gdk.Key.Delete) args.RetVal = true;
        } else
            args.RetVal = false;
    }

Este código, si se ha presionado la tecla Enter, pone el valor de args.RetVal a true para indicar que la señal KeyPress no debe seguir propagándose, mientras que sí permite su propagación, siempre y cuando el número de caracteres en el Buffer no superen el máximo permitido por la aplicación, poniendo args.RetVal a false cuando la tecla pulsada es cualquier otra que no sea Enter. La idea es que, cuando el usuario presiona Enter se envíe el mensaje en vez de crearse un nuevo párrafo.

El código que exhibo aquí todavía se está escribiendo. Es muy inseguro en su estado actual. Falta hacer que sí deje que el widget procese el evento cuando las teclas pulsadas son las teclas de borrar, aún si se superó el número máximo de caracteres por mensaje que impone la aplicación. También debo considerar en el futuro el caso en que el usuario pegue texto en el widget en vez de escribirlo con el teclado.

Si quito o comento la primera línea: [GLib.ConnectBefore] mi manejador no se disparará nunca.

Acerca de los atributos en C#: Introducción a los atributos en MSDN.

2 comentarios:

Unknown dijo...

Esta Bueno

Anónimo dijo...

Is correct

Publicar un comentario