Botones Personalizados
[Formas con diseño avanzado]

Fecha: 24/May/2005 (17-Mayo-2005)
Autor: Ewing Morales (ewingmarx@yahoo.com)

 


Seguramente alguna vez has intentado darle un toque personal a tus aplicaciones con botones personalizados ya sea con imágenes o solo dándoles una forma no convencional, pero claro, te has topado con la restricciones de Windows para estos casos, y al final has escogió alguna de las opciones mas comunes, a un botón le agregas una imagen o creas tu propio botón en base a imágenes superpuestas, pero ambas aunque no quieras aceptarlo tienen el mismo problema, el área activa siempre es RECTANGULAR.

Pero el reinado de las formas rectangulares ha llegado a su fin, y ahora podrás agregar botones de cualquier forma a tus formularios. Pero no solo es apariencia, la respuesta a los eventos del mouse solamente ocurren dentro la forma del botón, no en la clásica área rectangular. ¿Clásica? Claro, si tienes algún tema de Win XP comprueba acercando cuidadosamente tu mouse a alguno de los botones de Windows e identifica el área por ti mismo.

Por si no lo sabes, esta es la segunda parte de la entrega de interfaces avanzadas:

  • La primera les mostrara como hacer que las aplicaciones se vean como uno quiere y no como Windows puede (chécalo acá).

  • La segunda mostrara como hacer un botón realmente personalizado por medio de una imagen.
  • [Segunda Parte] Botones personalizados

    Les quiero mostrar una aplicación final que utiliza estos botones, en la imagen, algunos ejemplos de botones que se pueden crear no están atenuados:



    Muy bien, pero ¿como hacerlo?
    Primero que nada vamos a ver como crear un control personal (por si aun no sabes crearlo) y en paralelo estaremos aprendiendo como hacerlo personalizado.

    Descripción de componentes

    1) Primero necesitamos definir un botón predeterminado que se va a pintar por omisión cuando agregues este control a un formulario. Aunque aquí se muestran como una sola imagen, la idea es crear cuatro imágenes del mismo tamaño que permitan presentar al botón en sus tres estados (cuando el mouse esta fuera de el, cuando esta sobre el y cuando se le presiona), la cuarta imagen define una mascara que define el área activa del botón, es decir, solo se colectarán eventos del botón cuando el mouse se encuentre sobre esta área activa.

    2) Necesitamos crear un proyecto de tipo "Librería de Control de Windows". Y el tamaño de la forma del control la definiremos en 150 por 150 píxeles, es raro pensar en botones muy grandes en el formulario, de cualquier manera el tamaño no esta limitado en este proyecto.

    3) Necesitamos agregar ahora cuatro controles Picture box para cada una de las imágenes de estados del botón. Es preferible que si quieren que su imagen se vea como ustedes la diseñan debe ser almacenada en formato PNG y con un fondo transparente. También es importante que la imagen destinada a la mascara tenga fondo blanco y la mascara en si misma sea dibujada en color negro. Al final, vamos a dejar nuestra forma del control de la siguiente manera.

    Nuestras imágenes son de 24 x 24 píxeles, y los picture box tendrán la propiedad SizeMode configurada como AutoSize, la posición del primer picture box será en las coordenadas 0,0 y el nombre de este lo definiremos como botón. Agregamos los botones tal como se muestra en la imagen anterior tomando en cuenta la imagen de acuerdo al nombre del control picture box.

    4) Ahora ya esta hecho todo lo que debes hacer en la interfaz gráfica, ahora trabajaremos con la parte lógica, así que echemos un vistazo al código. Este esta compuesto de cuatro regiones: la región del código generado por el Visual Studio para los controles, una región en la que se definen las variables y funciones para que nuestro botón responda correctamente, una región en la que se definen las acciones de los objetos de nuestro control y por ultimo una región en la que definimos métodos y propiedades de este botón personalizado.

    Propiedades y métodos de un control personalizado

    El siguiente código muestra como puedes asignar una función que definas a un método del control personalizado, además cuatro propiedades básicas del control. Como puedes notar, las palabras Category y Description te permiten categorizar en la ventana de propiedades y eventos las que tu defines en el control.

    #region Metodos, propiedades y variables
    private Image imgMask;
    private Image imgOut;
    private Image imgIn;
    private Image imgClick;
    private TimeSpan timeToWaitAfterClick = new TimeSpan(0,0,0,0,100);
    
    public delegate void ClickEventHandler(object sender, System.EventArgs e);
    [Category("Action")]
    [Description("Occurs when the shaped button is clicked.")]
    public event ClickEventHandler shapedButtonClick;
    private void boton_Click(object sender, System.EventArgs e)
    {
        if ( shapedButtonClick != null )
        {
            boton.Image = bIn.Image;
            Application.DoEvents();
            System.Threading.Thread.Sleep(timeToWaitAfterClick);
            shapedButtonClick(this, e);
        }
    }
    
    [Category("Appearance")]
    [Description("Sets the image mask to define the shape of the button.")]
    public Image imageForMask 
    {
        get
        {
            return imgMask;
        }
        set
        {
            imgMask = value;
            leeBotonMask();
        }
    }
    
    [Category("Appearance")]
    [Description("Sets the appearance of the button when the mouse is out of it.")]
    public Image imageForMouseOut 
    {
        get
        {
            return imgOut;
        }
        set
        {
            imgOut = value;
            leeBotonMask();
        }
    }    
    
    [Category("Appearance")]
    [Description("Sets the appearance of the button when the mouse is over it.")]
    public Image imageForMouseIn 
    {
        get
        {
            return imgIn;
        }
        set
        {
            imgIn = value;
            leeBotonMask();
        }
    }
    
    [Category("Appearance")]
    [Description("Sets the appearance of the button when it is clicked.")]
    public Image imageForMouseClick 
    {
        get
        {
            return imgClick;
        }
        set
        {
            imgClick = value;
            leeBotonMask();
        }
    }
    
    [Category("Behavior")]
    [Description("Sets the number of milliseconds to sleep after the click action (999 as maximum).")]
    public int timeAfterClick 
    {
        get
        {
            return timeToWaitAfterClick.Milliseconds;
        }
        set
        {
            timeToWaitAfterClick = TimeSpan.FromMilliseconds(value);
            if (value >= 1000)
            timeToWaitAfterClick = TimeSpan.FromMilliseconds(999);
            if (value < 0)
            timeToWaitAfterClick = TimeSpan.FromMilliseconds(0);
        }
    }
    #endregion

    Variables y funciones auxiliares

    Este código define la variable mas importante del control, un ArrayList llamado puntosActivos, el cual almacenará cada uno de los puntos negros de la mascará ¿Para qué? Antes de que el objeto botón haga caso a los métodos referentes a la acción del puntero del ratón, preguntará si el punto sobre el cual esta ubicado el puntero es uno de los puntos activos, si es así, la acción puede proceder, en caso contrario, la acción es omitida. En el código que se muestra a continuación también puedes ver como se llena este ArrayList, la función leeBotonMask realiza ésta tarea y siempre está evaluando la mascará si haces algún cambio en las propiedades del control.

    #region Variables y funciones axiliares
    private System.Collections.ArrayList puntosActivos =
    new ArrayList();
    private Boolean presionado = false;
    
    private void leeBotonMask()
    {
        this.boton.Image = imgMask;
        this.bOut.Image = imgOut;
        this.bIn.Image = imgIn;
        this.bClick.Image = imgClick;
    
        puntosActivos.Clear();
        this.Width = boton.Image.Width;
        this.Height = boton.Image.Height;
        Bitmap bmp = new Bitmap(boton.Image);
        Color cActual, negro = Color.FromArgb(255,0,0,0);
        int i, j;
        for (i = 1; i < bmp.Height; i++)
        {
            for (j = 1; j < bmp.Width; j++)
            {
                cActual = bmp.GetPixel(j, i);
                if (cActual == negro) 
                {
                    puntosActivos.Add(new Point(j, i));
                }
            }
        }
        foreach (Point p in puntosActivos)
        {
            bmp.SetPixel(p.X,p.Y,Color.LightSteelBlue);
        }
        boton.Image = bOut.Image;
    }
    #endregion

    Acciones de Objetos

    Por último, vemos las acciones del objeto botón, cuando se carga en un nuevo formulario (método que solo debe ocurrir una vez), cuando el puntero del ratón está sobre él, cuando está fuera de él, cuando se presiona el botón del ratón sobre él y cuando este botón es liberado. Como platiqué anteriormente se verifica que el ratón este sobre un área activa definida por la mascara y además controla el estado del click del ratón. Verán que el método Click como tal, esta siendo llamado por el método MouseUp (Ver propiedades y métodos).

    #region Acciones de Objetos
    private void shapedButton_Load(object sender, System.EventArgs e)
    {
        if (puntosActivos != null && puntosActivos.Count > 0)
        {
            return;
        }
        imgMask        = this.boton.Image;
        imgOut        = this.bOut.Image;
        imgIn        = this.bIn.Image;
        imgClick    = this.bClick.Image;
    
        leeBotonMask();
    }
    
    private void boton_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        Point pOver = new Point(e.X, e.Y);
        if ( puntosActivos.IndexOf(pOver) < 0 )
        {
            boton.Image = bOut.Image;
            return;
        }
        if ( presionado == false )
        boton.Image = bIn.Image;
        else
        boton.Image = bClick.Image;
    }
    
    private void boton_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        Point pOver = new Point(e.X, e.Y);
        if ( puntosActivos.IndexOf(pOver) < 0 )
        {
            return;
        }
        boton.Image = bClick.Image;
        presionado = true;
    }
    
    private void boton_MouseLeave(object sender, System.EventArgs e)
    {
        boton.Image = bOut.Image;
    }
    
    private void boton_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        Point pOver = new Point(e.X, e.Y);
        presionado = false;
        if ( puntosActivos.IndexOf(pOver) < 0 )
        {
            return;
        }
        this.boton_Click(sender, System.EventArgs.Empty);
    }
    
    #endregion

    Ahora asigna los eventos MouseDown, MouseLeave, MouseMove y MouseUp del picture box llamado botón a su correspondiente función e igualmente hazlo con el evento Load del formulario (del control).

    Controles de usuario transparentes

    Te has dado cuenta que por omisión no se puede definir en un control de usuario la propiedad de color de fondo como transparente. Bueno para poder hacer esto solo agrega en constructor de clase la línea: SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor,true);

    Como se ve en el siguiente ejemplo, el constructor debe quedar así:

    public shapedButton()
    {
        // This call is required by the Windows.Forms Form Designer.
        InitializeComponent();
    
        // TODO: Add any initialization after the InitComponent call
        SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor,true);
    }

    ¿Cómo se usa?

    Compilen su proyecto, lo cual les va a generar una DLL. Creen un nuevo proyecto de tipo Aplicación de Windows. Copien a su directorio de proyecto la DLL generada por el control personalizado (no es necesario pero acuérdense donde esta si no hacen esta copia). Agréguenlo a la barra de herramientas ubicando la DLL con el explorador de componentes .NET. Agreguen también a las referencias del proyecto esta DLL y así mismo en el código del proyecto definan el uso de este componente.

    En la aplicación de ejemplo para el uso de este control personalizado mostramos el control que se pinta por default, uno que mostrará un MessageBox y otro que emula la opción cerrar.


    Por ahora es todo para esta entrega. Esperen pronto algunas más. Dudas y comentarios del tema diríjanlas a mi correo. Saludos.


    Fichero con el código de ejemplo: ewing_boton_personalizado.zip - 104 KB


    ir al índice