lunes, 20 de agosto de 2007

¿Cómo se utilizan los eventos en .net?

En un post anterior hablaba acerca de los delegados y su función, por si no lo leyeron o no lo recuerda (o no les intersa leerlo) lo resumo en pocas palabras. Los delegados son el mecanísmo por el cual indicamos cómo debe ser un método para poder interactuar con un evento o con un método que acepte un método como parámetro. Dicho más claro, podemos utilizar un método como parámetro de otro método, para indicar cómo debe ser el método parámetro utilizamos un delegado. Para los eventos es lo mismo, podemos asociar métodos a un evento, para indicar cómo deben ser los métodos utilizamos delagados.

¿Qué es un evento?

Un evento es un suceso, en .net es la acción de invocar un método de una clase cliente sin conocer cuál es este método. Por ejemplo un objeto B lanza su evento E1, ya que E1 fue asociado a el método M1 del objeto A este método es disparado al disparar el evento, sin que el objeto B lo sepa.

¿Cómo lo hacemos?

Bien, si nosotros desde nuestra clase servidor necesitamos informar a una clase cliente que algo ocurrió pero no conocemos a la clase cliente lo que hacemos es crear un evento. Un evento se utiliza asociado a un delegado, definimos un delegado que indica cómo debe ser el método que vamos a invocar y luego el evento que lo utiliza, a partir de esto la clase cliente puede asociar cualquier método que cumpla con el delegado al evento, para que nosotros desde la clase servidor disparemos el eventos y éste llame al método, pero sin saber a qué metodo llama, es más, puede llamar a múltiples métodos de múltiples clases sin saberlo, pero basta de charla veamos un ejemplo.

Primer ejemplo

Un caso típico de la utlización de eventos son los Winform, es éstos los eventos se utlizan para que un objeto, por ejemplo un botón, informe al formulario que ha sido pulsado, hagamos la siguiente prueba, creemos un proyecto Winform y agregemos un botón el formulario que aparece por defecto.

Si hacemos doble click sobre el botón el IDE nos envía al código.

   1:  private void button1_Click(object sender, EventArgs e) 
   2:  {
   3:  }

Aunque pocas veces nos detenemos a pensarlo, este método se genera automáticamente y es asociado al evento Click del botón sin que nos enteremos, abramos el archivo Form1.Designer.cs Si vamos al método InitilizeComponent vemos el siguiente código

   1:  // button1 
   2:  // this.button1.Location = new System.Drawing.Point(24, 28); 
   3:  this.button1.Name = "button1"; 
   4:  this.button1.Size = new System.Drawing.Size(75, 23); 
   5:  this.button1.TabIndex = 0; 
   6:  this.button1.Text = "button1"; 
   7:  this.button1.UseVisualStyleBackColor = true; 
   8:  this.button1.Click += new System.EventHandler(this.button1_Click); 
   9:  // // Form1 // 

La línea donde se encuentra la mágia es esta
this.button1.Click += new System.EventHandler(this.button1_Click);

Esto nos dice que el objeto button1 tiene su evento Click asociado el método button1_Click, ahora ¿qué significa System.EventHandler?, los más suspicaces adivinan que se trata de un delegado y es verdad, si nos ponemos sobre el código y presionamos F12 vemos la definición

public delegate void EventHandler(object sender, EventArgs e);

Es un delegado, en el cual se define que los métodos que lo utilicen debe devolver void y recibir dos parámetros. Hasta ahora es muy claro, se define un delegado con una firma tal y se asocia un método que cumpla con dicho delegado al evento, perfecto, pero cómo se declara el evento?.

Declarando un evento

Ponemos el cursos sobre Click y presionamos F12, saltamos al código de la clase Component (porque un botón hereda de ella) y vemos el siguiente código
public event EventHandler Click

Es la definición del evento, noten que después de la palabra event se define qué delegado se utliza, para poder asociar el evento a cualquier método que cumpla con el mismo, por último el nombre del evento, en este caso Click.

Asociando más métodos a los eventos

Por lo tanto podemos asociar cualquier método que cumpla con el delegado al evento, es cierto, hagamos la prueba, definamos un evento del siguiente modo

   1:  private void Form1_Load(object sender, EventArgs e) 
   2:  { 
   3:    this.button1.Click += MiMetodo; 
   4:  } 
   5:  private void MiMetodo(object sender, EventArgs e) 
   6:  { 
   7:    MessageBox.Show("Evento generado por " + sender.ToString()); 
   8:  }

Nuestro método "MiMetodo" cumple con el delegado EventHandler y dentro simplemente muestra un mensaje. en la línea this.button1.Click += MiMetodo asociamos el método al evento, noten que no creamos un objeto EventHandler, no es necesario gracias a la inferencia de tipos, es decir, el compilador nos ahorra el trabajo de hacerlo porque comprueba que nuestro método cumple con el delegado esperado, se puede hacer de cualquiera de las dos formas, es cuestión de gustos.

Si lo corremos vemos lo siguiente

Mágico, se ejecutó nuestro método, y también se ejecuto Button1_OnClick, y se ejecutarán todos los que se encuentren asociados, es más un método puede encontrarse asociado a múltiples eventos y un evento a múltiples métodos.

Un ejemplo desde cero

Hagamos un ejemplo desde cero para cerrar el concepto Supongamos que queremos que un formulario A al presionar un botón abra un formulario B, este formulario B tiene un botón que al presionarlo escribe un texto en un textbox del formulario A. ¿Cómo lo hacemos?. Primero que nada identifiquemos quién es de los dos el que dispara el evento y quién recibe el evento. Evidentemente si desde el formulario B necesitamos que ocurra algo en el A es claro que el formulario B llama al A a través de un evento, por lo tanto el evento debe encontrarse definido en el formulario B y el método que se asocia al evento en el A. Perfecto, hay algo más, necesitamos que cuando B llame al método de A le pase un texto para que se imprima, entonces el delegado tiene que tener un parámetro, para que el método reciba el texto.

Definiendo el delegado

Como dije antes necesitamos pasarle al método un texto, por lo tanto debe recibir un parámetro del tipo string y no regresar nada, entonces creamos otro formulario y agregamos el siguiente código:

   1:      public partial class Form2 : Form
   2:      {
   3:          public delegate void DelegadoMensaje(string mensaje);
   4:          public event DelegadoMensaje MiEvento;
   5:   
   6:          private void button1_Click(object sender, EventArgs e)
   7:          {
   8:              this.MiEvento("Hola evento con parámetros!!!");
   9:          }
  10:   
  11:          public Form2()
  12:          {
  13:              InitializeComponent();
  14:          }
  15:   
  16:          private void Form2_Load(object sender, EventArgs e)
  17:          {
  18:   
  19:          }
  20:   
  21:      }

Vamos paso a paso, en la línea 3 definimos el delegado para indicar cómo deben ser los métodos que se asocien al eventos, en este caso serán métodos que reciban un texto como parámetro y no retornen nada. En la línea 4 definimos el evento utilizando el delegado. Luego en la línea 8 dentro del evento que dispara el botón llamamos al evento y le pasamos un texto, en este momento se invocarán todos los métodos que hayan sido asociados a él.

Falta llamar desde el Form1 al Form2 y asociar un método al evento del Form2. Hagámoslo.

   1:      public partial class Form1 : Form
   2:      {
   3:          Form2 form2 = new Form2();
   4:   
   5:          public Form1()
   6:          {
   7:              InitializeComponent();
   8:          }
   9:   
  10:          private void button1_Click(object sender, EventArgs e)
  11:          {
  12:              form2.Show();
  13:          }
  14:   
  15:          private void Form1_Load(object sender, EventArgs e)
  16:          {
  17:              //this.button1.Click += MiMetodo;
  18:              form2.MiEvento += new Form2.DelegadoMensaje(form2_MiEvento);
  19:          }
  20:   
  21:          void form2_MiEvento(string mensaje)
  22:          {
  23:              this.label1.Text = mensaje;
  24:          }
  25:   
  26:          private void MiMetodo(object sender, EventArgs e)
  27:          {
  28:              MessageBox.Show("Evento generado por " + sender.ToString());
  29:          }
  30:   
  31:      }

En la línea 3 creamos un objeto del tipo Form2, en la línea 18 asociamos el método form2_MiEvento al evento, y luego en la línea 23 actualizamos el valor de la etiqueta, por lo tanto, el método form2_MiEvento va a ser invocado desde el Form2 cuando se presione el botón. Vamos a probarlo.

Funciona, perfecto, esto es sólo una introducción, hay muchas cosas interesantes acerca de los eventos, se pueden utilizar en aplicaciones de consola, en ASP.NET. Nos vemos en la próxima.

9 comentarios:

nando dijo...

MUY buena nota Leonardo!!!. Muy clara y simple sobre todas las cosas!. Aspectos que hoy en día son muy dificiles de hallar.
Me ha servido de mucho!

No dejes de escribir!!

Leonardo Micheloni dijo...

Gracias Nando, la idea es tener recursos en nuestro idioma para que la comunidad de habla hispana siga acercándose a .net, es una lástima que muchos colegas escriban sus blogs en inglés, realmente no entiendo, si quieren apoyar a la comunidad o conseguir publicidad a partir de que si escriben en inglés entran más personas, en fin, gracias una vez más. Saludos.

daniel dijo...

Gracias por este tutorial. Soy un recién llegado a .NET y es todo un lujo encontrarse textos como el tuyo, claros y en español.

Leonardo Micheloni dijo...

Gracias Daniel, nuevamente digo que la idea es esa, desde mi humilde posición poder dar una mano a la comunidad de habla hispana, saludos.

AlbertoC dijo...

Me parece bueno el ejemplo. Pero yo tengo la duda de cuál es la utilidad de utilizar este mecanismo. No sería más fácil?:

Public Class Form1
{
public void Mostrar(string msj)
{this.label1.text = msj;}
}
Public Class Form2
{
private void button1_Click(...)
{
Form1 form1 = new Form1();
form1.Mostrar("Hola evento");
}
}

No es más fácil así? Entonces cuál es la ventaja de hacerlo con delegados? Hasta que no comprenda esto, nunca comprenderé los delegados.

Gracias.

Leonardo Micheloni dijo...

Gracias por el comentario.
La idea de este mecanismo no se ve mucho porque es un ejemplo. Pero es muy potente, pensá en los formularios de windows y el modelo de eventos que utilizan, o en asp.net y los eventos. Te da muchas flexibilidad y potencia, otro ejemplo es un timer, se te ocurre una mejor forma de informar que el timer se disparó que con un evento?, parece medio raro al principio, pero te repito, en cuanto lo entendés y te acostumbrás te das cuenta que es muy interesante.
Si te interesan otros ejemplo, ahora se me ocurre nombrarte los web service, en este caso si los llamás de modo asincrónico te avisan por un delegado en cuanto terminan, o sea, se dispara un evento, sino no podrías hacerlo de un modo tan abstracto. Saludos.

MARIA dijo...

Hola es justo lo que necesito pero al implementarlo me sale un error
Referencia a objeto no establecida como instancia de un objeto. en la linea this.MiEvento("Hola evento con parámetros!!!");
Si alguien me puede ayudar quedaria muy agradecida

BYE

Malw Dark dijo...

¡Genial! Era justo lo que estaba buscando.
Thx :)

Leonardo Micheloni dijo...

Gracias por el comentario, estoy para ayudarte :) . Abrazo.