Introducción
El objetivo de este articulo será reflejar los siguiente puntos:
- Grabar una imagen en un campo de una base de datos
- Recuperar la imagen visualizándola en un celda del DataGridView y en un PictureBox
- Comunicar un formulario pasándole información en el constructor para la edición de una entidad
- Detectar el cierre del formulario para actualizar la información de los empleados
Dominio de la aplicación
Bien empezaremos definiendo al estructura de datos de la aplicación.

El dominio consiste en Empleados, con información básica del mismo, mas un campo de imagen el cual es opcional por lo cual esta marcado para permitir nulos.
El atributo Estado Civil tomara los valores de 1 a 4, los cuales fueron definido en un enumerado.
Adicionalmente el empleado podrá tener Estudios realizados, los cuales se asignan marcando de una lista.
El diseño de la interfaz es simple, un formulario principal que lista los usuarios.

Y otro que edita el usuario cuando se realiza un doble click en la fila de la lista.

La arquitectura de la aplicación es en dos capas, la presentación se comunica con DataAccess pasándole la información que necesita persistir o recuperar, el medio de comunicación son entidades definidas por medio de clases que modelan el dominio de la aplicación.

Si bien el esquema de estas entidades son muy parecido al de la base de datos ,se debe apreciar que la entidad “Empleado” posee una relación con la de estudios, lo cual posibilita la navegación de la información asociada.
Cargar lista de empleados (con imágenes)
En esta sección analizaremos como cargar las imágenes en el DataGridView, tomando esta información desde la base de datos.
El primer paso será recuperar los registros de la tabla, incluyendo la imagen.
[C#]
public static List<EmpleadoEntity> ObtenerTodos()
{
List<EmpleadoEntity> lista = new List<EmpleadoEntity>();
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
{
conn.Open();
string query = @"SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen
FROM Empleados";
SqlCommand cmd = new SqlCommand(query, conn);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
lista.Add(ConvertirEmpleado(reader, false));
}
}
return lista;
}
private static EmpleadoEntity ConvertirEmpleado(IDataReader reader, bool cargarRelaciones)
{
EmpleadoEntity empleado = new EmpleadoEntity();
empleado.IdEmpleado = Convert.ToInt32(reader["IdEmpleado"]);
empleado.Nombre = Convert.ToString(reader["Nombre"]);
empleado.Apellido = Convert.ToString(reader["Apellido"]);
empleado.FechaNacimiento = Convert.ToDateTime(reader["FechaNacimiento"]);
empleado.EstadoCivil = Convert.ToInt16(reader["EstadoCivil"]);
if (reader["Imagen"] != DBNull.Value)
empleado.Imagen = (byte[])reader["Imagen"];
if (cargarRelaciones)
{
empleado.Estudios = EstudiosDAL.ObtenerAsignadoEmpleado(empleado.IdEmpleado);
}
return empleado;
}
[VB.NET]
Public Shared Function ObtenerTodos() As List(Of EmpleadoEntity)
Dim lista As New List(Of EmpleadoEntity)()
Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("default").ToString())
conn.Open()
Dim query As String = "SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen" & _
"FROM Empleados"
Dim cmd As New SqlCommand(query, conn)
Dim reader As SqlDataReader = cmd.ExecuteReader()
While reader.Read()
lista.Add(ConvertirEmpleado(reader, False))
End While
End Using
Return lista
End Function
Private Shared Function ConvertirEmpleado(ByVal reader As IDataReader, ByVal cargarRelaciones As Boolean) As EmpleadoEntity
Dim empleado As New EmpleadoEntity()
empleado.IdEmpleado = Convert.ToInt32(reader("IdEmpleado"))
empleado.Nombre = Convert.ToString(reader("Nombre"))
empleado.Apellido = Convert.ToString(reader("Apellido"))
empleado.FechaNacimiento = Convert.ToDateTime(reader("FechaNacimiento"))
empleado.EstadoCivil = Convert.ToInt16(reader("EstadoCivil"))
If reader("Imagen") IsNot DBNull.Value Then
empleado.Imagen = DirectCast(reader("Imagen"), Byte())
End If
If cargarRelaciones Then
empleado.Estudios = EstudiosDAL.ObtenerAsignadoEmpleado(empleado.IdEmpleado)
End If
Return empleado
End Function
Es preciso notar como se asigna la imagen a la entidad
[C#]
if (reader["Imagen"] != DBNull.Value)
empleado.Imagen = (byte[])reader["Imagen"];
[VB.NET]
If reader("Imagen") IsNot DBNull.Value Then
empleado.Imagen = DirectCast(reader("Imagen"), Byte())
End If
lo cual resulta muy simple.
En el formulario “ListaEmpleados” se usa esta información para cargar el DataGridView
[C#]
private void CargarListaEmpleados()
{
dgvEmpleados.AutoGenerateColumns = false;
dgvEmpleados.DataSource = EmpleadosDAL.ObtenerTodos();
foreach (DataGridViewRow row in dgvEmpleados.Rows)
{
//se asigna el alto de la fila para poder ver la imagen correctamente
row.Height = 120;
//se recupera la entidad que es asignada como dato
EmpleadoEntity empleado = row.DataBoundItem as EmpleadoEntity;
if (empleado.Imagen == null)
row.Cells["Imagen"].Value = ImageHelper.ObtenerImagenNoDisponible();
else
row.Cells["Imagen"].Value = ImageHelper.ByteArrayToImage(empleado.Imagen);
}
}
[VB.NET]
Private Sub CargarListaEmpleados()
dgvEmpleados.AutoGenerateColumns = False
dgvEmpleados.DataSource = EmpleadosDAL.ObtenerTodos()
For Each row As DataGridViewRow In dgvEmpleados.Rows
'se asigna el alto de la fila para poder ver la imagen correctamente
row.Height = 120
'se recupera la entidad que es asignada como dato
Dim empleado As EmpleadoEntity = TryCast(row.DataBoundItem, EmpleadoEntity)
If empleado.Imagen Is Nothing Then
row.Cells("Imagen").Value = ImageHelper.ObtenerImagenNoDisponible()
Else
row.Cells("Imagen").Value = ImageHelper.ByteArrayToImage(empleado.Imagen)
End If
Next
End Sub
seguramente una pregunta que se hagan es porque en este método se esta recorriendo las filas del DataGridView después de asignar los datos al mismo ?
bien esto tiene una explicación, resulta que la celda de imagen del DataGridView requiere un tipo Image para poder trabajar, pero la entidad que proporcionamos devuelve un array de byte, lo cual no es compatible, entonces es que se aprovecha la capacidad del DataGridView para retornar la entidad que bindea a cada fila para obtener la entidad “Empleado” y convertir el array de byte al tipo Image.
También debo aclara que esto se intento hacer en un evento, como ser el CellFormatting, pero resulto un desastre porque este evento repintaba constantemente la información generando un parpadeo molesto en el control, se podría haber realizado esto mismo en el evento DataBindingComplete, pero para este caso no valía la pena separa la funcionalidad, por eso se dejo allí mismo.
En ese método se hace uso del la funcionalidad de “helper” que no es mas que un grupo de funciones que facilitan ciertas tareas, en este caso convierten el array de byte en Image.
Comunicar Formulario y detectar cierre del form hijo
Al realizar un doble click en la celda del DataGridView este entra en modo edición, esto implica simplemente toma la información y pacérsela al formulario
Desde “ListaEmpleados” se trabaja con el evento tomando solo el id del empleado, y en la instancia del formulario se lo pasa en el constructor. También en la misma operación se adjunta el evento FormClosing para poder detectar el cierre el form y proceder a la actualización del la lista de empleados.
[C#]
private void dgvEmpleados_CellContentDoubleClick(object sender, DataGridViewCellEventArgs e)
{
int IdEmpleado = Convert.ToInt32(dgvEmpleados.Rows[e.RowIndex].Cells["IdEmpleado"].Value);
//
// al pasarle un id de empleado este lo cargara para su edicion
//
EditarEmpleado frmEditar = new EditarEmpleado(IdEmpleado);
frmEditar.FormClosing += new FormClosingEventHandler(frmEditar_FormClosing);
frmEditar.Show();
}
void frmEditar_FormClosing(object sender, FormClosingEventArgs e)
{
//
// al cerrarse el form de edicion se ingresa a este evento
// para actualizar la informacion del listado
//
EditarEmpleado frmEdit = sender as EditarEmpleado;
if(frmEdit.DialogResult == DialogResult.OK)
CargarListaEmpleados();
}
[VB.NET]
Private Sub dgvEmpleados_CellContentDoubleClick(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs)
Dim IdEmpleado As Integer = Convert.ToInt32(dgvEmpleados.Rows(e.RowIndex).Cells("IdEmpleado").Value)
'
' al pasarle un id de empleado este lo cargara para su edicion
'
Dim frmEditar As New EditarEmpleado(IdEmpleado)
AddHandler frmEditar.FormClosing, New FormClosingEventHandler(AddressOf frmEditar_FormClosing)
frmEditar.Show()
End Sub
Private Sub frmEditar_FormClosing(sender As Object, e As FormClosingEventArgs)
'
' al cerrarse el form de edicion se ingresa a este evento
' para actualizar la informacion del listado
'
Dim frmEdit As EditarEmpleado = TryCast(sender, EditarEmpleado)
If frmEdit.DialogResult = DialogResult.OK Then
CargarListaEmpleados()
End If
End Sub
En el formulario que recibe esta información “EditarEmpleado” solo utiliza el id del empleado, porque con este ira a la db y recuperara la entidad para cargar el formulario, no hace falta enviar la entidad completa, solo la mínima información como para que este form pueda recuperar el resto.
[C#]
public partial class EditarEmpleado : Form
{
private int? _idEmpleado = null;
public EditarEmpleado()
{
InitializeComponent();
}
public EditarEmpleado(int idEmpleado)
: this()
{
_idEmpleado = idEmpleado;
}
.
.
.
}
[VB.NET]
Public Partial Class EditarEmpleado
Inherits Form
Private _idEmpleado As Nullable(Of Integer) = Nothing
Public Sub New()
InitializeComponent()
End Sub
Public Sub New(idEmpleado As Integer)
Me.New()
_idEmpleado = idEmpleado
End Sub
.
.
.
End Class
Cuando se termina la edición se retorna como un resultado del formulario que indicara si se grabo o se cancelo el formulario, esto se logra por medio del DialogResult.
[C#]
private void btnGuardar_Click(object sender, EventArgs e)
{
.
.
.
//
// se graba
//
EmpleadosDAL.Save(empleado);
this.DialogResult = DialogResult.OK;
this.Close();
}
private void btnCancelar_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
[VB.NET]
Private Sub btnGuardar_Click(sender As Object, e As EventArgs)
.
.
.
'
' se graba
'
EmpleadosDAL.Save(empleado)
Me.DialogResult = DialogResult.OK
Me.Close()
End Sub
Private Sub btnCancelar_Click(ByVal sender As Object, ByVal e As EventArgs)
Me.DialogResult = DialogResult.Cancel
Me.Close()
End Sub
Editar Empleado (mostrando la imagen en un Picturebox)
Esta tarea es bastante simple, solo implica recuperar una entidad en concreto haciendo uso de la cada de datos y cargar la entidad en los controles para presentar esta al usuario.
[C#]
public static EmpleadoEntity ObtenerById(int idEmpleado)
{
EmpleadoEntity empleado = null;
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
{
conn.Open();
string query = @"SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen
FROM Empleados WHERE IdEmpleado = @idEmpleado";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@idEmpleado", idEmpleado);
SqlDataReader reader = cmd.ExecuteReader();
if (reader.Read())
{
empleado = ConvertirEmpleado(reader, true);
}
}
return empleado;
}
[VB.NET]
Public Shared Function ObtenerById(ByVal idEmpleado As Integer) As EmpleadoEntity
Dim empleado As EmpleadoEntity = Nothing
Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("default").ToString())
conn.Open()
Dim query As String = "SELECT IdEmpleado, Nombre, Apellido, FechaNacimiento, EstadoCivil, Imagen " & _
"FROM Empleados WHERE IdEmpleado = @idEmpleado"
Dim cmd As New SqlCommand(query, conn)
cmd.Parameters.AddWithValue("@idEmpleado", idEmpleado)
Dim reader As SqlDataReader = cmd.ExecuteReader()
If reader.Read() Then
empleado = ConvertirEmpleado(reader, True)
End If
End Using
Return empleado
End Function
Una vez que se tiene la entidad solo se carga esta en los contoles.
[C#]
private void EditarEmpleado_Load(object sender, EventArgs e)
{
CargarEstadoCivil();
CargarEstudio();
//por defecto se carga una imagen de no disponible
picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible();
//
// se carga la informacion del empleado que se quiere editar
//
if (_idEmpleado.HasValue)
{
EmpleadoEntity empleado = EmpleadosDAL.ObtenerById(_idEmpleado.Value);
_idEmpleado = empleado.IdEmpleado;
txtNombre.Text = empleado.Nombre;
txtApellido.Text = empleado.Apellido;
dtpFechaNacimiento.Value = empleado.FechaNacimiento;
cbEstadoCivil.SelectedValue = Convert.ToInt32(empleado.EstadoCivil);
if (empleado.Imagen == null)
picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible();
else
picImagenEmpleado.Image = ImageHelper.ByteArrayToImage(empleado.Imagen);
//
// Se obtienen los estudios del empleado
//
AsignarEstudios(empleado);
}
}
private void CargarEstadoCivil()
{
cbEstadoCivil.DisplayMember = "Descripcion";
cbEstadoCivil.ValueMember = "IdEstadoCivil";
cbEstadoCivil.DataSource = EstadoCivilDAL.ObtenerTodos();
}
private void CargarEstudio()
{
dgvEstudios.AutoGenerateColumns = false;
dgvEstudios.DataSource = EstudiosDAL.ObtenerTodos();
}
private void AsignarEstudios(EmpleadoEntity empleado)
{
List<DataGridViewRow> rows = (from row in dgvEstudios.Rows.Cast<DataGridViewRow>()
let idEstudio = Convert.ToInt32(row.Cells["IdEstudio"].Value)
join dd in empleado.Estudios on idEstudio equals dd.IdEstudio
select row).ToList();
rows.ForEach(o => o.Cells["Seleccion"].Value = true);
}
[VB.NET]
Private Sub EditarEmpleado_Load(ByVal sender As Object, ByVal e As EventArgs)
CargarEstadoCivil()
CargarEstudio()
'por defecto se carga una imagen de no disponible
picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible()
'
' se carga la informacion del empleado que se quiere editar
'
If _idEmpleado.HasValue Then
Dim empleado As EmpleadoEntity = EmpleadosDAL.ObtenerById(_idEmpleado.Value)
_idEmpleado = empleado.IdEmpleado
txtNombre.Text = empleado.Nombre
txtApellido.Text = empleado.Apellido
dtpFechaNacimiento.Value = empleado.FechaNacimiento
cbEstadoCivil.SelectedValue = Convert.ToInt32(empleado.EstadoCivil)
If empleado.Imagen Is Nothing Then
picImagenEmpleado.Image = ImageHelper.ObtenerImagenNoDisponible()
Else
picImagenEmpleado.Image = ImageHelper.ByteArrayToImage(empleado.Imagen)
End If
'
' Se obtienen los estudios del empleado
'
AsignarEstudios(empleado)
End If
End Sub
Private Sub CargarEstadoCivil()
cbEstadoCivil.DisplayMember = "Descripcion"
cbEstadoCivil.ValueMember = "IdEstadoCivil"
cbEstadoCivil.DataSource = EstadoCivilDAL.ObtenerTodos()
End Sub
Private Sub CargarEstudio()
dgvEstudios.AutoGenerateColumns = False
dgvEstudios.DataSource = EstudiosDAL.ObtenerTodos()
End Sub
Private Sub AsignarEstudios(empleado As EmpleadoEntity)
Dim rows As List(Of DataGridViewRow) = (From row In dgvEstudios.Rows.Cast(Of DataGridViewRow)() _
Let idEstudio = Convert.ToInt32(row.Cells("IdEstudio").Value) _
Join dd In empleado.Estudios On idEstudio Equals dd.IdEstudio _
Select row).ToList()
'rows.ForEach(Function(o) o.Cells("Seleccion").Value = True)
rows.ForEach(Function(o) InlineAssignHelper(o.Cells("Seleccion").Value, True))
End Sub
La asignación de la imagen es muy simple y similar a como se trabajo en el DataGridview, solo se convierte el array de byte recuperado de la tabla en la base de datos y se carga directo en el PictureBox.
Algo interesante a remarcar es la forma en como se carga la lista de Estudios, en esta se usan dos consultas separadas, por un lado la primera que carga todos los estudios existentes, si se ingresara en modo de alta de empleado solo esta lista seria cargada, pero si se esta editando se recupera la lista de estudios asignados a ese empleado en particular y por medio de una consulta linq (método AsignarEstudios) se realiza la unión entres ambas listas aquellas filas del DataGridView que coincidan serán marcadas porque son las que el empleado tiene registradas en la tabla.
Búsqueda de una imagen (cargar archivo a un PictureBox)
Un punto que debería ser menor pero que he visto con mucha dificultad en preguntas en los foros, es como cargar seleccionar un archivo y cargarlo en un PictureBox?
El código es muy simple pero suele generar problemas.
[C#]
private void btnBuscarImagen_Click(object sender, EventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = "Archivo JPG|*.jpg";
if (fileDialog.ShowDialog() == DialogResult.OK)
{
picImagenEmpleado.Image = Image.FromFile(fileDialog.FileName);
}
}
[VB.NET]
Private Sub btnBuscarImagen_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim fileDialog As New OpenFileDialog()
fileDialog.Filter = "Archivo JPG|*.jpg"
If fileDialog.ShowDialog() = DialogResult.OK Then
picImagenEmpleado.Image = Image.FromFile(fileDialog.FileName)
End If
End Sub
Persistir la edición
La operación de persistir los datos o guardarlos requiere dos etapas, una implica tomar la información de los controles y generar los objetos de dominio que la capa de datos requiere para llevar a cabo la actualización en la base de datos, la segunda será simplemente procesar la entidad generando los INSERT y UPDATE necesario.
[C#]
private void btnGuardar_Click(object sender, EventArgs e)
{
//
// Se crea la entidad
//
EmpleadoEntity empleado = new EmpleadoEntity()
{
IdEmpleado = _idEmpleado.GetValueOrDefault(),
Nombre = txtNombre.Text,
Apellido= txtApellido.Text,
FechaNacimiento = dtpFechaNacimiento.Value,
EstadoCivil = Convert.ToInt16(cbEstadoCivil.SelectedValue),
Imagen = ImageHelper.ImageToByteArray(picImagenEmpleado.Image)
};
//
// Se asignan los estudios seleccionados, se unicializa la lista
// para cargar la selecciond el usuario
//
empleado.Estudios = new List<EstudioEntity>();
IEnumerable<DataGridViewRow> rowsSelected = dgvEstudios.Rows.Cast<DataGridViewRow>()
.Where(o => Convert.ToBoolean(o.Cells["Seleccion"].Value));
foreach (DataGridViewRow row in rowsSelected)
{
EstudioEntity estudio = new EstudioEntity()
{
IdEstudio = Convert.ToInt32(row.Cells["IdEstudio"].Value)
};
empleado.Estudios.Add(estudio);
}
//
// se graba
//
EmpleadosDAL.Save(empleado);
this.DialogResult = DialogResult.OK;
this.Close();
}
[VB.NET]
Private Sub btnGuardar_Click(sender As Object, e As EventArgs)
'
' Se crea la entidad
'
Dim empleado As New EmpleadoEntity() With { _
.IdEmpleado = _idEmpleado.GetValueOrDefault(), _
.Nombre = txtNombre.Text, _
.Apellido = txtApellido.Text, _
.FechaNacimiento = dtpFechaNacimiento.Value, _
.EstadoCivil = Convert.ToInt16(cbEstadoCivil.SelectedValue), _
.Imagen = ImageHelper.ImageToByteArray(picImagenEmpleado.Image) _
}
'
' Se asignan los estudios seleccionados, se unicializa la lista
' para cargar la selecciond el usuario
'
empleado.Estudios = New List(Of EstudioEntity)()
Dim rowsSelected As IEnumerable(Of DataGridViewRow) = dgvEstudios.Rows.Cast(Of DataGridViewRow)().Where(Function(o) Convert.ToBoolean(o.Cells("Seleccion").Value))
For Each row As DataGridViewRow In rowsSelected
Dim estudio As New EstudioEntity() With { _
.IdEstudio = Convert.ToInt32(row.Cells("IdEstudio").Value) _
}
empleado.Estudios.Add(estudio)
Next
'
' se graba
'
EmpleadosDAL.Save(empleado)
Me.DialogResult = DialogResult.OK
Me.Close()
End Sub
Es preciso remarcar como en la entidad “Empleado” es cargada la lista de Estudios que este posee y solo una entidad es enviada desde la Presentación a la capa de datos. Durante esta operación también se toma la imagen del PictureBox y se convierte en un array de byte.
La actualización realiza varias operaciones las cuales son muy extensas en código, por lo tanto no será puesta directa en el articulo, pero si podrán verse en el código de ejemplo que puede descargarse.
Código de Ejemplo
La base de datos utilizada en el ejemplo es la Sql Server Express 2008 R2, como ver en la solución el mdf esta integrado al Visual Studio, por lo tanto con solo tener el sql server express instado esta debería funciona adjuntándose sola al servicio.
En la carpeta “script” del proyecto “DataAccess” se encuentra un archivo .sql con las instrucciones para crear la estructura de tablas y datos que se requieren para este articulo.