Este artículo describe cómo generar por código tus propios eventos y también cómo
programar la respuesta a estos.
Delegados
Un delegado es un objeto al que otros objetos ceden (delegan) la ejecución de su código. También se conocen como punteros a función con seguridad de tipos.
Al instanciar un delegado, se asocia con un método de instancia o compartido de un objeto, y posteriormente, durante la ejecución, será el delegado el que se encargue de ejecutar dicho método y no el propio objeto. También se pueden asociar los delegados con procedimientos Sub o Function de módulos.
‘delegado sin parámetros
Public Delegate Sub EventoMoverPiezaHandler()
‘delegado con parámetros
Public Delegate Sub EventoMoverPiezaHandler(ByVal
sender As Object,
ByVal e As
MuevePiezaArgs)
‘delegado de tipo función
Public Delegate Function EventoMoverPiezaHandler(ByVal sender As Object, ByVal e As MuevePiezaArgs) As string
Creación
de Delegados.
Seguidamente, y ya en un procedimiento, declaramos una variable correspondiente al tipo del delegado. A continuación, conectamos el delegado con el procedimiento que posteriormente deberá ejecutar, empleando la palabra clave AddressOf, seguida del nombre del procedimiento.
AddressOf devuelve un puntero o dirección de entrada al procedimiento que será el que utilice el delegado para saber la ubicación del procedimiento que debe ejecutar. Por último, para ejecutar el procedimiento al que apunta el delegado, llamaremos a su método Invoke(). A continuación se muestran dos técnicas para crear un delegado; la segunda es más simple pero en ambas el resultado es el mismo.
Public Delegate Sub EventoMoverPiezaHandler()
'Modo 1
Sub Main1()
'Declarar un delegado
Dim
loDelegTexto As EventoMoverPiezaHandler
'Obtener la dirección de
procedimiento a ejecutar y asignarla al
delegado
loDelegTexto = AddressOf
MostrarTexto
'ejecutar
procedimiento a través del delegado
loDelegTexto.Invoke()
End Sub
'Modo 2
Sub Main()
' Declarar un delegado y asociar con una dirección o
puntero a un procedimiento
Dim loDelegTexto As New EventoMoverPiezaHandler(AddressOf
MostrarTexto)
loDelegTexto.Invoke()
End Sub
'Este sera el
procedimiento invocado (ejecutado) por el delegado
Public Sub MostrarTexto()
MsgBox("Prueba
de Delegados")
End Sub
Public Delegate Sub EventoMoverPiezaHandler()
Sub Main1()
'Declarar un delegado
Dim
loDelegMensa As EventoMoverPiezaHandler
loDelegMensa = AddressOf
MostrarTexto
loDelegMensa.Invoke()
loDelegMensa = AddressOf
VisualizarFecha
loDelegMensa.Invoke()
End Sub
'Procedimientos ejecutados por los delegados
Public Sub MostrarTexto()
MsgBox("Prueba
de Delegados")
End Sub
Public Sub
VisualizarFecha()
Dim ldtFecha As Date
ldtFecha
= Date.Today
MsgBox("Fecha Actual", , ldtFecha)
End Sub
La interfaz de usuario de
Microsoft Windows está orientada a eventos. El flujo de control del programa se basa principalmente en los eventos
del control de Windows Forms. En este artículo, vamos a crear un control que
aparece en la caja de herramientas. Se puede arrastrar
este control en un formulario tal y como cualquiera de los controles
integrados en Windows. El control tendrá eventos
que se pueden optar por responder o ignorar en su código. Vamos
a usar las excepciones para
indicar que algo ha ido mal durante la ejecución.
Las excepciones no pueden ser ignoradas.
Usando el manejador de excepciones, el código puede tratar de reparar el problema o puede salir del programa.
La tarea en este artículo es
crear una representación visual de un tablero de ajedrez, en el que podemos
arrastrar y soltar las diferentes piezas sobre dicho tablero.
Un somero
análisis textual del problema conduce al diseño de clases siguiente. En este
caso, el objeto de la pantalla está representado por la clase de Windows Forms,
que contiene un tablero y las diferentes piezas. Las piezas se mueven encima
del tablero que es un fondo estático pero cada casilla tiene unas coordenadas
fijas “Posición”. Se muestra en el siguiente diagrama de clases UML.
Se
introduce un nuevo elemento de UML, el rombo
sólido. El rombo sólido indica una relación llamada "Composición"
en la terminología orientada a objetos. La composición es una relación en la
que algunos los objetos son "partes" de otro objeto. Lleva el sentido
de que el objeto no puede existir sin los otros. Todos los objetos son creados y destruidos como una unidad.
Este
análisis refleja sólo lo que está estático en el problema, tales como la
ubicación de las piezas en el tablero.
No se describen los movimientos y las capturas de piezas.
¿Cómo saber si se ha realizado un movimiento? Para saber lo que sucede en un programa de este tipo se necesitan eventos que son los acontecimientos, las señales que envía un objeto a otro, de que algo ha sucedido.
¿Cómo saber si se ha realizado un movimiento? Para saber lo que sucede en un programa de este tipo se necesitan eventos que son los acontecimientos, las señales que envía un objeto a otro, de que algo ha sucedido.
§ MuevePieza para la
clase de CTablero, este evento, generado por la pieza,
será recibido por el formulario para indicar la nueva posición de la pieza en el tablero.
será recibido por el formulario para indicar la nueva posición de la pieza en el tablero.
§ CapturadePieza para la
clase CPieza. Este evento se generará cuando una pieza captura a otra. La
ubicación de la pieza depende de su posición y del movimiento que realiza, si
el nuevo movimiento coincide con la posición de otra pieza, se produce una
captura y se lanza un evento CapturadePieza que pone la propiedad EnJuego de la
pieza capturada en False.
El uso de los eventos MuevePieza y CapturaPieza en el código del formulario puede coordinar el comportamiento del tablero y las piezas. En UML, los eventos se modelan como señales, que son similares a las clases. En la ilustración siguiente, una flecha de puntos marcados <<envío>> indica que en una clase particular, se genera un evento particular.
El UML
también proporciona una sintaxis para indicar que las clases de recibir los
eventos. La clase Form recibe tanto el evento MuevePieza como el CapturaPieza.
La última decisión de diseño es cómo implementar la interfaz de usuario, teniendo en cuenta el modelo de objetos. Sabemos que tiene que haber un tablero y unas piezas que se mueven sobre ese tablero como elementos visuales del formulario. Las propiedades de estos elementos visuales están íntimamente ligadas a las clases. De hecho, se pueden implementar las clases como clases derivadas de la genérica de control de Windows, la clase UserControl, lo que significa que la presentación visual y el comportamiento de un objeto son todos los contenidos en una clase. Además, el control se puede agregar a la caja de herramientas, y luego arrastrado a la forma en el diseñador de formularios. Aquí está el diseño completo UML:
Implementando la clase CTablero
Implementaremos la clase CTablero. Esta clase se deriva de la Clase UserControl de este modo el tablero pasa a ser como un control más de la caja
de herramientas, y se dibuja el tablero a sí
mismo, también dibujará las piezas encima del tablero.
Public Class CTablero
Inherits UserControl
End Class
El nuevo
control de Usuario está vacío, vamos a definir la forma y el color del nuevo
control reemplazando el método OnPaint.
No es
necesario añadir código para las propiedades del tablero pues ya están creadas
en la clase base UserControl.
Pintando
el Tablero.
Se dibuja el contorno del tablero como una serie cuadrados iguales alternando
el color de cada tesela del tablero.
Añadir constante a la clase CTablero para modificar el tamaño de las casillas.
Private Const LongitudCasilla As
Integer = 50 'debe
ser divisible entre 5
Este código introduce la palabra clave Const. El modificador constante indica que el valor
de la variable no puede ser modificado.
Los valores constantes pueden ser de cualquier tipo, pero el
compilador debe ser capaz de evaluar la
expresión a la derecha del signo
igual. Debido a que el compilador
no asigna memoria para instancias de la clase, la expresión no puede contener una declaración
new. El resultado es que los valores de referencia
constante serán nothing o null, o una cadena. En este caso se utiliza el
campo de la constante para que pueda
cambiar el tamaño y proporciones de cada
casilla al cambiar este valor. Todos
los dibujos utilizan este campo, en lugar de literales enteros,
como por ejemplo "50". El uso
del modificador const permitirá que el compilador nos
ayude, evitando accidentalmente cambiar estos valores en el código.
En el menú Ver, hacer clic en Diseñador para ver el control en el diseño del formulario.
Hacer doble clic en el control para crear el método Load en el editor de
código. Agregar este código para fijar la propiedad Height de cada casilla a 50 píxeles.
Dentro
de la clase CTablero ponemos un control para
cada casilla posteriormente iremos alternando los colores.
Private Sub CTablero_Load(ByVal sender As
System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Me.BackColor = Color.Brown
Me.Top = 50
Me.Left = 50
End Sub
Ahora pintamos el tablero en tiempo de
ejecucución haciendo que cada casilla sea un control diferente.
En la clase CTablero definimos el método Dibuja_Tablero para ubicar cada control CTablero en un lugar diferente y con el color correspondiente
Public Sub Dibuja_Tablero()
Dim casilla As New CTablero
Dim ArrCasillas As New ArrayList
Dim i As Integer
Dim par As Boolean
'Introduce en un ArrayList los 64 controles de
casilla que conforman el
tablero completo
ArrCasillas.Add(Form1.CTableroA8)
ArrCasillas.Add(Form1.CTableroB8)
ArrCasillas.Add(Form1.CTableroC8)
ArrCasillas.Add(Form1.CTableroD8)
ArrCasillas.Add(Form1.CTableroE8)
ArrCasillas.Add(Form1.CTableroF8)
ArrCasillas.Add(Form1.CTableroG8)
ArrCasillas.Add(Form1.CTableroH8)
ArrCasillas.Add(Form1.CTableroA7)
...
...
(introduce 64 controles casilla)
...
ArrCasillas.Add(Form1.CTableroF2)
ArrCasillas.Add(Form1.CTableroG2)
ArrCasillas.Add(Form1.CTableroH2)
ArrCasillas.Add(Form1.CTableroA1)
ArrCasillas.Add(Form1.CTableroB1)
ArrCasillas.Add(Form1.CTableroC1)
ArrCasillas.Add(Form1.CTableroD1)
ArrCasillas.Add(Form1.CTableroE1)
ArrCasillas.Add(Form1.CTableroF1)
ArrCasillas.Add(Form1.CTableroG1)
ArrCasillas.Add(Form1.CTableroH1)
i = 0
For Each casilla In ArrCasillas
'Asigna el color alternativamente (por filas)
If i <> 8 And i <>
16 And i <> 24 And
i <> 32 And i <> 40 And i <> 48 And
i <> 56 And i <> 64 Then
If par = True Then
par = False
Else
par = True
End If
End
If
'Llama lal método Asigna posición que ubica cada
casilla y le da el color
Asigna_Posicion(ArrCasillas(i),
par)
i = i + 1
Next
End Sub
Private Sub Asigna_Posicion(ByVal
Casilla As CTablero, ByVal
Par As Boolean)
Nombre = Mid(Casilla.Name, 9, 2)
'(para Longitud casilla = 50 Si viene de la fila
1 top = 400, fila 2 top = 350 ...Fila 8 top = 50
Casilla.Top = (LongitudCasilla * 9) - (Mid(Nombre, 2, 1) *
LongitudCasilla)
'Coloca las
casillas por Columnas
Select Case Mid(Nombre, 1, 1)
Case "A"
Casilla.Left = LongitudCasilla
Case "B"
Casilla.Left = LongitudCasilla * 2
Case "C"
Casilla.Left = LongitudCasilla * 3
Case "D"
Casilla.Left = LongitudCasilla * 4
Case "E"
Casilla.Left
= LongitudCasilla * 5
Case "F"
Casilla.Left = LongitudCasilla * 6
Case "G"
Casilla.Left = LongitudCasilla * 7
Case "H"
Casilla.Left
= LongitudCasilla * 8
End Select
'pone el
color alternativamente
If Par = True Then
Casilla.BackColor = Color.LightSalmon
Else
Casilla.BackColor = Color.Brown
End If
End Sub
El
tablero queda pintado del siguiente modo:
Si reescribimos
el método OnSizeChanged podemos
establecer la propiedad Height y la Width de la
casilla a n pixeles y para modificar
el ancho del control a nxn el
valor de una casilla. Se puede escribir el código que
sigue o usar
la funcionalidad que proporciona Visual Studio.
El código
agregado a este método, se muestra aquí:
Protected Overrides Sub OnSizeChanged(ByVal
e As System.EventArgs)
Me.Height = LongitudCasilla
Me.Width = LongitudCasilla
End Sub
Si ahora
cambiamos LongitudCasilla por 30
Private Const LongitudCasilla As
Integer = 30 'debe
ser divisible por 5
Veremos este
tablero con las casillas más pequeñas, de 30 x 30.
Implementar Eventos en una clase
En los procedimientos siguientes se describe cómo implementar un
evento en una clase. El primer
procedimiento implementa un evento que no tiene datos asociados, sino que
utiliza las clases System.EventArgs y System.EventHandler de los datos del evento y un controlador de delegado. El segundo procedimiento implementa un evento
personalizado con los datos, que
define las clases personalizadas de
los datos del evento y el controlador de eventos delegado.
Implementar un evento sin datos asociados
1. Definir un evento public en la
clase, poner en el tipo miembro de tipo de Evento el delegado System.EventHandler .
Public Class
MuevePieza
' ...
Public Event MoverPieza As
EventoMoverPiezaHandler
End Class
2. Proporcionar un método
protegido en la clase que provoca
el evento. Nombrar el método On[NombreEvento]. Provocar el evento dentro del método.
Public Class
MuevePieza
Public Event MoverPieza As EventoMoverPiezaaHandler
' El método protegido OnMoverPieza levanta el evento invocando el
delegado. El enviador es siempre la instacia actual de la clase
Protected Overridable
Sub OnMoverPieza(ByVal
e As MuevePiezaArgs)
MsgBox("Levanta el evento MoverPieza")
RaiseEvent MoverPieza(Me, e)
End Sub
End Class
3. Determinar el momento de
generar el evento en su clase. Llamar a On[NombreEvento para provocar el evento.
Public Class MuevePieza
Private
nclickpieza As Integer
= 0
Private PosicionAnterior As String = "PD"
Public
Event MoverPieza As EventoMoverPiezaaHandler
' El método protegido OnMoverPieza levanta el evento invocando el delegado.
El enviador es siempre la instacia actual de la clase
Protected Overridable Sub OnMoverPieza(ByVal e As MuevePiezaArgs)
MsgBox("Levanta el evento
MoverPieza")
RaiseEvent
MoverPieza(Me, e)
End Sub
Public Sub
Comienzo()
Dim
e As New
MuevePiezaArgs(nclickpieza, PosicionAnterior)
OnMoverPieza(e)
End
Sub
End Class
Implementar Eventos con datos específicos
1. Definir una clase que proporciona datos para el evento. Nombrar la clase como [NombreEvento]Args, y derivarla de
la clase System.EventArgs, para añadir cualquier miembro
específico.
'Esta clase define un
constructor con los argumentos del evento, en este caso NclickPieza que
contiene el Número de veces que
'se ha movido la pieza
y PosicionAnterior que contiene en eun string la posicion anterior
Public Class MuevePiezaArgs
Inherits
EventArgs
Private
nclickpieza As Integer
Private
PosicionAnterior As String
'Constructor.
Public Sub New(ByVal nclickpieza As Integer, ByVal
PosicionAnterior As String)
Me.nclickpieza
= nclickpieza
Me.PosicionAnterior
= PosicionAnterior
End Sub
End Class
2. Declarar un delegado para el evento. Nombrando
el delegado como [NombreEvento]Handler.
' Declaracion del delegado.
Public Delegate Sub
EventoMoverPiezaaHandler(ByVal sender As Object, ByVal e As
MuevePiezaArgs)
3. Definir
un evento miembro público llamado NombreEvento en su clase.
Establecer el tipo de miembro de evento
en el tipo de delegado del
evento. (Como implementar eventos sin datos asociados)
Public Class
MuevePieza
' ...
Public Event MoverPieza As EventoMoverPiezaHandler
End Class
4. Definir un
método protegido en la clase que
dispara el evento. El nombre del método On[NombreEvento. Dispara el
evento dentro del método. Esto
elimina la necesidad de manejar el
NullReferenceException que se produce
cuando se provoca un evento, pero no se han unido a él controladores
de eventos. Esta comprobación es
necesaria en este caso porque la clase
simplemente produce el evento, pero no proporciona un controlador para ello.
Public Class MuevePieza
Public Event MoverPieza As EventoMoverPiezaaHandler
' El método protegido OnMoverPieza levanta el evento invocando el
delegado. El enviador es siempre la instacia actual de la clase
Protected Overridable
Sub OnMoverPieza(ByVal
e As MuevePiezaArgs)
MsgBox("Levanta el evento MoverPieza")
RaiseEvent MoverPieza(Me, e)
End Sub
End Class
5. Determinar el
momento de levantar el evento en la
clase. Llamar a On[NombreEvento] para disparar el
evento y pasar en el caso específico de los datos mediante el uso de [NombreEvento]EventArgs.
Public Class MuevePieza
Private
nclickpieza As Integer
= 0
Private
PosicionAnterior As String
= "PD"
Public
Event MoverPieza As EventoMoverPiezaaHandler
' El método protegido OnMoverPieza levanta el evento invocando el
delegado. El enviador es siempre la instacia actual de la clase
Protected Overridable Sub OnMoverPieza(ByVal e As MuevePiezaArgs)
MsgBox("Levanta el evento
MoverPieza")
RaiseEvent
MoverPieza(Me, e)
End Sub
Public Sub
Comienzo()
Dim e As New
MuevePiezaArgs(nclickpieza, PosicionAnterior)
OnMoverPieza(e)
End Sub
End Class
Cómo dibujar una pieza sobre el tablero
1. En el Explorador de soluciones, hacer doble clic en Form1 para abrirlo en la forma diseñador.
2. Desde el área de Windows Forms del Cuadro de herramientas,
arrastre un PictureBox
control en el formulario.
3. Establecer la propiedad Name del control PictureBox a FiguraPeonBlanco, y la Propiedad SizeMode
a AutoSize.
4. Hacer click en el botón (...)
junto a la propiedad Image para seleccionar una imagen para
el peón
blanco.
5. Definimos el evento a disparar la acción desde el Form1.
5. Definimos el evento a disparar la acción desde el Form1.
El siguiente programa de ejemplo coloca un picturebox de un
peón sobre una casilla determinada al hacer click sobre él. En un programa real
esto debería colocarse en el mouse-up de un drag-and-drop pero para mostrar
cómo se dispara un evento basta con este pequeño ejemplo.
Llamada
desde el evento Click de un PictureBox1 del Form1. (Este es el único código que
va en el Form1 del programa de Ajedrez)
Private Sub PictureBox1_Click(ByVal sender As
System.Object, ByVal e As
System.EventArgs) Handles PictureBox1.Click
' Instancia el evento que recibe.
Dim Coloca As New ColocaPieza()
' Instancia el evento fuente.
Dim
Pieza As New
MuevePieza()
'Dirige el método RangoMoverPiezaa hacia el
evento de MoverPiezaa.
AddHandler
Pieza.MoverPieza, AddressOf
Coloca.RangoMoverPieza
Pieza.Comienzo()
End Sub
Finalmente se implementa la clase ColocaPieza que es la que mueve la pieza realmente.
(Todas las clases descritas van en el módulo de
la clase CPieza de nuestro programa
de ajedrez)
' La clase ColocaPieza tiene el método RangoMoverPieza que maneja el
evento mover pieza
Public Class ColocaPieza
Public Sub
RangoMoverPiezaa(ByVal sender As Object, ByVal e As
MuevePiezaArgs)
Form1.PictureBox1.Left = 200
Form1.PictureBox1.Top = 200
End Sub
End Class
Configurar Métodos de Eventos sin utilizar el Diseñador
En el
ejemplo
anterior se ha creado control de usuario con los eventos. Cuando se arrastra
una pieza en el formulario, los
eventos están disponibles en la
lista de métodos.
Pero
no es necesario utilizar el diseñador para conectar un evento caso
a las instancias de los métodos de
la clase, se puede hacer simplemente
mediante el uso de declaraciones de código. El establecimiento de métodos de eventos en el código permite:
§ Crear instancias de control en tiempo de ejecución y responder a sus eventos.
§ Cambiar el controlador de eventos para un evento determinado en tiempo de ejecución.
Se puede elegir
entre dos formas de configurar los
métodos de evento. Una vía es el uso
de la palabra clave Handles. Y en la otra forma se utiliza la instrucción AddHandler. Para
utilizar la palabra clave Handles, se debe declarar la instancia con la palabra clave WithEvents como
un campo de una clase. El problema es que no se puede utilizar la palabra clave New en
la declaración, por lo que la clase debe crear una instancia en otras partes de la clase, lo más probable en el constructor.
Una vez se declara la
clase utilizando la palabra clave
WithEvents, los eventos están
disponibles para la instancia en la lista de métodos del editor de código,
que es el método utilizado por el Diseñador de formularios.
Ejemplo completo
Esto es
un ejemplo completo de manejo de Eventos, consiste en la creación de una clase
que controla un Timer que descuenta de 10 a 0 y va mostrando en una caja de
texto el valor, cuando termina muestra Done en la caja de texto.
En el
formulario de la aplicación se pone un botón llamado Boton_Prueba y una
caja de texto llamada Text_Evento. En el Form1_Load de la aplicación se ponen
las siguientes líneas:
'Lineas de Evento
Boton_Prueba.Text = "Start"
mText = New TimerState
dentro de la clase formulario
se define el siguiente código:
Se define la variable mText
como Timer
Private WithEvents mText As
TimerState
'Al hacer click en el botón se pone el contador a 10. Se dispara al pulsar el botón Boton_Prueba
Private Sub Boton_Prueba_Click(ByVal sender As
System.Object, ByVal e As
System.EventArgs) Handles Boton_Prueba.Click
mText.StartCountdown(10.0, 0.1)
End Sub
'Este Procedimiento es el que se dispara al ejecutar el RaiseEvent
UpdateTime en la clase TimerState
Private Sub mText_UpdateTime(ByVal Countdown As Double) _
Handles mText.UpdateTime
Text_Evento.Text = Format(Countdown, "##0.0")
' Use DoEvents to allow the display to refresh.
My.Application.DoEvents()
End Sub
'Este evento se dispara cuando el
evento termina RaiseEvent finished
Private Sub mText_ChangeText() Handles mText.Finished
Text_Evento.Text = "Done"
End Sub
Y la clase que maneja el evento se declara a parte
'Clase del evento
Class TimerState
'Definición de eventos
Public Event
UpdateTime(ByVal Countdown As Double)
Public Event Finished()
'Procedimiento llamado desde el Click del Botón
Public Sub
StartCountdown(ByVal Duration As Double, ByVal Increment As
Double)
Dim Start As Double =
DateAndTime.Timer
Dim
ElapsedTime As Double
= 0
Dim SoFar As Double = 0
Do While ElapsedTime < Duration
If
ElapsedTime > SoFar + Increment Then
SoFar += Increment
'Esta línea dispara mText_UpdateTime RaiseEvent UpdateTime(Duration - SoFar)
End If
ElapsedTime
= DateAndTime.Timer - Start
Loop
RaiseEvent Finished()
End Sub
End Class
No hay comentarios:
Publicar un comentario