El enlace a datos o mejor conocido por su nombre en inglés Databinding, es una técnica que sirve para asociar y sincronizar objetos de negocios con elementos visuales (controles) en la interfaz de usuario para mostrar u obtener datos. Este mecanismo de enlace está disponible en la mayoría de los controles de Windows Forms, Windows Presentation Fundation (WPF) y WebForms ya que, todos heredan de la clase Controls, que implementa la interfaz IBindableComponent la cual nos permite que los controles se asocien a un Contexto de Enlace (Binding Context) que se crea por defecto en los formularios Windows Forms.
El enlace a datos ha estado presente desde hace mucho tiempo, pero desde la aparición de la versión 2.0 de .Net Framework ha sido mucho más fácil de usar gracias a la incorporación del componente BindingSource. Gracias a éste es más fácil enlazar controles a orígenes de datos y administrar la concurrencia y la notificación de cambios entre estos controles y los orígenes de datos, simplificando significativamente el desarrollo de aplicaciones que tengan que recuperar o persistir datos en algún tipo de base de datos.
Ahora bien, antes de comenzar a escribir sobre como hacer databinding usando los objetos/entidades que se generan con Cooperator Framework, voy a hablar un poco sobre algunos conceptos de databinding.
El enlace a datos o databinding puede hacerse de dos formas:
- Enlace a datos sencillo (Simple Databinding), y
- Enlace a datos complejo (Complex Databinding)
El enlace a datos sencillo es cuando se enlaza una propiedad de un mismo objeto u origen de datos a una propiedad de un control. La gran mayoría de los controles emplean este tipo de enlace utilizando la propiedad DataBindings y entre ellos, podemos mencionar controles como el Textbox, Label, Checkbox, Radiobutton, DateTimePicker, NumericUpDown, y Picturebox, por mencionar algunos.
El enlace a datos complejo, es el que nos permite enlazar una lista de objetos a un control para mostrar una o más propiedades de dichos objetos. La lista de controles con soporte para este tipo de databinding no tiene tanta variedad como el enlace simple, pero podemos mencionar los más importantes que son, el DataGridView, ListView, ListBox y ComboBox. Estos controles permiten el enlace a datos a través de sus propiedades DataSource y DataMember. Algunos de estos controles también necesitan las propiedades DisplayMember y ValueMember como es el caso del ListBox y el ComboBox.
Ahora bien, para que un objeto/entidad pueda servir como origen de datos y ser utilizado en el enlace a datos, éste necesita implementar al menos una de las interfaces estándar relacionadas con el enlace a datos, como lo son IList, Typed IList, IComponent, IListSource, ITypedList, IBindingList, IBindingListView, IEditableObject e IDataErrorInfo. Ejemplos de este tipo de objetos con la implementación de una o más de estas interfaces son el Dataset, Datatable, DataView, DataViewManager, DataColumn, Array y por supuesto, los objetos /entidades generados con Cooperator Framework ya que éstos implementan la interfaz IEditableObject y sus listas de objetos heredan de la clase genérica List(Of T) que a su vez implementa la interfaz IList, por lo que soportan perfectamente el databinding. Además, las ListViews implementan IBindindListView que sirve para ordenar y filtrar los elementos de la listas.
En fin, hablar del enlace a datos con más profundidad me tomaría muchas líneas en el blog ya que, tendría que hablar de muchísimas cosas como DataProviders, DataConsumers, objetos Binding, BindingContext, CurrencyManager, PropertyManager y además, hablar de para qué sirve implementar tal o cual interface relacionada con el databinding. Por tal motivo dejo este enlace que a mí me resultó bastante clarificador para entender mejor todo esto que acabo de mencionar. Está en inglés, así que si no saben inglés o les da güeva usar un traductor online ¡pues se chingaron! porque no me voy a poner a traducir ese texto nomás para solapar su flojera.
Ahora sí. Esperando que ya entiendan lo que es y con qué se come el databinding, voy a comenzar a escribir sobre como hacer databinding usando los objetos/entidades generados con el Modeler que nos proporciona Cooperator Framework.
Primero voy a hablar sobre cómo realizar el enlace a datos sencillo. Para esto utilizaré una entidad de una de mis aplicaciones (que en realidad es más compleja, por eso es una entidad, pero para facilitar la lectura del código la simplifiqué) con la que le pongo el nombre a un banco y el formulario es como el siguiente:

Y el código de carga de éste formulario es el siguiente:
- Public Class frmEdicionDeBanco
- Dim oBanco As Banco
-
- Private Sub frmEdicionDeBanco_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
- ' Si no se asignó un objeto...
- If oBanco Is Nothing Then
- ' Se quiere agregar un nuevo objeto, por lo tanto hay que crearlo así como sus objetos relacionados.
- oBanco = New Banco
- ' Cuando se trata de un objeto nuevo, se inicializa su Status a verdadero (Activo).
- oBanco.Status = True
-
- Try
- ' Se obtiene un nuevo código consecutivo para cada objeto nuevo.
- oBanco.idbanco = BancoRules.GetNextID()
-
- Catch ex As Exception
- MessageBox.Show("Ocurrió un error al intentar obtener el número consecutivo para el nuevo banco.", _
- "Error al buscar consecutivo", MessageBoxButtons.OK, MessageBoxIcon.Information)
-
- End Try
-
- Me.Text = "Agregar Banco"
-
- Else
- ' De lo contrario, se prepara el formulario para editar un objeto existente.
-
- Me.Text = "Modificar Banco"
- Me.txtId.ReadOnly = True
-
- End If
-
- ' Se enlazan controles a los BindingSources.
- BindControls()
-
- End Sub
- Private Sub BindControls()
- ' Se enlaza el BindingSource
- bsBanco.DataSource = oBanco
-
- ' Se enlazan los datos generales
- txtId.DataBindings.Add("Text", bsBanco, bancoColumn.idbanco.ToString("G"), True)
- txtNombre.DataBindings.Add("Text", bsBanco, bancoColumn.nombre.ToString("G"), True)
- txtNombreCorto.DataBindings.Add("Text", bsBanco, bancoColumn.nombrecorto.ToString("G"), True)
- chkStatus.DataBindings.Add("Checked", bsBanco, bancoColumn.status.ToString("G"), True)
-
- End Sub
Como se puede ver, al cargarse el formulario se revisa si existe alguna instancia de la entidad Banco para configurar dicha entidad y también para saber qué cadena mostrar como título del formulario, además, al final se hace lo que más nos interesa de todo el código, el llamado al procedimiento BindControls.
En el procedimiento BindControls se enlaza la entidad Banco al componente BindingSource (bsBanco), el cual no se ve en la imagen de arriba porque solamente se muestra en la bandeja de componentes del IDE de Visual Studio y es invisible en tiempo de ejecución porque no tiene interfaz de usuario. Al enlazarse, el componente BindingSource se encargará de gestionar el enlace, sincronización y notificación de cambios entre la entidad Banco y los controles y viceversa. En las siguientes líneas se hace el databinding entre las propiedades de la entidad Banco y los controles en el formulario mediante la propiedad DataBindings de cada control (como se menciona en el párrafo donde se explica que es el enlace a datos sencillo de esta entrada). El método Add() tiene muchas sobrecargas pero yo regularmente uso la que pide 4 argumentos:
- Cadena con el nombre de la propiedad del control a la que se enlazará la propiedad del objeto/entidad.
- Origen de datos u objeto/entidad que actúa como fuente de datos. En este caso se usa la instancia del componente BindingSource ya que previamente se enlazó con la entidad Banco.
- Cadena con el nombre de la propiedad del objeto subyacente que se enlaza con la propiedad del control. En este caso se usa la enumeración correspondiente a la entidad banco, la cual nos proporciona una lista con todos los nombres de las columnas de la tabla Banco en la base de datos así como el nombre de otras propiedades adicionales en el caso de las entidades (propiedades xxxString), ésta es una aportación al framework de su servilleta (o sea yo) y me apoyo en el método ToString() para convertir el nombre del elemento de la enumeración en una cadena. Esto sería equivalente a hacer lo siguiente:
[Enum].GetName(GetType(BancoColumn), BancoColumn.IdBanco
pero el método GetName() de las enumeraciones tiene muchos chequeos internos y al usar la función GetType() estamos usando reflection y, quienes saben lo que es reflection también conocen sus consecuencias, lo cual nos da como resultado una reducción del desempeño de las aplicaciones y además ¿a poco no es más sencillo utilizar el método ToString()? (tal vez use reflection internamente, pero es más fácil de usar). - Es un valor booleano. True para aplicar formato a los valores mostrados, Falso en caso contrario.
En algunos casos, como por ejemplo, cuando se hace databinding con Comboboxes, utilizo una sobrecarga que admite un quinto (sin albur) argumento, que sirve para que el BindingSource sepa cuando propagar los cambios hechos en el control hacia el objeto subyacente, y normalmente se ve como algo así:
cboPais.DataBindings.Add("SelectedValue", bsDomicilio, DomicilioColumn.IdPais.ToString("G"), True, DataSourceUpdateMode.OnPropertyChanged)
Con estas pocas líneas de código, la entidad banco esta enlazada a los controles en el formulario y cualquier cambio que se haga en ellos se aplicará inmediatamente en dicha entidad.
Ahora bien, todo esto es muy bonito pero hay que recordar que el enlace a datos también se debe deshacer antes de cerrar el formulario o la aplicación para que no queden objetos abiertos o basura en memoria y se produzcan las molestas fugas de memoria (memory leaks). Por lo que a continuación muestro el código para desenlazar los controles:
- Private Sub frmEdicionDeBanco_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
- ClearBindings()
- oBanco = Nothing
-
- End Sub
-
- Private Sub ClearBindings()
- ' Se quita el enlace entre el bindingsource y los controles.
- txtId.DataBindings.Clear()
- txtNombre.DataBindings.Clear()
- txtNombreCorto.DataBindings.Clear()
- chkStatus.DataBindings.Clear()
-
- ' Se quita la referencia de enlace entre la entidad y el bindingsource.
- bsBanco.DataSource = Nothing
-
- End Sub
-
El procedimiento ClearBindings es el que realiza todo el desenlace, primero utilizando el método Clear() de la propiedad DataBindings de los controles con lo que se eliminan todos los objetos Bindings creados previamente mediante el uso del método Add() y luego restableciendo la propiedad DataSource del componente BindingSource para desconectar la entidad Banco de este componente. De esta forma, sea cual sea la razón por la que se cierre el formulario, siempre se realizará primero el desenlace de datos y luego se cerrará el formulario.
Para finalizar esta tema del enlace a datos sencillo, voy a mostrar como persistir la instancia de la entidad Banco en la base de datos, almacenando así los nuevos valores o los cambios hechos a dicha entidad. Esto lo hago en el procedimiento controlador del evento Click del botón Aceptar.
-
- Private Sub btnAceptar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAceptar.Click
- Try
- ' Si un objeto no se modifica y se intenta guardar hay que validarlo de todas formas.
- BancoRules.Validate(oBanco)
-
- ' Se muestra el cursor de espera mientras se graba la información en la BD.
- Me.Cursor = Cursors.WaitCursor
-
- Try
- ' Si es un objeto nuevo...
- If ObjectStateHelper.IsNew(oBanco) Then
- BancoRules.InsertBanco(oBanco) ' Se guarda (inserta) el objeto
- ClearBindings() ' Se quitan los enlaces al objeto.
- oBanco = New Banco ' Se crea un nuevo objeto.
- Try
- oBanco.idbanco = BancoRules.GetNextID() ' Se obtiene un nuevo código consecutivo para cada objeto nuevo.
-
- Catch ex As Exception
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- MessageBox.Show("Ocurrió un error al intentar obtener el número consecutivo para el nuevo banco.", _
- "Error al buscar consecutivo", MessageBoxButtons.OK, MessageBoxIcon.Information)
-
- End Try
-
- oBanco.Status = True ' Se inicializa su Status a verdadero (Activo).
- BindControls() ' Se enlazan los controles al objeto recién creado.
- txtId.SelectAll() ' Se selecciona todo el texto.
- txtId.Focus() ' Se otorga el foco al primer control del formulario.
- Else
- BancoRules.SaveBanco(oBanco) ' Se guarda (actualiza) el objeto
- Me.Close() ' Se cierra el formulario en caso de Update.
-
- End If
-
- Catch ex As Exception
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- ' Si se captura una excepción, se muestran sus detalles.
- MessageBox.Show(String.Format("Detalles del error:{0}{1}", Environment.NewLine, ex.Message), _
- "Ocurrieron errores al intentar guardar la información", _
- MessageBoxButtons.OK, MessageBoxIcon.Warning)
-
- Finally
- ' Se muestra el cursor por defecto.
- Me.Cursor = Cursors.Default
-
- End Try
-
- Catch ex As Exception
- ' Si se captura una excepción, se muestran sus detalles.
- MessageBox.Show(String.Format("Detalles del error:{0}{1}", Environment.NewLine, ex.Message), _
- "Ocurrieron errores al validar la información", _
- MessageBoxButtons.OK, MessageBoxIcon.Warning)
-
- End Try
-
- End Sub
En el fragmento de código anterior, se puede ver que todo el código del procedimiento esta encerrado en un bloque Try… Catch para asegurarnos de capturar cualquier error que pueda surgir en él. Después se puede ver que se llama al método compartido (Shared) Validate de la clase BancoRules, que a su vez llama al método Validate de la clase Banco el cual se encarga de validar las propiedades de la instancia de la entidad. Este tipo de validación no está disponible por defecto en los objetos/entidades generados con Cooperator Modeler, hay que activarla quitando unos cuantos comentarios en el código generado, pero para eso escribiré otra entrada al respecto. Este método genera excepciones cuando encuentra una propiedad en la instancia con un valor incorrecto o no válido y es por eso que este llamado también se encuentra encerrado en el bloque Try … Catch del procedimiento. Después se ven una serie de instrucciones donde se revisa si la instancia de la entidad Banco es una entidad nueva o no, para saber a qué método de persistencia llamar. Esto se hace mediante un método compartido de la clase ObjectStateHelper de la librería Core de Cooperator Framework. Una vez que determina si es un objeto nuevo o no, se llama al método de persistencia pertinente (Insert o Save) a través de unos métodos compartidos en la clase de la capa Rules, aunque también se puede hacer el llamado directo usando los Mappers, pero prefiero hacerlo desde la capa Rules para poder validar permisos o hacer alguna otra operación necesaria y mantener el código en la capa de presentación más “limpio”. Pero para quien desee ver más o menos lo que se hace en uno de estos métodos compartidos de la clase BancoRules, les muestro este fragmento de código:
- ''' <summary>
- ''' Inserta una entidad banco en la base de datos.
- ''' </summary>
- ''' <param name="entity">La entidad <see cref="Banco">Banco</see> que se va a insertar.</param>
- Public Shared Sub InsertBanco(ByVal entity As Banco)
- If entity Is Nothing Then
- Throw New ArgumentNullException("entity")
-
- End If
- ' Aquí se puede hacer una verificación de permisos antes de grabar en la base de datos. P. Ej.
- ' If Not SecurityLayer.IsAuthorized(oUsuario, BancoOperations.AddBanco.ToString(“G”)) Then
- ' Throw New Exception(“El usuario no esta autorizado para realizar esta operación.”)
- ' End If
-
- Try
- BancoMapper.Instance().Insert(entity) ' Se inserta en la base de datos.
-
- Catch ex As Exception
- Throw
-
- End Try
-
- End Sub
-
Volviendo al procedimiento del botón Aceptar, se puede ver como se hace uso directo del objeto/entidad para validarlo y persistirlo ya que, cualquier cambio hecho mediante los controles en el formulario lo afectarán inmediatamente y dicho cambio estará disponible para ser guardado en la base de datos. Así que con esto, se termina la parte del enlace a datos o databinding simple.
Ahora, voy a mostrar como hacer el databinding complejo, pero en lugar de usar un objeto/entidad, usaré una lista de éstos. Veamos el formulario de ejemplo:
Y ahora veamos el código de cómo se carga el formulario:
- Public Class frmCatalogoDeBanco
- Dim oBancoListView As BancoListView
- Dim strIdColumn As String = BancoColumn.IdBanco.ToString("G")
-
- Private Sub frmCatalogoDeBanco_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
- ' Se obtienen y cargan los datos en la cuadrícula.
- CargarGrid()
-
- End Sub
-
- ''' <summary>
- ''' Carga la cuadrícula con datos.
- ''' </summary>
- Private Sub CargarGrid()
- Dim strSortColumn As String = strIdColumn ' Se ordena por default con el Id
-
- If (dgvBancos.DataSource IsNot Nothing) AndAlso (dgvBancos.SortedColumn IsNot Nothing) Then
- strSortColumn = dgvBancos.SortedColumn.DataPropertyName
-
- End If
-
- Try
- ' Se cambia el cursor (puntero del ratón) a cursor de espera.
- Me.Cursor = Cursors.WaitCursor
- ' Se crea una vista.
- oBancoListView = New bancoListView(BancoRules.GetAll())
- ' Se ordena la vista.
- oBancoListView.Sort(strSortColumn, True)
- ' Se asigna la vista como origen de datos de la cuadrícula.
- dgvBancos.DataSource = oBancoListView
- ' Se configuran las columnas.
- FormateaColumnasDeGrid()
-
- Catch ex As Exception
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- ' Se muestra el mensaje de error.
- MessageBox.Show(String.Format("Detalles del error:{0}{1}", vbNewLine, ex.Message), _
- "Ocurrieron errores al intentar obtener los datos", _
- MessageBoxButtons.OK, MessageBoxIcon.Warning)
- Finally
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- End Try
-
- End Sub
-
- ''' <summary>
- ''' Formatea las columnas de la cuadrícula.
- ''' </summary>
- Private Sub FormateaColumnasDeGrid()
- ' Se obtiene el total de columnas en la cuadrícula
- Dim intTotalColumnas As Integer = dgvBancos.Columns.Count - 1
-
- With dgvBancos
- For intIndex As Integer = 0 To intTotalColumnas
- Select Case .Columns(intIndex).DataPropertyName
- Case bancoColumn.idbanco.ToString("G")
- .Columns(intIndex).HeaderText = "Código"
- .Columns(intIndex).AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader
-
- Case bancoColumn.nombre.ToString("G")
- .Columns(intIndex).HeaderText = "Nombre"
- .Columns(intIndex).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
-
- Case bancoColumn.status.ToString("G")
- .Columns(intIndex).HeaderText = "Estatus"
- .Columns(intIndex).AutoSizeMode = DataGridViewAutoSizeColumnMode.ColumnHeader
-
- Case Else
- .Columns(intIndex).Visible = False
-
- End Select
-
- Next
-
- .Refresh()
-
- End With
-
- End Sub
En este fragmento de código se puede ver que al cargarse el formulario se llama al procedimiento CargarGrid(). Este procedimiento determina cual será la columna por la que la cuadrícula será ordenada, después crea una ListView de entidades Banco, que no es mas que una Lista tipada de entidades Banco pero que tiene implementados métodos y propiedades que permiten que la lista se pueda ordenar y filtrar. Después se ordena dicha lista y posteriormente se hace el enlace a datos complejo de acuerdo a como se mencionó en los primeros párrafos de esta entrada, es decir, usando la propiedad DataSource del control DataGridView. Para finalizar, se llama al procedimiento FormateaColumnasDeGrid() que se encarga de colocar los encabezados y establecer el ancho de las columnas de la cuadrícula basándose en la propiedad que está enlazada a cada una de ellas, de nuevo, haciendo uso de la enumeración BancoColumn.
Hay que aclarar que en este caso estoy utilizando una ListView para hacer el databinding, pero se puede utilizar perfectamente una List, es decir, que en lugar de haber usado una instancia de la clase BancoListView, pude haber usado una instancia de la clase BancoList, la cual también se puede usar en el databinding, solo que, esta última clase no tiene métodos de ordenamiento o filtrado incorporados entre sus características. También hay que aclarar que en el código, al crear la ListView, se hace un llamado al método GetAll de una clase compartida en el proyecto Rules, y que éste a su vez hace el llamado al método GetAll del mapper BancoMapper. Este método GetAll() devuelve una instancia de la clase BancoList con todos los datos de la tabla Banco en la base de datos y que es necesaria pasar como argumento al constructor de la clase BancoListView. Como ya mencioné antes, yo llamo a métodos de clases en el proyecto Rules para todo el acceso a datos, pero se puede los mismo llamando directamente a los métodos de los mappers, por ejemplo, para crear la ListView, pude haber hecho algo como lo siguiente:
- oBancoListView = New BancoListView(BancoMapper.Instance.GetAll())
Una última aclaración es necesaria. Cuando se establece la propiedad DataSource del control DataGridView. El mecanismo de databinding se encarga de obtener las propiedades de los elementos en la ListView y los va asignando a los objetos DataGridViewColumn que contiene éste control, ahorrándonos todo ese trabajo, pero como los títulos de las columnas pueden quedar un poco raros, prefiero darles un toque de formateo, el cual en el código creo que es bastante claro.
Ahora, solo falta ver como se agregan, modifican y eliminan elementos de la lista. Para agregar/modificar hago uso del formulario de edición que mostré en el ejemplo del enlace a datos sencillo, éste cuenta con una propiedad que permite establecer la entidad Banco que se va a editar en el formulario, el código de dicha propiedad se ve a continuación:
- Public Class frmEdicionDeBanco
- Dim oBanco As Banco
-
- ''' <summary>
- ''' Devuelve o establece el Banco.
- ''' </summary>
- ''' <value>El Banco.</value>
- Public Property Banco() As Banco
- Get
- Return oBanco
-
- End Get
- Set(ByVal value As Banco)
- oBanco = value
-
- End Set
-
- End Property
-
Y para pasar una entidad que se encuentra en la lista al formulario de edición, se usa el siguiente código:
- Private Sub btnModificar_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnModificar.Click
- ' Se cambia el cursor (puntero del ratón) a cursor de espera.
- Me.Cursor = Cursors.WaitCursor
- ' Se crea una nueva instancia del formulario de edición.
- Dim frmBanco As New frmEdicionDeBanco
- ' Se le asigna el formulario MDI padre.
- frmBanco.MdiParent = Me.MdiParent
- ' Se le pasa el objeto a editar.
- frmBanco.Banco = oBancoListView.Find(strIdColumn, Me.dgvBancos.CurrentRow.Cells(strIdColumn).Value)
- ' Finalmente se muestra el formulario de edición.
- frmBanco.Show()
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- End Sub
-
En la línea 9 del fragmento de código anterior se puede apreciar el uso del método Find() de la ListView el cual nos permite obtener un elemento de la lista realizando una búsqueda basada en el nombre-valor de una de sus propiedades. Este método no estaba implementado en las primeras versiones de Cooperator y fue hasta la versión 1.4 o 1.4.5 que ésta fue implementada.
En el caso de que se desee agregar un nuevo objeto, solo se debe abrir el formulario de edición sin pasarle ningún objeto/entidad y en el método de carga de dicho formulario se detectará que no se pasó ningún objeto/entidad y creará uno nuevo, el cual se podrá recuperar mediante la propiedad (Banco) que se ve en el código de arriba.
Ahora bien, para eliminar objetos/entidades que se encuentran en ListViews enlazadas a DataGridViews, utilizo el siguiente código:
- Private Sub btnEliminar_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnEliminar.Click
- Dim strMessage As String
- Dim intSelected As Integer
-
- ' Se obtiene el número de renglones seleccionados.
- intSelected = dgvBancos.SelectedRows.Count
- ' Se genera el mensaje dependiendo del número de renglones seleccionados.
- If intSelected = 1 Then
- strMessage = "¿Está seguro de eliminar este registro?"
-
- Else
- strMessage = "¿Está seguro de querer eliminar los registros seleccionados?"
-
- End If
-
- ' Se solicita confirmación de eliminación.
- If MessageBox.Show(strMessage, "Confirme eliminación...", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) = Windows.Forms.DialogResult.Yes Then
- Dim oBanco As New Banco
-
- ' Se recorre la colección de renglones seleccionados.
- For Each oRow As DataGridViewRow In dgvBancos.SelectedRows
- ' Se cambia el cursor (puntero del ratón) a cursor de espera.
- Me.Cursor = Cursors.WaitCursor
-
- ' Se obtiene un objeto a la vez para eliminar.
- oBanco = oBancoListView.Find(strIdColumn, oRow.Cells(strIdColumn).Value)
-
- Try
- ' Se intenta eliminar
- Rules.CustomRules.BancoRules.DeleteBanco(oBanco)
- ' Si hay éxito, se quita el objeto de la lista.
- oBancoListView.Remove(oBanco)
-
- Catch exNotExist As Cooperator.Framework.Data.Exceptions.RowDoNotExistException
- Dim strExMessage As String = String.Empty
-
- strExMessage = String.Format("El banco {0} - {1} ya no existe en la base de datos.{2}Quizá otro usuario lo eliminó anteriormente.", oBanco.IdBanco, oBanco.Nombre, vbNewLine)
-
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- MessageBox.Show(strExMessage, "Banco inexistente", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
- ' Se quita el objeto de la lista puesto que ya no existe.
- oBancoListView.Remove(oBanco)
-
-
- Catch ex As Exception
- Dim strExMessage As String = String.Empty
-
- strExMessage = String.Format("Detalles del error:{0}{1}", vbNewLine, ex.Message)
-
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- MessageBox.Show(strExMessage, String.Format("Error al intentar eliminar el Banco {0} - {1}", oBanco.IdBanco, oBanco.Nombre), MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
-
- Finally
- ' Se restaura el cursor de ratón
- Me.Cursor = Cursors.Default
-
- End Try
-
- Next
-
- ' Se libera la memoria que ocupaba el objeto.
- oBanco = Nothing
-
- End If
-
- End Sub
El código de este procedimiento es bastante sencillo y creo que con los comentarios en él será fácil de entender, solo haré referencia a unas cuantas líneas. Primero, se puede ver nuevamente el uso del método Find() de la ListView combinado con la propiedad SelectedRows del control DataGridView para obtener los elementos seleccionados para ser eliminados. Segundo, se debe poner atención al código encerrado en el broque Try…Catch, donde primero se intenta eliminar el objeto/entidad de la base de datos y después se remueve de la lista. Yo lo hago así para facilitarme el trabajo, porque si no se elimina el objeto/entidad de la base de datos, tampoco se elimina de la lista, además de que esto me permite no tener que recargar todos los datos de la lista desde la base de datos o después de aplicar un filtro. Pero si se desea, se puede hacer algo como lo siguiente:
- ' Se recorre la colección de renglones seleccionados.
- For Each oRow As DataGridViewRow In dgvBancos.SelectedRows
- ' Se obtiene un objeto a la vez para eliminar.
- oBanco = oBancoListView.Find(strIdColumn, oRow.Cells(strIdColumn).Value)
- ' Se marca el objeto/entidad como borrado.
- ObjectStateHelper.SetAsDeleted(oBanco)
- Next
- BancoMapper.Instance().Update(oBancoListView)
solo que esto obligaría a hacer una de dos cosas; poner el filtro de objetos marcados como “deleted” (eliminados) por medio de la propiedad Filter de la ListView con algo como:
- oBancoListView.Filter = "deleted=false"
o recargar la lista desde la base de datos (con un GetAll()) para que la lista me quede sin objetos/entidades eliminados en la cuadrícula.
¡Y eso es todo! Con este código se consigue controlar las operaciones básicas más conocidas como el ABC de un catálogo, utilizando el enlace a datos complejo de una ListView de entidades generadas con el Modeler de Cooperator Framework y un DataGridView.
Como nota final, debo aclarar a aquellos curiosos que lo hayan notado, que en el formulario que se muestra en el enlace a datos sencillo aparecen unos pequeños íconos rojos. Éstos se muestran cuando se usa el componente ErrorProvider y que, con una modificación a Cooperator se puede usar para informar de los errores de validación en los objetos/entidades enlazados a los controles en los formularios, ya sea por enlace sencillo o complejo. Esta modificación será comentada en la entrada del blog que se dedicará a la validación de objetos/entidades.