Índice de la sección dedicada a .NET (en el Guille) Windows Forms Paso a Paso

Ejemplo 3 de Desarrollo de aplicaciones de escritorio con Windows Forms

Links para acceder a los otros ejemplos: Ejemplo 1, Ejemplo 2, Ejemplo 3



En este tercer ejemplo "paso a paso" sobre Windows Forms, vamos a crear controles personalizados, así como una aplicación que utilice esos controles creados por nosotros.

Los controles que podemos crear con Visual Studio .NET, (realmente con .NET Framework), pueden ser de tres tipos:
1- Usando herencia, de esta forma aprovechamos toda la funcionalidad de un control existente al que sólo tendremos que indicarle el código necesario para que haga las cosas que nosotros queremos que haga y que no estén implementadas en el control que queremos "personalizar"... por ejemplo para que sólo acepte números.
2- Usando varios controles ya existentes para poder crear un "control de usuario" (o control compuesto). Esta sería la forma que "antes" se usaba para poder crear nuestros propios controles. Esta forma de personalizar los controles es útil cuando necesitamos más de un control, por ejemplo, si queremos crear un control que contenga una etiqueta y una caja de textos. Esto es así, ya que con .NET Framework no podemos usar la herencia múltiple y por tanto no podríamos crear una nueva clase que herede las clases Label y TextBox.
3- Creando el control a partir de cero, es decir, nos tenemos que encargar de todos los pormenores y detalles, por ejemplo, si el control va a mostrar algo de forma gráfica, nos tendremos que encargar de dibujar el control cuando el sistema operativo nos avise de ese "detalle", normalmente la forma de avisarnos de que hay que dibujar el control, es mediante el evento Paint.

En este ejemplo, los controles los vamos a crear usando herencia, para que comprobemos lo fácil que es crear un control personalizado, de forma que se adapte a nuestras necesidades o preferencias.
En concreto vamos a crear un TextBox personalizado para que sólo acepte números.
Como ya comenté en el párrafo anterior, usando la herencia nos aprovechamos de todas las características que ya tenga el control en el que nos vamos a basar, por tanto sólo tendremos que escribir el código que nos permita personalizar el control a nuestras preferencias, el resto del código "normal" ya lo tendremos heredado del control que hemos utilizado como base; debido a que el control que vamos a crear se basa en un control TextBox, todas las características de ese control lo vamos a tener a nuestra disposición, lo único que tendremos que escribir o codificar son las características que queremos personalizar. Como el control que vamos a crear es una caja de textos que sólo admita números, vamos a personalizar el procedimiento que detecta la pulsación de teclas (OnKeyPress), para que se acepten sólo las pulsaciones que nosotros queramos considerar como números; también vamos a codificar la propiedad Text, que será la que se usará cuando asignemos un nuevo valor a la caja de textos, de forma que se compruebe si el valor asignado realmente representa sólo números.

Los pasos a seguir para crear este ejemplo serán los mismos mostrados en los ejemplos anteriores, por tanto sólo indicaré las cosas que realmente sean diferentes a otros tipos de proyectos. De todas formas, aunque no se indique de forma explícita, voy a indicar cuales serán esos pasos.

1- Iniciamos el Visual Studio .NET

2- Creamos un nuevo proyecto del tipo Biblioteca de controles Windows usando el lenguaje de nuestra predilección.


Figura 1, Diálogo para Nuevo proyecto

3- Cuando creamos un proyecto del tipo Biblioteca de controles de Windows, se añade un contenedor del tipo Control de usuario, este sería el tipo de control que tendríamos que usar para crear controles compuestos por controles ya existentes, esta sería la forma más parecida a la usada cuando creábamos controles ActiveX.
Ese control no lo necesitaremos para crear nuestro control "heredado", por tanto, iremos al Explorador de soluciones, seleccionaremos el archivo UserControl1.cs (si el idioma seleccionado es C#, en caso de que sea otro el lenguaje usado, la extensión del archivo será diferente) y lo eliminaremos.


Figura 2, El explorador de soluciones

4- El siguiente paso es agregar una clase al proyecto, para ello iremos al menú Proyecto y seleccionaremos la opción "Agregar clase..."

5- Se mostrará un cuadro de diálogo como el mostrado en la figura 3, ahí le indicaremos que es una clase y el nombre será: TextBoxNum.cs, (recuerda que la extensión dependerá del lenguaje que estemos usando, en caso de Visual Basic .NET, la extensión sería .vb).


Figura 3, Agregar nuevo elemento al proyecto

6- Al añadir una nueva clase, se mostrará la plantilla de esa nueva clase, como nosotros queremos crear una clase derivada de la clase TextBox, (la clase caja de textos), en la declaración de la clase debemos indicarle que la clase TextBoxNum se deriva de TextBox, para hacer esto, tendremos que escribir lo siguiente:

En C# la declaración de la clase quedaría así:
public class TextBoxNum : TextBox
Esta es la forma de indicar la clase de la que se hereda en el lenguaje C#.

En Visual Basic .NET la declaración de la clase sería de esta otra forma:
Public Class TextBoxNum : Inherits TextBox

Nota:
En los siguientes pasos sólo se mostrará el código de C#, aunque este mismo código usando Visual Basic .NET podrás descargarlo desde mi sitio web: http://www.elguille.info/NET/universidad/tour2003.asp

7- El siguiente paso será escribir el código para implementar nuestras preferencias, como ya indiqué al principio, este control heredado servirá para aceptar sólo números, por tanto vamos a crear una función que "filtre" cada uno de los caracteres que se vayan escribiendo en la caja de textos o bien cuando se asigne un nuevo valor a la propiedad Text. Por tanto sólo vamos a escribir la susodicha función, además del procedimiento OnKeyPress que será el que llame el .NET cuando el usuario pulse una tecla, también vamos a escribir el código de la propiedad Text.

8- Antes de escribir el código real de nuestro control, tenemos que indicarle al compilador dónde encontrar la clase que vamos a usar de base de nuestro control. Como sabemos todos los controles para aplicaciones Windows están dentro del espacio de nombres Windows.Forms, por tanto necesitamos agregar una referencia (o importación) al espacio de nombres Windows.Forms, para ello lo indicaremos de la siguiente forma:
using System.Windows.Forms;
(recuerda que este sería el código para C#)

Nota:
Si no añadimos esta importación del espacio de nombres Windows.Forms, al compilar se producirá un error de que no se ha encontrado la clase TextBox.

9- Empecemos viendo el código de la función que comprueba si el carácter pulsado es de los que vamos a aceptar.

// array con los dígitos aceptados por el textbox numérico
char[] digitos = new char[]{'0','1','2','3','4','5','6','7','8','9',
                            '.',',','-','\b'};
// esta función permite controlar si el carácter es de los admitidos
protected virtual bool CaracterCorrecto(char c)
{
    // devolverá true si el carácter está en el array
    return (Array.IndexOf(digitos, c) != -1);
}

Nota:
La función CaracterCorrecto está declarada como protected virtual; cuando declaramos una función con el modificador de acceso protected dicha función sólo será visible dentro de la propia clase y en clases derivadas de esta, por otro lado, el modificador virtual indica que esta función puede ser reemplazada por otra en una clase derivada, un ejemplo de esto último lo veremos a continuación.

Lo que aquí hacemos es crear un array (matriz) de tipo char con los dígitos que queremos aceptar: los números del 0 al 9, además del punto, la coma, el signo menos y la tecla de "borrar hacia atrás".
La función devuelve true o false según el carácter comprobado esté o no dentro del array. Para comprobar si dicho carácter está incluido en el array, usamos el método IndexOf de la clase Array, el cual comprueba si un carácter está incluido o no en el array indicado, en caso de que no se haya encontrado, se devolverá -1.

10- Cuando el usuario pulsa en una tecla, el sistema operativo se encarga de llamar al método OnKeyPress (entre otros) de la clase, éste método será el que produzca el evento KeyPress del control insertado en el formulario (o contenedor). Por tanto, necesitamos interceptar ese procedimiento para comprobar si la tecla pulsada es una de las que nosotros vamos a aceptar. Los métodos que interceptan los eventos de una clase, se declaran protected para que sólo sean accesibles desde la propia clase o de las clases derivadas, por tanto si reemplazamos dicho método en nuestra clase, podremos comprobar dicha tecla y aceptarla o no, de eso se encarga el método OnKeyPress que vamos a definir en nuestra clase derivada de TextBox, cuyo código es:

// el procedimiento que intercepta la pulsación de teclas
protected override void OnKeyPress(KeyPressEventArgs e)
{
    // Comprobar si aceptamos el carácter pulsado,
    // si el carácter pulsado no está en el array, no aceptarlo
    if( !CaracterCorrecto(e.KeyChar) )
        e.Handled = true;
    // es conveniente llamar a el procedimiento
    // del mismo nombre de la clase base
    base.OnKeyPress(e);
}

Este procedimiento, al estar declarado como override, reemplaza al procedimiento de mismo nombre de la clase base, por tanto, éste será el que se utilice al producirse la pulsación de una tecla.
Lo que hacemos es comprobar si no es uno de los caracteres aceptados y en caso de ser así, (se cumplirá la condición if), le asignamos un valor true a la propiedad Handled con lo que conseguimos que se ignore dicha pulsación.
Por último llamamos al método del mismo nombre de la clase base, por si esa clase hiciera a su vez otras comprobaciones.

11- Por último, vamos a reemplazar la propiedad Text para que también se comprueben los caracteres aceptables al asignar un nuevo valor a dicha propiedad. En este caso lo que haremos es recorrer cada uno de los caracteres de la cadena asignada y aceptar sólo los que sean correctos, por tanto llamaremos a la función CarracterCorrecto para que se encargue de hacer dicha comprobación.

// La propiedad Text "sobrescrita" para adaptarla
// a nuestra nueva implementación
public override string Text
{
    get
    {
        return base.Text;
    }
    set
    {
        // aceptar sólo dígitos
        string s = "";
        foreach(char c in value)
        {
            // si es un carácter correcto,
            // lo agregamos a la nueva cadena
            if( CaracterCorrecto(c) )
                s += c;
        }
        base.Text = s;
    }
}

El bloque get, que es el que se llama al recuperar el contenido de una propiedad, simplemente devuelve el contenido de la propiedad Text de la clase base, ya que no tenemos que hacer ninguna comprobación.
En el bloque set, que es el que se utiliza al asignar un nuevo valor a una propiedad, es donde hacemos lo comentado anteriormente, para ello recorremos cada uno de los caracteres de la cadena a asignar (indicada por value) y si dicho carácter es de los aceptables, lo asignamos a la variable s, de forma que al final del bucle, dicha variable contenga sólo los caracteres que hemos aceptado. Nuevamente usamos la misma propiedad de la clase base por si en dicha propiedad se hicieran algunas otras comprobaciones.

Nota:
Es recomendable que siempre que derivemos una clase y reemplacemos alguna propiedad, usar dicha propiedad de la clase base tanto para devolver el valor como para asignar el nuevo contenido, esto nos permitirá continuar la cadena, de forma que si en la clase base se hacen algunas comprobaciones, éstas no se pierdan. En nuestro código, si en lugar de usar: base.Text = s; hubiésemos devuelto directamente el valor de la variable s, seguramente no habría ningún problema, pero si alguien usa nuestra clase para crear una nueva caja de textos, las validaciones que hacemos al asignar a la propiedad Text se perderían si no simplemente devolviera el nuevo valor.
Además de que al usar la propiedad Text de la clase base como contenedor del valor asignado, nos evitamos tener que declarar una variable privada que se encargue de "contener" dicho valor. Imagínate lo que podría ocurrir si sólo devolvemos el valor de la variable s y no lo "conservamos": ¡Nunca se podría cambiar el contenido de dicha propiedad!

12- Una vez que tenemos todo el código necesario para hacer que nuestra clase derivada de TextBox sólo acepte números, podemos compilarlo para que podamos usarlo en una aplicación de prueba.

13- Vamos a crear un proyecto de prueba. Para ello vamos a agregar un nuevo proyecto a nuestra "solución". Para conseguir nuestro objetivo, sigue estos pasos:

14- En el menú Archivo, selecciona Agregar proyecto y del menú mostrado, selecciona Nuevo proyecto..., tal como se muestra en la figura 4:


Figura 4, Agregar un nuevo proyecto a la solución existente
 

15- Se mostrará el cuadro de diálogo de selección de nuevo proyecto, del cual seleccionaremos Aplicación para Windows, esto agregará un proyecto con un formulario en el que añadiremos nuestro control.

16- Para que podamos agregar nuestro control al formulario, lo mejor será añadirlo al cuadro de herramientas (donde están los controles incluidos con Visual Studio .NET), para conseguirlo podemos hacerlo de varias formas. Debido a que VS.NET nos permite personalizar el cuadro de herramientas, vamos a crear nuestra propia ficha y en ella vamos a agregar el control TextBoxNum, veamos cómo:

Nota:
Si en lugar de agregar una nueva ficha, decides agregarlo a cualquiera de las existentes, te puedes saltar el siguiente paso.

17- Mostramos el Cuadro de herramientas, pulsamos con el botón secundario del ratón sobre él y del menú desplegable mostrado, seleccionamos Agregar ficha, tal como se muestra en la figura 5.


Figura 5, Opciones para personalizar el Cuadro de herramientas

Se agregará una nueva ficha a dicho Cuadro de herramientas (cuando agregamos una ficha, en la parte inferior del Cuadro de herramientas tendremos que escribir el nombre de la nueva ficha).

18- Ahora vamos a agregar nuestro control a la nueva ficha (o a la que esté en ese momento mostrada). Para ello, pulsa con el botón secundario del ratón sobre la ficha, del menú desplegable (mostrado en la figura 5), selecciona Personalizar cuadro de herramientas... Se mostrará un cuadro de diálogo con dos fichas, selecciona Componentes de .NET Framework y pulsa en el botón Examinar...
Localiza el directorio del proyecto TextBoxNum y dentro del directorio bin estará la librería con nuestro nuevo control (la extensión será .DLL)
Al seleccionarla, se mostrará dentro de la lista del cuadro de diálogo. Pulsamos aceptar y la tendremos disponible en la ficha del Cuadro de herramientas.


Figura 6, Personalizar cuadro de herramientas con el nuevo control

19- Una vez que tenemos nuestro control en el Cuadro de herramientas, ya podemos añadirlo al formulario como cualquier otro control.
Al agregar el control al formulario, en las referencias del proyecto se habrá agregado la referencia oportuna a la librería en la que se encuentra nuestra clase.

Como dato curioso, fíjate en el texto mostrado, debido a que el control no admite letras, sólo se mostrará el número 1, esto es así, porque por defecto, el contenido de la propiedad Text de las cajas de texto es el nombre del control y como el nuestro no admite letras, simplemente se queda con el número de TextBoxNum1 (que es cómo se llamará el control agregado).

20- Como ejercicio podrías añadir una propiedad al control de forma que nos permita indicar si admitimos números decimales o no, la propiedad podría llamarse ConDecimales y ser del tipo boolean, de forma que si el valor que contiene es true, admitirá decimales (tal como está ahora) y si vale false, no admitirá decimales (ni el separador de miles).

21- Sigue este link para ver una de las posibles soluciones.


Índice de ejemplos del University Tour 2003-2004

 

la Luna del Guille o... el Guille que está en la Luna... tanto monta...