sábado, 18 de septiembre de 2010

[WinForms] - Mover UserControl

 

Introducción

El objetivo de este articulo será el revisar algunos aspectos en la creación de User Controls, como ser propiedades, eventos, definición de argumentos en los eventos, propiedades con enumeración.

Consistirá en un control en donde se pueda definir una imagen y a esta asociarle un texto descriptivo, la idea es que el usuario pueda mover el control por la pantalla para posicionarlo de forma correcta.

Contara con al opción para cambiar el texto del label en runtime, además de permitir asignar un estado que bloquear la movilidad.

Definición de las propiedades

El control contara con propiedades que definen estados y aspectos visuales, también habrá otras que solo podrán ser usadas solo desde código, como ser el “HasImage”, la cual indica si hay una imagen asociada al control, esta ha sido marcada con el atributo “Browsable” en falso para evitar que sea visualizada en la cuadro de propiedades.

[Browsable(false)]
public bool HasImage
{
    get { return _image != null; }
}


private Image _image;

[Browsable(true)]
[Category("Custom")]
public Image Image 
{
    get { return _image; }
    set { _image = value; }
}

private string _texto;

[Browsable(true)]
[Category("Custom")]
public string Texto
{
    get { return _texto; }
    set 
    { 
        _texto = value;
        lblMensaje.Text = _texto;
    }
}


private EnumEstado _estado;

[Browsable(true)]
[Category("Custom")]
public EnumEstado Estado
{
    get { return _estado; }
    set
    {
        _estado = value;
    }
}

También se definen el atributo “Category”, para que sean agrupadas todas juntas cuando se visualicen en el cuadro de propiedades, lo cual facilita la búsqueda.

Evento MouseMove

El permitir que un control tenga la capacidad de movimiento, no es tan directo como parece, con solo asignar el evento MouseMove en el formulario no alcanza para que esto funcione.

O sea hacer esto, no es suficiente:

public void testUserControl1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        TestUserControl us = sender as TestUserControl;
        if (us != null && us.HasImage)
        {
            us.Left += e.X;
            us.Top += e.Y;
        }
    }
}

Si bien al arrastrar el mouse en las zonas libres del User Control se lanzara el evento, si por casualidad el puntero del mouse se sitúa por sobre un control definido en el User Control, el evento se produce en el control interno, sin ser elevado hacia el formulario.

Pero este problema tiene solución, y consiste en atrapar los eventos de los controles internos del User Control que se esta desarrollando y elevar el evento hacia afuera:

public new event MouseEventHandler MouseMove;

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (this.Estado == EnumEstado.Inactivo)
        return;

    if (this.MouseMove != null)
        this.MouseMove(this, e);
}

private void label1_MouseMove(object sender, MouseEventArgs e)
{
    if (this.Estado == EnumEstado.Inactivo)
        return;

    if (this.MouseMove != null)
        this.MouseMove(this, e);
}

protected override void OnMouseMove(MouseEventArgs e)
{
    if (this.Estado == EnumEstado.Inactivo)
        return;

    if (this.MouseMove != null)
        this.MouseMove(this, e);
}

La primer línea de este código define el evento MouseMove, pero hay que remarcar que el User Control de por si ya posee este evento lo cual trae conflicto al definir uno propio, es por eso que se usa el modificado “new” en la declaración, allí se esta forzando una redefinición del evento.

El ultimo método donde se realiza un override del OnMouseMove, permite tomar la acción cuando el puntero del mouse se sitúa en una zona libre del User Control, y de esta forma agregarle lógica especializada, como es en este caso que al esta en un estado Inactivo no permita el movimiento.

Los dos métodos restantes permiten capturar los eventos MouseMove de los controles internos, en este caso un PictureBox y un Label, también se la aplica cierta lógica de estados y se lanza el evento hacia el formulario, pero en este caso no se usa un override, sino que se adjunta el evento en la inicialización del User Control, asignado el handler del evento:

public TestUserControl()
 {
     InitializeComponent();

     pictureBox1.MouseMove += new MouseEventHandler(pictureBox1_MouseMove);
     lblMensaje.MouseDown += new MouseEventHandler(label1_MouseMove);
     
     ModoEdicion(false);

     this.Estado = EnumEstado.Activo;
     this.Texto = this.Name;

 }

Cambio del Texto

La acción de cambio de Texto implica dos operaciones, manejo de visibilidad de los controles, el envió del evento informando del cambio al formulario.

Para llevar a acabo esta operación se contara un con menú contextual que será desplegado al realizar click con el botón derecho del mouse sobre el User Control.

private void ModoEdicion(bool modo)
{
    txtMensaje.Visible = modo;
    mnuAceptar.Visible = modo;
    mnuAceptar.Visible = modo;
    mnuCancelar.Visible = modo;

    lblMensaje.Visible = !modo;
    mnuCambiarTexto.Visible = !modo;
}

private void mnuCambiarTexto_Click(object sender, EventArgs e)
{
    ModoEdicion(true);

    txtMensaje.Text = _texto;
    txtMensaje.Select();
    txtMensaje.SelectAll();
}

private void mnuAceptar_Click(object sender, EventArgs e)
{
    this.Texto = txtMensaje.Text;

    ModoEdicion(false);

    if (CambioMensaje != null)
        CambioMensaje(this, new MensajeEventArgs(_texto));
}

private void mnuCancelar_Click(object sender, EventArgs e)
{
    ModoEdicion(false);
}

El primer método simplemente cumple la función de implementar como el cambio de estado afecta a la visibilidad de los controles, inicialmente se visualiza lo escrito en el Label, y una modalidad de edición se despliega el TextBox para ingresar la nueva descripción.

Los demás eventos pertenecen a los ítems del menú, que realizaran las acciones necesarias para el edición, por ejemplo la opción “mnuAceptar”, no solo asigna los escrito a la propiedad, sino que además lanza un evento informando al formulario que ha cambiado el mensaje asignado al control.

El formulario por su parte se adjunta al evento expuesto:

private void Form1_Load(object sender, EventArgs e)
{
    testUserControl1.CambioMensaje += new CambioMensajeEventHandler(testUserControl1_CambioMensaje);
}

void testUserControl1_CambioMensaje(object sender, MensajeEventArgs e)
{
    TestUserControl usrcontrol = sender as TestUserControl;

    if (usrcontrol != null)
        MessageBox.Show(string.Format("El texto ha cambiado: {0}", e.Texto));
}

Algo interesante es como se hace uso del argumento “e” den método del evento para tomar el valor ingresado en el control y desplegarlo en el MessageBox, este se logra mediante la definición de un EventArgs creado especialmente para este fin.

La declaración del evento se logra mediante dos simples líneas:

public delegate void CambioMensajeEventHandler(object sender, MensajeEventArgs e);

public partial class TestUserControl : UserControl
{

	public event CambioMensajeEventHandler CambioMensaje;
		
	.
	.
	.
}

Pero como comentamos el delegate hace uso de un argumento en el evento especial, el cual se define al final del control:

public class MensajeEventArgs : EventArgs
{
    public MensajeEventArgs(string texto)
    {
        _texto = texto;
    }

    private string _texto;

    public string Texto
    {
        get { return _texto; }
    }
}

Ejemplo de código

El código fue desarrollado usando Visual Studio 2008.

[C#]
[VB.NET]

5 comentarios:

RobertoMartin dijo...

Que tal Leandro! Primero te agradezco por todos estos tutoriales, ya he utilizados varios de ellos.

Tengo un pequeño problema para agrupar las priedades.
[Browsable(true)]
[Category("ColoresPersonales")]
Pongo estas dos lineas sobre cada propiedad pero cuando agrego el userControl al formulario, las propiedades no se muestran agrupadas.
Descargue el codigo fuente que subiste y ahí tampoco las muestra agrupadas.
Que puede ser? Hay que configurar algo aparte?

Gracias y disculpa las molestias!

Leandro Tuttini dijo...

hola RobertoMartin

pero estas habilitando la vista agrupada por el atributo en el panel de propiedades

imagen

o sea debes habilitar el icono que en el imagen visualizas seleccionado para agrupar

saludos

Carlos dijo...

Hola Roberto , como se haria para poder mover la imagen de la tierra en tiempo de diseño.

Leandro Tuttini dijo...

hola Carlos

en tiempo de diseño se trata de un user control lo mueves como cualquier otro control que quieres ubicar en el diseñador

pero una vez que lo ubicaste alli en ejecucion queda fijo, solo con la funcionalidad explciada en el articulo puedes desplazarlo

saludos

RobertoMartin dijo...

Gracias!!! no me había dado cuenta de eso!