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] 

15 comentarios:

  1. 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!

    ResponderEliminar
  2. 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

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

    ResponderEliminar
  4. 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

    ResponderEliminar
  5. Gracias!!! no me había dado cuenta de eso!

    ResponderEliminar
  6. Hola Leandro buenas tardes, de nuevo molestandote, buen blog me saca de dudas muchas veces, me preguntaba si es que tendras un ejemplo de como pasar una matriz bidimensional (x,y) como parametro a un procedimiento almacenado a una base de datos oracle 11g, agradezco de antemano tu atencion.

    ResponderEliminar
  7. hola Franky

    no evaluaste usar un xml
    digo convertir el array a xml y pasarlo a un procedure de Oracle

    ODP.NET XML Support


    saludos

    ResponderEliminar
  8. Están caídos los enlaces de descarga de los ejemplos, podrías habilitarlos por favor!

    ResponderEliminar
  9. hola Ermis

    los links ya estan actualizados

    saludos

    ResponderEliminar
  10. Hola Leandro...No soy muy experto en el tema
    Ya baje el codigo y en verdad no comprendo bien el ejemplo...
    Yo tengo un control de botones y necesito que donde lo coloque me cargue los datos que estan en una clase ...es posible me ayudes...
    Gracias...

    ResponderEliminar
    Respuestas
    1. hola
      Pero por el planteo que realizas no tiene mucha relacion con movel el control por la pantalla en runtime
      Por lo que entiendo quieres cargar datos, pero como asignas la instancia de la clase a ese user control ?
      Defines una propiedad publica o quizas un metodo para poder pasarle la instancia de la clase que quieres visualizar
      La clase que quieres visualizar es fija o podria ser cualquier clase?
      saludos

      Eliminar
    2. Hola.
      Mi planteamiento es el siguiente tengo un usercontrol que maneja una botonera con los botones de agregar, eliminar, actualizar, lo que deseo es desde el formulario que el usercontrol poder llevarle una clase cualquiera y desde el usercontrol poder llamar otro metodo para guardar u actualizar la informacion.
      En el momento he definido propiedades y he tratado de pasar la clase pero no me lleva los datos que ingreso desde el formulario.

      Eliminar
    3. hola
      No entendi muy bien que sera ese "otro metodo" que necesitas invocar, ni tampoco que contiene esa clase cualquiera que necesita administrar
      Si puedo decirte que la forma de comunicar el user control con el form que lo contiene es por medio de metodo publicos o eventos
      Si quieres que el user control lance una accion al form podrias exponer un evento, asi el form se subscribe para recibir las acciones y poder realizar cierta operacion con la clase, o poder llamar a ese otro metodo
      Conoces como definir un evento ?
      saludos

      Eliminar
    4. Hola Leandro
      no conozco como definir un evento, es posible me expliques por favor.
      gracias..

      Eliminar
    5. hola
      La forma mas simple de definir un evento es por medio del EventHandler
      aunque tambine podrias hacerlo por medio de un delegate y el event
      Step by Step: Event handling in C#
      La idea es que el user control exponga el evento y el form se adjunte al mismo, desde el user control al presionar un control interno lanzaria este evento que defines
      saludos

      Eliminar