domingo, 29 de noviembre de 2009

C# – ADO.NET – Parte 3 – Actualización Información Sql Server

 

Introducción

En esta oportunidad avanzaremos un poco mas allá en el uso de ADO.NET para el trabajo con la información, es por eso que no solo recuperaremos datos, sino que también agregaremos nuevos registros, actualizaremos los existentes y eliminaremos aquellos que ya no sean necesarios

Este post es la continuación de un post anterior:

C# – ADO.NET - Parte 1 - Recuperar Información Sql Server

es por ello que ciertos aspectos serán pasados por alto, pues fueron explicados con mas detalle en la primer parte.

1 – Creación nueva entidad (Insert)

Para esta operación se ha creando en la clase ContactosDAL, varias funciones que encapsulan la funcionalidad.

Encontraran un método de nombre Save(), el cual es el único visibilidad publica en la clase que permite la actualziacion de la entidad, y es por este que se decide si la operación a llevar a cabo será en definitiva un Update() o un Insert()

public static ContactoEntity Save(ContactoEntity contacto)
{

    if (string.IsNullOrEmpty(contacto.Nombre))
        throw new BusinessException(Messages.MSG1002);

    if (string.IsNullOrEmpty(contacto.Apellido))
        throw new BusinessException(Messages.MSG1003);

    if (Exists(contacto))
        return Update(contacto);
    else
        return Insert(contacto);
}

 

El método Save() es bastante simple, hace uso de una función interna de nombre Exists(), para decidir si el contacto como entidad enviado desde la presentación es uno nuevo, o si existe y hay que actualizarlo

private static bool Exists(ContactoEntity contacto)
{
    if (contacto == null)
        throw new BusinessException(Messages.ERR1001); 

    return Exists(contacto.IdContacto);
}

private static bool Exists(int Id)
{
    string sql = @"SELECT COUNT(*)
                      FROM Contacto
                      WHERE IdContacto = @Id";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", Id);

        conn.Open();

        int count = Convert.ToInt32(command.ExecuteScalar());

        if (count == 0)
            return false;
        else
            return true;
        
    }
}

La estructura de conexión y creación de objetos se respeta al igual que se comento en el primer articulo escrito, la diferencia en este es que se hace uso del  ExecuteScalar del objeto SqlCommand.

Como se observara este método ideal para estos casos, ya que es utilizado cuando se necesita recuperar un solo valor de la base de datos.

El método ExecuteScalar:

- Ejecuta la consulta y devuelve la primera columna de la primera fila del conjunto de resultados que devuelve la consulta. Se omiten todas las demás columnas y filas.
- Utilice el método ExecuteScalar para recuperar un único valor (por ejemplo, un valor agregado) de una base de datos.

En este caso se esta preguntando cuantos registro hay con el id de contacto que le pasamos como parámetro, si devuelve cero quiere decir que no existe el contacto, en cambio un valor de uno indica que existe por lo tanto debe proceder con la actualización.

Como se notara el método Exists() esta sobrecargado, o sea tiene dos definiciones del mismo pero con distintos parámetros, uno recibe la instancia de un contacto, mientras que al segundo solo el id, es mas el primero termina haciendo uso de siguiente para reutilizar la funcionalidad, y solo codificarla en uno de ellos.

Una vez determinado si existe o no el registro se proceder con las operaciones, por ejemplo el Insert()

private static ContactoEntity Insert(ContactoEntity contacto)
{
    string sql = @"INSERT INTO Contacto ([IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero])
                      VALUES (@Id, 
                            @Nombre, 
                            @Apellido, 
                            @FechaNacimiento, 
                            @Localidad, 
                            @Calle, 
                            @Numero)";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        int NextId = MaxId() + 1;

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", NextId);
        command.Parameters.AddWithValue("Nombre", contacto.Nombre);
        command.Parameters.AddWithValue("Apellido", contacto.Apellido);
        command.Parameters.AddWithValue("FechaNacimiento", contacto.FechaNacimiento);
        command.Parameters.AddWithValue("Localidad", string.IsNullOrEmpty(contacto.Localidad) ? (object)DBNull.Value : contacto.Localidad);
        command.Parameters.AddWithValue("Calle", string.IsNullOrEmpty(contacto.Calle) ? (object)DBNull.Value : contacto.Calle);
        command.Parameters.AddWithValue("Numero", contacto.Numero.HasValue ? contacto.Numero : (object)DBNull.Value );

        conn.Open();

        command.ExecuteNonQuery();

        contacto.IdContacto = NextId;

        return contacto;

    }
}

Durante la creación del registro hay una operación previa que debe realizarse, y esta consiste en obtener el id siguiente de la clave de la entidad a insertar.

Es por ello que se creo el método MaxId()

private static int MaxId()
{
    string sql = @"SELECT MAX(IdContacto)
                      FROM Contacto";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);

        conn.Open();

        return Convert.ToInt32(command.ExecuteScalar());

    }
}

Este es un método muy simple que también hace uso del ExecuteScalar() para obtener el único valor devuelto para la función de agregación que se ejecuta en la consulta sql, en este caso se utiliza la función Max, para recuperar el ultimo id de la tabla utilizada en la consulta.

Además de obtener el próximo id, durante el proceso de insertar se validan ciertos campos, para verificar si estos contiene información asignada, pero en caso de no tenerla se envía a la db el valor DBNull.Value, esto se puede hacer ya que los campos donde se realiza la operación permiten valores nulos como contenido.

Como este es un proceso de insert y la clave es generada de forma interna a la aplicación, es por esta razón que el final se actualiza la entidad pasada por parámetro y es devuelta para que el sistema pueda informarlo al usuario, ya sea en un mensaje de texto, o para registrar la operación en algún sistema de log, si fuera necesario.

2 – Actualización de la entidad (Update)

El proceso de actualización es bastante similar al de creación de la entidad, solo varia en que este requiere que la entidad exista, se tenga el identificador o código para poder identificarlo

private static ContactoEntity Update(ContactoEntity contacto)
{

    string sql = @"UPDATE Contacto SET 
                          [Nombre] = @Nombre
                          ,[Apellido] = @Apellido
                          ,[FechaNacimiento] = @FechaNacimiento
                          ,[Localidad] =  @Localidad
                          ,[Calle] =  @Calle
                          ,[Numero] = @Numero
                    WHERE [IdContacto] = @Id";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", contacto.IdContacto);
        command.Parameters.AddWithValue("Nombre", contacto.Nombre);
        command.Parameters.AddWithValue("Apellido", contacto.Apellido);
        command.Parameters.AddWithValue("FechaNacimiento", contacto.FechaNacimiento);
        command.Parameters.AddWithValue("Localidad", string.IsNullOrEmpty(contacto.Localidad) ? (object)DBNull.Value : contacto.Localidad);
        command.Parameters.AddWithValue("Calle", string.IsNullOrEmpty(contacto.Calle) ? (object)DBNull.Value : contacto.Calle);
        command.Parameters.AddWithValue("Numero", contacto.Numero.HasValue ? contacto.Numero : (object)DBNull.Value);

        conn.Open();

        command.ExecuteNonQuery();

        return contacto;

    }
}

A diferencia del proceso de creación de la entidad en esta oportunidad no es creado un nuevo Id, sino que este debe enviarse para que la actualización pueda realizarse correctamente. Es mas esta Id es utilizado en la sección WHERE de la consulta para poder identificar el registro que se quiere actualizar.

3 – Eliminación de la entidad (Delete)

Para este proceso una vez que se tiene la entidad simplemente se envía el id y eso es todo.

public static void Delete(ContactoEntity contacto)
{
    if (contacto == null)
        throw new BusinessException(Messages.ERR1001); 

    Delete(contacto.IdContacto);
}

public static void Delete(int Id)
{
    string sql = @"DELETE FROM Contacto 
                   WHERE [IdContacto] = @Id";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", Id);

        conn.Open();

        command.ExecuteNonQuery();

    }
}

Consideración sobre el ejemplo

Algo que se notara cuando se analice la aplicación es que fue eliminado el DataSet del ejemplo previo en el post anterior, dejando únicamente  el uso de la entidad como clase, esto se debió a que era necesario uniformizar la aplicación para tratar con solo un tipo de entidad, y poder así conservar esta cuando se esta en modo edición.

Se vera a nivel de presentación una propiedad que representa la entidad seleccionada, el tener o no un objeto que representa al contacto marca el modo en que se encuentra la aplicación, ya que al hacer dobleclick en una entidad de la grilla esta carga el contenido y asigna un valor en este propiedad interna del formulario

Los ejemplos requieren al menos Sql Server 2008 Express instalado localmente

[C#]
[VB.NET]

domingo, 22 de noviembre de 2009

[ADO.NET] Recuperar Información MS Access

 

Introducción


Con este ejemplo intento demostrar como con algunos simples cambios se puede utilizar ado.net de una forma similar tanto si se accede a sql server como si es Access la base de datos utilizada

Este post esta adaptado de la primera parte

C# – ADO.NET - Recuperar Información Sql Server – Parte 1

por lo tanto muchas de las explicaciones serán obviadas ya que los pasos son idénticos para ambos métodos.

Ante cualquier duda en los paso necesario para utilizar ado.net se podrá recurrir al primer articulo ya que la forma de codificar es idéntica para Sql Server y Access

En los ejemplos se observara que la técnica puede aplicarse exactamente de la misma forma, solo se ha cambiado los nombre de los objetos utilizados, por ejemplo en lugar de ser SqlConnection se utilizaría el OleDbConnection, y así con el resto de los objetos a utilizar.

 

1 - Recuperar un conjunto de datos (DataReader)


En este se recorre mediante el objeto OleDbDataReader los datos recuperados mediante la ejecución de la consulta.

public static List<ContactoEntity> GetAll()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

    List<ContactoEntity> list = new List<ContactoEntity>();

    using (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        OleDbCommand command = new OleDbCommand(sql, conn);

        conn.Open();

        OleDbDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            list.Add(LoadContacto(reader));
        }

        return list;
    }


}

Como se observa el método utilizado para crear la entidad se mantiene exactamente igual, ya que al usarse una interfaz del tipo IDataReader, esta mantiene compatibilidad tanto si proviene de Sql server, como Access

private static ContactoEntity LoadContacto(IDataReader reader)
{
    ContactoEntity contacto = new ContactoEntity();

    contacto.IdContacto = Convert.ToInt32(reader["IdContacto"]);

    contacto.Nombre = Convert.ToString(reader["Nombre"]);
    contacto.Apellido = Convert.ToString(reader["Apellido"]);

    contacto.FechaNacimiento = Convert.ToDateTime(reader["FechaNacimiento"]);

    contacto.Localidad = Convert.ToString(reader["Localidad"]);
    contacto.Calle = Convert.ToString(reader["Calle"]);
    contacto.Numero = Convert.ToInt16(reader["Numero"]);


    return contacto;
}

2- Recuperar un conjunto de datos (DataSet)


Al igual que el ejemplo planteado en Sql Server, este utiliza un DataSet Tipado es mas, como el DataSet es independiente de la tecnología utilizada en el acceso a los datos se pudo reutilizar el del ejemplo anterior

public static dtoContacto GetAllFromDataSet()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

    using (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        OleDbCommand command = new OleDbCommand(sql, conn);

        OleDbDataAdapter da = new OleDbDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        return contactos;
    }


}

3 – Recuperar un solo registro (DataReader)


Debe remarcarse en esta parte del ejemplo que Access también permite el uso de parámetros en sus consultas, en este caso al usarse el proveedor: Microsoft.Jet.OLEDB.4.0, los parámetros siguen teniendo el mismo formato que Sql Server, o sea hacen uso de @param, (en donde param es el nombre del parámetro), pero si se utiliza algún otro proveedor de acceso a datos para OleDb puede que esto cambie, y se necesite hacer uso del signo ?, para especificar la posición del mismo.

public static ContactoEntity GetById(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

    ContactoEntity product = null;

    using (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        OleDbCommand command = new OleDbCommand(sql, conn);
        command.Parameters.AddWithValue("Id", Id);

        conn.Open();

        OleDbDataReader reader = command.ExecuteReader();

        if (reader.Read())
        {
            product = LoadContacto(reader);
        }

        return product;
    }
}

4 – Recuperar un solo registro (DataSet)


Aquí hay que resaltar el cambio de  enumerado utilizado para definir el tipo de datos del parámetro, siendo el OleDbType

 

public static dtoContacto.ContactoRow GetByIdFromDataSet(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

    using (OleDbConnection conn = new OleDbConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        OleDbCommand command = new OleDbCommand(sql, conn);

        OleDbParameter param = new OleDbParameter("Id", OleDbType.Integer);
        param.Value = Id;
        command.Parameters.Add(param);

        OleDbDataAdapter da = new OleDbDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        if (contactos.Contacto.Rows.Count > 0)
            return contactos.Contacto.Rows[0] as dtoContacto.ContactoRow;
        else
            return null;
    }
}

Conclusión

Una vez se comprenda el concepto general de los objetos implicados en las consultas a las base de datos, las diferencias son mínimas, conociendo los objetos de ADO.NET básicos se puede consultar cualquier base de datos con pequeños cambios y lo aprendido para trabajar con una base de datos especifica pueden aplicarse al resto.

 

[C#] 
[VB.NET] 

domingo, 15 de noviembre de 2009

[ADO.NET] Parte 1 - Recuperar Información Sql Server

 

Introducción


He notado que a veces faltan ejemplo integradores sobre el uso de ciertos aspectos que a primera vista parecen simple, pero al no encontrase en uso de forma integradora pueden resultar algo difíciles de captar las ideas, especialmente cuando se comienza en el aprendizaje.

La objetivo de este articulo es mostrar como hacer so de ado.net paso a paso en un ejemplo concreto utilizando varios métodos de consultas y trabajo de la información obtenida

 

Algunas aclaraciones antes de comenzar


Si bien la explicación del ejemplo se centrara en el uso de ado.net bajo lenguaje c#, los ejemplos podrán descargarse también en vb.net

Las explicación del articulo aplica a ambos lenguajes sin problemas.

Con respecto al diseño del ejemplo por ahí algunas aclaraciones previas podrá facilitar la comprensión.

Se visualizaría que en el ejemplo se han declarado dos clases que por ahí no son familiares, estas llevan el nombre: ContactoDAL y ContactoEntity.

Para el que no conozca el DAL (Data Access Layer) en realidad apunta a separa en capas la aplicación para dividir las responsabilidades y poder así encapsular y abstraer funcionalidad facilitando la reutilización.

En este caso en particular aplique este mismo concepto pero no lo puse en un proyecto nuevo, simplemente para no complicar el ejemplo, es por eso que tanto estas clases como el formulario están en el mismo proyecto.

También verán una clase que termina con la palabra entity, esta básicamente representa la entidad del dominio, y define las propiedades que posee la tabla de contactos, esta entidad será la intermediaria entre la funcionalidad de la DAL y la presentación. Esta clase evitara que a la presentación le lleguen objetos que claramente son de datos.

Aunque vale aclarar que en este ejemplo al estar contenido en un solo proyecto se pierde un poco el objetivo de abstraer a la presentación de componente de datos, pero igualmente esta es una buen técnica para mantener el código prolijo.

 

1 - Recuperar un conjunto de datos (DataReader)


La primera operación que será utilizada será la de recuperar todos los ítems de una tabla y cargar una grilla con ellos.

Para esta operación hay dos formas para acceder a los datos, por medio de:

  • DataReader
  • DataSet

Para los DataReader es que se hace uso de la clase con sufijo “entity” de esta forma se cargan los datos en estas entidades evitando pasar el reader a la presentación, lo cual no es nada aconsejable.

Algo importante para contar acerca de los reader es que estos necesitan mantener la conexión a al db abierta durante el procesamiento (o lectura de los registros que se están recuperando), es por eso que usar clases de entidades ya que se procesa el reader en un tiempo muy corto y luego se cierra. Hay que destacar que lo reader en acceso secuencial de lectura son mas rápido que los dataset.

Para el dataset, se hace uso de un DataSet tipado, en el proyecto lleva el nombre de “dtoContacto”, este permite tener una estructura definida, similar a la que brinda “ContactoEntity”.

En la clase ContactoDAL, se encuentran dos métodos “GetAll” y “GetAllFromDataSet”.

Empezaremos explicando como hacer uso de DataReader.

public static List<ContactoEntity> GetAll()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

    List<ContactoEntity> list = new List<ContactoEntity>();

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);

        conn.Open();

        SqlDataReader reader = command.ExecuteReader();

        while (reader.Read())
        {
            list.Add(LoadContacto(reader));
        }

        return list;
    }

}

> Creación del objeto de conexión a la base de datos (línea 14): Este siempre será el primer paso ya que es necesario definir el objeto que permitirá establecer el contacto con la base de datos. Para realizar la tarea se necesitara de un string de conexión, para mas detalles ver “Cadena de conexión”.

Algo interesante que observaran es el uso de la sentencia using, el objetivo de esta es permitir delimitar la sección en que estará disponible un objeto, o sea el ámbito en el cual la instancia será usada, verán como al final cuando se termina de utilizar los objetos no se realiza un cierre de la conexión o un Dispose de los objetos, es justamente el bloque using el que hará esto por nosotros.

> Objeto Command (línea 17): este objeto permitirá unificar la consulta sql de selección que vamos a utilizar con la conexión a la base de datos que establecimos en el paso anterior.

El objeto command posee la funcionalidad para ejecutar la consulta que se ha creado, ya sea esta para insertar o actualizar la información, o como en este caso para recuperarla.

> Ejecución de la consulta (línea 21): el objeto command posee un método de nombre ExecuteReader(), el cual devolverá como resultado un objeto del tipo SqlDataReader.

> Lectura de los datos devueltos (líneas 23-26): al tratarse de un grupo de registros los que se recuperaran de la consulta es necesario realizar un ciclo por cada uno de ellos realizando la transformación de los datos para adecuarlos al objeto definido como entidad de negocio de la aplicación, en este caso “ContactoEntity”

El método Read() del objeto SqlDataReader tiene dos funcionalidades básicas, devuelve un true o false según se encuentren registros para leer, y además posiciona el cursor el el siguiente registro habilitado, es por eso que el while cumple la función ideal para recorrer cada ítem del objeto DataReader.

> Transformación de los datos del reader a la entidad (línea 25): esta operación se ha encapsulado en un método separado ya que es una operación que será reutilizada:

private static ContactoEntity LoadContacto(IDataReader reader)
{
    ContactoEntity contacto = new ContactoEntity();

    contacto.IdContacto = Convert.ToInt32(reader["IdContacto"]);

    contacto.Nombre = Convert.ToString(reader["Nombre"]);
    contacto.Apellido = Convert.ToString(reader["Apellido"]);

    contacto.FechaNacimiento = Convert.ToDateTime(reader["FechaNacimiento"]);

    contacto.Localidad = Convert.ToString(reader["Localidad"]);
    contacto.Calle = Convert.ToString(reader["Calle"]);
    contacto.Numero = Convert.ToInt16(reader["Numero"]);


    return contacto;
}

Como se observara en el bloque de código este método crea una instancia nueva de la entidad que estamos utilizando, luego asigna a cada propiedad la columna que le corresponde del registro que se esta leyendo en ese momento, el cual se ha pasado como parámetro al método, y por ultimo retorna el objeto entity con los datos para ser asignado a la colección de “ContactoEntity”.

 

2- Recuperar un conjunto de datos (DataSet)


Esta operación tendrá mucho en común con la anterior solo diferirá en la utilización algunos objetos distintos ya que se hará uso de un DataSet, (en este caso tipado) para cargar los datos

 

public static dtoContacto GetAllFromDataSet()
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto";

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);

        SqlDataAdapter da = new SqlDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        return contactos;
    }

}

Como se observara en el código los dos primeros pasos son coincidentes con la operación anterior que utilizaba DataReader para la obtención de los datos, se detallara a continuación las diferencias.

> Creación del adaptador (línea 17): Este objeto DataAdapter proporcionara la herramienta principal para poder cargar un objeto del tipo DataSet.

Como ser observara el constructor del objeto SqlDataReader en este caso recibe al objeto Command como parámetro, pero debe comentarse que no es la única alternativa, en este caso se uso el command para mantener una uniformidad en la codificación, pero podría haber pasado como parámetro la consulta sql y el objeto conexión directamente al constructor del DataAdapter, de esta forma ya no se necesitara mas del objeto Command.

> Creación y llenado de los datos (líneas 19-21): La definición del dataset tipado se encuentra en el archivo de nombre “dtoContacto.xsd”, al inspeccionar el archivo se notara que este contiene un DataTable de nombre “Contacto” y la definición de las columnas, las cuales coinciden con las de la tabla de la base de datos.

Nota: cuando se crea un DataSet Tipado lo mas común es que se agregue de forma automática un TablaAdapter, en este caso se removió dejando la entidad lo mas simple posible.

El método Fill() del DataAdapter realizara la ejecución y carga de los datos en el DataSet Tipado, al cual además le indicamos cual es el DataTable que se estará cargando. Debe recordarse que un DataSet puede contener mas de un DataTable, en este caso posee solo uno, pero podría haber mas en otros casos.

Nota: al diferencia del objetos Command el SqlAdapter no requiere la apertura previa de la conexión, es por eso que la línea conn.Open() no esta presente. El DataDapter realiza la apertura, utilización y cierre de la conexión de forma automática

 

3 – Recuperar un solo registro (DataReader)


A diferencia la la operación en donde recuperábamos todo la información de una tabla, en este caso se remarcan dos puntos

- la utilización de parámetros en la consulta

- la no utilización del while para recorrer los datos provenientes de la consulta

 

public static ContactoEntity GetById(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

    ContactoEntity product = null;

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Id", Id);

        conn.Open();

        SqlDataReader reader = command.ExecuteReader();

        if (reader.Read())
        {
            product = LoadContacto(reader);
        }

        return product;
    }
}

> Creación del parámetro de la consulta (línea 19): como se observa el objeto Command posee una propiedad que permite definir los parámetros en la consulta, es allí donde serán agregados, y hay diferentes formas de hacerlo, la mas practica y directa para crear parámetros simples es utilizando el método AddWithValue().

Pero debe remarcarse un punto importante en el uso de este método, como se observara en ningún momento se define un tipo de dato para el parámetro, lo cual parecería hasta mágico como lo infiere, pero no es así, para que esto funcione correctamente el tipo de la variable que se utiliza debe ser del tipo correcto, en este caso el “Id” es del tipo int, el cual coincide con el tipo de la columna “Id” de la tabla en la base de datos, es por esto que todo es resulto fácilmente.

Por ejemplo, si el parámetro será una fecha la variable usada en el método AddWithValue, debería ser también del tipo fecha, o en su defecto se tendría que convertir (castear) al tipo DataTime. En definitiva el método para agregar parámetros es simple de usar pero requiere ciertos cuidados a tener en cuenta, ya que de otra forma no podrá determinar de forma dinámica el tipo de datos del parámetro.

> Lectura del registro (línea 25-28): a diferencia del proceso en donde se leían un grupo de registros, en este solo nos interesa uno solo, es por eso que al reemplazar el while por un simple if, este cumple la misma funcionalidad, el Read() del DataReader devuelve true si hay registro, y además posiciona el cursor en el mismo para su lectura.

Luego se reutiliza el método que devolverá la entidad completa, y esta es retornada como resultado del método.

 

4 – Recuperar un solo registro (DataSet)


A diferencia el método en donde se recuperaba un conjunto de registros aquí hay variantes

- se utiliza un parámetro en la consulta

- se devuelve un solo registro del DataTable y no el DataSet completo

public static dtoContacto.ContactoRow GetByIdFromDataSet(int Id)
{
    string sql = @"SELECT [IdContacto]
                          ,[Nombre]
                          ,[Apellido]
                          ,[FechaNacimiento]
                          ,[Localidad]
                          ,[Calle]
                          ,[Numero]
                      FROM Contacto
                      WHERE IdContacto = @Id";

    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);

        SqlParameter param = new SqlParameter("Id", SqlDbType.Int);
        param.Value = Id;
        command.Parameters.Add(param);

        SqlDataAdapter da = new SqlDataAdapter(command);

        dtoContacto contactos = new dtoContacto();

        da.Fill(contactos, "Contacto");

        if (contactos.Contacto.Rows.Count > 0)
            return contactos.Contacto.Rows[0] as dtoContacto.ContactoRow;
        else
            return null;
    }
}

> Creación del parámetro de la consulta (línea 18-20): a modo de ejemplo en este caso se ha utilizado una técnica distinta, en donde se define explícitamente cual es el tipo de datos del parámetro.

Se crea el objeto SqlParameter, pasando en el constructor el nombre y tipo de dato del parámetro, luego a la instancia se le asigna el valor, y por ultimo se agrega a la colección de parámetros del objeto Command

> Determinar si se encontró el registro, y retorno del ContactRow (lineas 28-31): Como en este caso lo que se carga es un Datset completo, en realidad un DataTable si se lo devuelve directamente quien deba manipular los datos deberá agregar validaciones que comprueben si esta o no el registro cargado.

Es por ello que estas validaciones se agrego del lado de los datos, en el if se pregunta si al menos hay un registro cargado, luego se toma el primero de la colección para retornarlo, en caso de no haber ninguno registro para los filtros asignados se devuelve null.

Al retornar el valor se castea a un tipo de dato algo particular “dtoContacto.ContactoRow”, esta es una clase que crea internamente el DataSet Tipado, y representa un registro del DataTable “Contacto” 

 

Cadena de Conexión


Seguramente se habrá notado en el código que al hacer uso del objeto SqlConnection se utiliza una clase que devuelve la cadena de conexión:

ConfigurationManager.ConnectionStrings["default"]

El ConfigurationManager permite el acceso al archivo de configuración situado en el “app.config”, este archivo al editarlo posee un formato xml en donde se podrá especificar la configuración del sistema, en este caso en particular se utiliza para especificar la cadena de conexión a la base de datos, pero podría servir para conservar otro tipo de datos variables para el sistema.

Lo bueno de esta implementación es que al compilar la aplicación el archivo de configuración queda libre como archivo de texto que podrá editarse con el notepad y cambiarse sin necesidad de recompilar la aplicación desarrollada. Esto es muy bueno para realizar cambios una vez que se ha realizado el deploy en la pc del usuario.

Para utilizar el ConfigurationManager es necesario agregar la referencia a la assembly de nombre “System.Configuration”

 

Consideraciones acerca de la aplicación de ejemplo


Los ejemplos de código están usando Sql Server como parte de la solución, por lo tanto se aconseja que localmente al menos se tenga instado el Sql Server 2008 Express con el servicio ejecutándose.

Para editar un item de la grilla una vez que este cargada, se deberá hacer click con el botón derecho del mouse sobre de la grilla, y seleccionar con que operación recuperar los datos. Se esta haciendo uso control ContextMenuStrip para la edición del registro seleccionado.

 

[C#] 
[VB.NET] 

sábado, 17 de octubre de 2009

[ASP.NET] Pasar información entre User Control

 

Introducción


El desarrollo modular de aplicaciones web requiere la confección de componentes que permitan encapsular ciertos compartimientos que serán reutilizados, o que necesitan ser tratados de una forma unificada para facilitar la mantenibilidad del desarrollo.

Es por eso que en ciertas ocasiones el uso de User Controls es una buena práctica, aunque un aspecto no menor es la comunicación entre estos componentes encapsulados con el resto de la aplicación, o con otros componentes.

En este texto explicare como lograr la comunicación entre dos User Control, pasando datos de uno a otro de una forma desacoplada.

 

Diseño


El planteo se basará en la utilización de dos users control muy simples que estarán contenidos en una misma página web.

El primer user control encapsulará una grilla la cual cargara un listado de empleados.

El segundo contiene una serie de Textbox que serán utilizados para visualizar el registro seleccionado del primer user control.

 

User Control – Listado


Este estará formado por un control GridView.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebUserControlsCommunicated
{
    public partial class ucGrid : System.Web.UI.UserControl
    {
        public delegate void GridSelectorCommandEventHandler(GridSelectorCommandEventArgs e);
        public event GridSelectorCommandEventHandler GridSelectorChanged;

        public class GridSelectorCommandEventArgs
        {
            public int Id { get; protected set; }
            public string Nombre { get; protected set; }
            public string Cargo { get; protected set; }

            public GridSelectorCommandEventArgs(int id, string nombre, string cargo)
            {
                this.Id = id;
                this.Nombre = nombre;
                this.Cargo = cargo;
            }
        }


        public DataTable DataSource { get; set; }


        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                GridView1.DataSource = this.DataSource;
                GridView1.DataBind();
            }
        }

        protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            int Id = Convert.ToInt32(GridView1.SelectedRow.Cells[1].Text);
            string nombre = Convert.ToString(GridView1.SelectedRow.Cells[2].Text);
            string cargo = Convert.ToString(GridView1.SelectedRow.Cells[3].Text);


            if (GridSelectorChanged != null)
                GridSelectorChanged(new GridSelectorCommandEventArgs(Id, nombre, cargo));

            
        }
    }
}

Los puntos importantes a destacar del código serian:

- línea 30, define la propiedad que permitirá asignar el origen de datos que se usará para cargar el GridView. Esta propiedad será utilizada en el evento Page_Load del user control, asignando el contenido.

- líneas 12 y 13, en las mismas se define el handler y evento que permitirán que la información  de los items seleccionados del GridView viajen desde el interior del user control hacia la página.

- líneas 15-27, definen el argumento del evento que será usado para realizar el pasaje de los datos seleccionados en la grilla. Es por medio de este argumento que se podrá recuperar los valores elegidos, ya que desde fuera del user control no se tendra acceso directo al GridView.

Habría una alternativas a esta opción, la cual consiste en dejar disponible en una propiedad los valores seleccionados, imitando un poco a las propiedades como ser el SelectedRow, o SelectedItems que poseen otro controles.

- líneas 42-53, estas defines el evento local al user control que se ejecutará al utilizar el botón de selección del GridView, básicamente será una especie de conversión de eventos, en donde un evento atrapado localmente, es transformado en un evento exterior, el cual es lanzado en la línea 50.

Como ser observara las primeras acciones son las de recuperar los valores de la columnas de la fila seleccionada, para poderlos usar como parámetros del argumento del evento que se lanzara.

Es muy importante cuando se va a lanzar un evento validar que este tenga algún método adjunto, ya que de no tener ninguno y ejecutarse este producirá un fallo, la validación por distinto de null evita este problema.

 

User Control – Listado de TextBox


Este user control contendrá una serie de TextBox, destinado a la visualización de la información seleccionada en el user control que contiene el GridView

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebUserControlsCommunicated
{
    public partial class ucTextView : System.Web.UI.UserControl
    {

        public int Id { get; set; }
        public string Nombre { get; set; }
        public string Cargo { get; set; }

        protected void Page_Load(object sender, EventArgs e)
        {

        }

        public void Refresh(int id, string nombre, string cargo)
        {
            this.Id = Id;
            this.Nombre = nombre;
            this.Cargo = cargo;

            this.Refresh();
        }

        public void Refresh()
        {
            txtId.Text = Convert.ToString(this.Id);
            txtNombre.Text = this.Nombre;
            txtCargo.Text = this.Cargo;
        }
    }
}

Este control tiene bastante menos lógica que implementar ya que su funcionalidad se reduce a recibir los valores y asignarlos a los TextBox que correspondan.

Como se observa cuenta con una serie de propiedades que representan cada atributo de la entidad.

Y un método Refresh() que será el encargado de realizar en concreto la asignación de cada propiedad con su respectivo control.

 

Integración – Default.aspx


Bien llego el momento de poner todo en conjunto a funcionar.

Para ello se hará uso del formulario web, Default.aspx, es allí donde se arrastrara cada user control en el diseñador de la pagina, haciendo uso de una tabla para dar algo de formato y ubicación.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace WebUserControlsCommunicated
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            ucGrid1.GridSelectorChanged += new ucGrid.GridSelectorCommandEventHandler(ucGrid1_GridSelectorChanged);

            if (!IsPostBack)
            {
                ucGrid1.DataSource = CargarTabla();
            }
        }

        void ucGrid1_GridSelectorChanged(ucGrid.GridSelectorCommandEventArgs e)
        {
            ucTextView1.Id = e.Id;
            ucTextView1.Nombre = e.Nombre;
            ucTextView1.Cargo = e.Cargo;

            ucTextView1.Refresh(); 
        }



        private DataTable CargarTabla()
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("Id");
            dt.Columns.Add("Nombre");
            dt.Columns.Add("Cargo");

            DataRow row = dt.NewRow();
            row["Id"] = 1;
            row["Nombre"] = "Andres";
            row["Cargo"] = "Developer";
            dt.Rows.Add(row);

            row = dt.NewRow();
            row["Id"] = 2;
            row["Nombre"] = "Federico";
            row["Cargo"] = "PM";
            dt.Rows.Add(row);

            row = dt.NewRow();
            row["Id"] = 3;
            row["Nombre"] = "Leonardo";
            row["Cargo"] = "Developer";
            dt.Rows.Add(row);

            return dt;

        }

    }
}

En la línea 14 es donde se define la utilización del evento expuesto por el user control que contiene la grilla, la asignación del handler requiere de un método declarado en las líneas 22-29.

Es en este método ucGrid1_GridSelectorChanged donde se hará uso de los parámetro del argumento del evento GridSelectorCommandEventArgs, para especificar los datos al segundo user control.

Como se habrá notado en este caso se asignan las propiedades directamente al segundo user control, para llamar por ultimo el método Refresh() que realizara la asignación de estos valores dentro del control; pero también se podría haber utilizado para tal fin el método Refresh() sobrecargado con los valores de cada propiedad necesaria para desplegar la información.

[C#] 
 

lunes, 12 de octubre de 2009

C# – Word – Utilización de Tablas

 

Introducción

El hacer uso de las librerías de Interop de Office puede llega a ser complejo en ciertas circunstancias, mas cuando se necesita hacer uso de posiciones relativas a otros elemento en un documento, es por ello que en este ejemplo intento reflejar como hacer uso de tablas en Word manipulándolas desde código.

Pasos previos

Se debe remarcar que para que estos ejemplos puedan ser ejecutados es necesario contar un Office instalado en el equipo, ya que las librerías de Interop requieres que se encuentren presentes los componente COM que utilizan, y es necesario que Word sea instalado para ello.

Notarán en el código en sus primeras líneas la inclusión de un alias en el using para poder referenciar a la funcionalidad de Office Word

using Office = Microsoft.Office.Interop.Word;
Introducir una tabla dentro de otra

La primera aproximación que tendremos con las tablas será para utilizarlas de forma anidadas, o sea una tabla dentro de otra.

Para ellos contaremos con el siguiente código:

        private void btnTabladentroTabla_Click(object sender, EventArgs e)
        {
            Office.ApplicationClass app = new Office.ApplicationClass();
            
            //
            // Agrego un nuevo documento a la aplicacion Word
            //
            object missing = System.Reflection.Missing.Value;

            Office.Document doc = app.Documents.Add(ref missing, ref missing,
                                 ref missing, ref missing);



            //
            // Adiciono una tabla sobre el documento
            //
            object DefaultTableBehavior = Office.WdDefaultTableBehavior.wdWord9TableBehavior;
            object WdAutoFitBehavior = Office.WdAutoFitBehavior.wdAutoFitWindow;

            Office.Table table1 = doc.Tables.Add(app.Selection.Range, 4, 3, ref DefaultTableBehavior,ref WdAutoFitBehavior);
            
            //
            // Inserto al segunda tabla, pero a esta le indicare que el range donde debe
            // incluirse esta dentro de la primer tabla, mas precisamente en la primer columna
            // segunda fila
            //
            Office.Table table2 = doc.Tables.Add(table1.Cell(2,1).Range, 2, 3, ref DefaultTableBehavior,ref WdAutoFitBehavior);

            //
            // Agrego una fila nuevo a la tabla
            //
            table2.Rows.Add(ref missing);

            //
            // Guardo el archivo
            //
            object fileNameSave = Path.Combine(Application.StartupPath, "ArchivoGenerado.doc");

            doc.SaveAs(ref fileNameSave, ref missing, ref missing, 
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing, 
                        ref missing, ref missing, ref missing, ref missing, ref missing);


            doc.Close(ref missing, ref missing, ref missing);

            MessageBox.Show("El documento se generó correctamente");

        }

El código de por si, al seguirlo paso a paso esta bastante claro en las operaciones que realiza, pero por ahí hay algunos puntos a remarcar.

Uno de ellos es la línea 23 se notará que el rango utilizado en la creación de la tabla incluye la selección de una celda de la tabla creada previamente, este es el punto clave para insertar una tabla dentro de otra, la sección correcta del lugar donde ubicarla.

Una aspecto bastante raro que me encontré cuando realizaba las pruebas es que por mas que hacia uso de los parámetros NumRows la tabla interna siempre aparecía con una sola fila, es por ellos que adicione una línea de código que inserta una fila adicional. Esto se visualiza en la línea 27 del código.

Insertar una tabla en medio de otras dos

Aquí también es importante jugar con una correcta selección de los objetos, y ubicar correctamente la posición para insertar la nueva tabla.

        private void btnTablaEntreTablas_Click(object sender, EventArgs e)
        {
            Office.ApplicationClass app = new Office.ApplicationClass();

            //
            // Realizo al apertura de un archivo existente, el cual contendra 4 tablas
            //
            object missing = System.Reflection.Missing.Value;
            object fileNameAbrir = Path.Combine(Application.StartupPath, "ArchivoOriginal.doc");

            Office.Document doc = app.Documents.Open(ref fileNameAbrir, ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing, 
                                                    ref missing, ref missing, ref missing);

            //
            // Selecciono la segunda tabla del documento
            //
            doc.Tables[2].Range.Select();
 
            //
            // de la seleccion anterio me desplazo un posicion hacia abajo
            //
            object moveUnit = Office.WdUnits.wdLine;
            object moveLength = 1;

            app.Selection.MoveDown(ref moveUnit, ref moveLength, ref missing);

            //
            // Agrego un espacio, o entrer, para generar la separacion para la proxima tabla
            //
            app.Selection.TypeParagraph();


            //
            // Inserto a la nueva tabla
            //
            object DefaultTableBehavior = Office.WdDefaultTableBehavior.wdWord9TableBehavior;
            object WdAutoFitBehavior = Office.WdAutoFitBehavior.wdAutoFitWindow;

            Office.Table table1 = doc.Tables.Add(app.Selection.Range, 4, 3, ref DefaultTableBehavior, ref WdAutoFitBehavior);

            Office.Range cellrange = table1.Cell(1, 1).Range;
            cellrange.Text = "Tabla Intermedia";

            //
            // Guardo el documento
            //
            object fileNameSave = Path.Combine(Application.StartupPath, "ArchivoGenerado.doc");

            doc.SaveAs(ref fileNameSave, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing,
                        ref missing, ref missing, ref missing, ref missing, ref missing);


            doc.Close(ref missing, ref missing, ref missing);

            MessageBox.Show("El documento se genero correctamente");

        }

En este ejemplo a diferencia del anterior, se parte de un documento ya existente, al cuál se le agregara una tabla intermedia entre otras dos tablas.

En la línea 17 se visualiza como se selecciona la tabla debajo de la cual se quiere insertar la nueva. Las siguientes líneas 21-27 son importantes para posicionar el cursor de forma correcta.

Debe remarcarse como se hace uso de la propiedad Selection del objeto Application, en este caso no se hace uso del Document. El objetivo, una vez que se tiene la tabla seleccionada, se baja una posición con el cursor, y luego agregar un enter o párrafo, para lograr separar las tablas.

Sino se agrega un párrafo intermedio entonces las tablas se crearán unidas, no logrando el efecto deseado.

[C#]
 

sábado, 3 de octubre de 2009

C# - Crystal Reports – Sumatoria Condicional (Conditional Sum)

 
Introducción

Cuando se requiere trabajar con Crystal en la creación de reportes un punto interesante es poder agregar condiciones a los campos de agregación, como se una sumatoria al final de la pagina, para ello tenemos herramientas en Crystal que utilizaremos en este ejemplo

Desarrollo

Para el ejemplo haremos uso de un ambiente simple en donde tendremos una lista de usuarios y sus asistencias a determinado evento.

Para ello ene le ejemplo se hará uso como origen de datos un DataSet Tipado, el cual era cargado en este caso de forma manual, pero bien podría hacerse accediendo a una base de datos

Como próximo paso se creara el reporte agregando a la sección de detalle los campos definidos en el DataSet.

Una vez que tenemos el reporte armado, se procederá a la creación del campo de sumatoria, para ello se realiza un click sobre el campos al cual se le quiere aplicar la suma y se selecciona Insert –> Running Total …

En el dialogo que se visualizara se podrá cambiar el nombre en este caso se utilizo “AsistenciasTotal”, y la parte mas importante será definir la formula que usara, para ello se deberá utilizar la opción que dice “Use a formula”

Al presionarla se visualizara otro cuadro en donde se introducirá la formula que es requerida en este caso filtraremos por un nombre, por ejemplo “Carlos”, por último se acepta el cuadro con el botón superior “Save and close”.

Cuando se acepte el ultimo cuadro seguramente los campos pueden quedar encimados, pera solucionarlo solo hace falta moverlos, ubicándolos donde sea requerido.

El reporte final, luego de ubicar correctamente los campos, quedara de esta forma:

Y al ejecutarlo el resultado será el siguiente:

Debe notarse además que si se visualiza el “Field Explorer” se encontrara el campo de sumatoria recién creado.

 

[C#]
 

sábado, 19 de septiembre de 2009

C# – AutoComplete ComboBox o TextBox

 

Introducción


Muchas veces es necesario exponer al usuario herramientas de búsqueda que le faciliten la interacción con la aplicación que desarrollamos.

Una de estas herramientas es precisamente el AutoComplete, por el cual el usuario podrá ir visualizando los ítems existentes a medida que se escribe en un control.

TextBox AutoComplete


Hacer uso de las opciones de autocomplete de estos controles es bastante simple, solo hace falta especificar un par de propiedades, pero hay que tener en cuenta algunos puntos.

Estas propiedades son:

AutoCompleteSource

AutoCompleteMode

AutoCompleteCustomSource

textBox1.AutoCompleteCustomSource = DataHelper.LoadAutoComplete();
textBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

En el ejemplo se visualiza como asignar estas propiedades, pero debe prestarse atención a la propiedad “AutoCompleteCustomSource” esta es clave, pues contendrá la lista de ítems.

Esta propiedad es justamente uno de los puntos a tener en cuenta, ya que requiere cargar una lista de ítems que provenga de una colección del tipo “AutoCompleteStringCollection”

Es por ello que en el ejemplo se visualiza la generación de los datos que cargaran esta colección

public static AutoCompleteStringCollection LoadAutoComplete()
{
    DataTable dt = LoadDataTable();

    AutoCompleteStringCollection stringCol = new AutoCompleteStringCollection();

    foreach (DataRow row in dt.Rows)
    {
        stringCol.Add(Convert.ToString(row["Nombre"]));
    }

    return stringCol;
}

El código es bastante simple de entender, se obtiene los datos desde la db y como siguiente paso los recorre cargando la lista de ítems del autocomplete.

ComboBox AutoComplete


La utilización de las opciones de autocomplete de un control Combobox son idénticas a las de un TextBox, solo difiere en que el combo requiere cargar sus ítems previamente para la selección por parte del usuario.

O sea el combobox requiere dos listas para bindear

- una normal que se asignara al DataSource, y en donde se especificara tanto el valor a desplegar como el valor de la key

- una especial con la lista de descripciones para el autocomplete

//
// Cargo los datos del combobox
//
comboBox1.DataSource = DataHelper.LoadDataTable();
comboBox1.DisplayMember = "Nombre";
comboBox1.ValueMember = "Id";

//
// cargo la lista de items para el autocomplete
//
comboBox1.AutoCompleteCustomSource = DataHelper.LoadAutoComplete();
comboBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
comboBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;

Conclusión


El autocomplete es una excelente opción para ayudar al usuario en la interacción con la aplicación brindándole un fácil acceso mientras escribe la búsqueda.

 

[C#] 
[VB.NET] 

sábado, 12 de septiembre de 2009

[Winform] Realizar tareas antes de inicializar aplicación

 

Introducción


Algunas veces en las aplicaciones es necesarios desplegar una ventana de login, o un formulario de inicio, o ambos, esto genera un gran problema cuando la aplicación esta iniciada, ya que la ventana principal del formulario se encuentra visible. La idea es poder realizar operaciones previas a la visualización de la pantalla definida como principal.

Es por ello que existe un área (o método) previa al inicio, en donde se puede realizar operaciones previas al inicio de la aplicación.

Esta función tiene el nombre de Main()

 

C# – Main


Cuando se crea una “Windows Applicacion”, se podrá apreciar una clase de nombre: Program.cs

Esta clase es clave para poder realizar trabajos previos.

De forma inicial encontrara código similar al siguiente:

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}

Las aplicaciones Windows en C#, siempre inician en un método Main.

Por supuesto podrá acomodarse para que realice algunas tareas previas, como ser, el mostrar un cuadro de login:

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    //
    // Realizo la apertura del formulario para validar el login
    // esta tarea es previa al inicio de la aplicacion
    //
    frmLogin login = new frmLogin();
    login.ShowDialog();

    //
    // Si el login es correcto, procedo con la apetura normal
    // de la aplicacion
    //
    if (login.DialogResult == DialogResult.OK)
        Application.Run(new frmPrincipal());

}

Como se observa en el código en las líneas 11 y 12, se incia un nuevo formulario, que tendrá la responsabilidad de de la autenticación de usuario.

Dependiendo del resultado de la operación previa es que se dará inicio, o no, al hilo principal de la aplicación.

Algunos puntos interesantes a destacar:

  • Es necesario que los formulario previos realicen una pausa en la ejecución de método Main(), es por eso que se hace uso del ShowDialog() para mostrar el formulario de login. En este caso el simple Show() no podrá ser utilizado ya que este no produce un stop, sino que deja al método Main() continuar su ejecución
  • El código de las líneas 4 y 5 siempre serán las primeras líneas del método  Main(), ya que estas deben ejecutarse previas al despliegue de cualquier formulario
  • Como se observara en ningún momento se hizo uso de alguna funcionalidad de exit, o quit que cerrara la aplicación, simplemente con evitar pasara por la asignación de un formulario en Application.Run(), fue suficiente.

 

VB.NET – Sub Main


Si bien la explicación de este método en C# es bastante similar a la de VB.NET, deben remarcarse algunas diferencias que son fundamentales para lleva a cabo la tarea de forma correcta.

En principio al crear la Windows Application en VB.NET, esta no creara el archivo de clase “Program”, o similar, como si se hace en C# de forma automática. Es por eso que la creación de la clase deberá hacerse de forma manual.

En el ejemplo se visualizará una clase de nombre “SubMain.vb”, y dentro de esta el siguiente código:

<STAThread()> _
Shared Sub Main()

    Application.EnableVisualStyles()
    Application.SetCompatibleTextRenderingDefault(False)

    Dim login As frmLogin = New frmLogin
    login.ShowDialog()

    If (login.DialogResult = DialogResult.OK) Then

        Application.Run(New frmPrincipal)

    End If

End Sub

Como verán es prácticamente idéntico método Main() codificado en C#.

Bien hasta aquí todo perfecto, pero falta un paso mas que tal vez no sea tan obvio como parece.

Resulta que para que la aplicación VB.NET pueda tomar el método Main() como inicio hay que desmarcar la el check de nombre “Enable Application framework”

VB.NET SubMain Startup

Como se observa en la imagen, este check esta desmarcado y es en ese momento en donde se puede cambiar en el combo “Startup object” al método Sub Main.

En este link: Cómo: Cambiar el objeto inicial de una aplicación (Visual Basic)

Se encontrará una explicación mas amplia del tema.

 

Conclusión


Haciendo uso de método Main() se puede realizar operaciones previas a la carga del formulario principal de la aplicación de una forma simple y prolija.

 

[C#] 
[VB.NET] 

domingo, 6 de septiembre de 2009

Comunicar formularios MDI

 

Introducción


Este post representa la continuación de Comunicar formularios de forma desacoplada

Pero a diferencia del anterior en esta oportunidad se trabajara con formularios MDI

 

Diferencias


Al hacerse uso de formularios MDI, algo que ya no podrá ser utilizado es el parámetro Owner en el método Show(), al realizar la apertura del formulario hijo.

Es por ello que será necesito hacer uso de una propiedad en el formulario hijo para salvar este inconveniente, y poder así determinar que formulario esta ejecutando la acción de apertura.

 

Primer Paso – Definición de la interfaz

A diferencia el ejemplo anterior en este oportunidad se hará uso de un valor algo mas complejo que simple texto en la comunicación, es por ello el uso de un objeto DataTable como medio de transporte de datos entre los formularios.

Aunque nada impediría que se utilices clases custom creadas por uno.

public interface IForm
{
    bool LoadDataGridView(DataTable dataTableParam);
}

En este caso la interfaz también retorna un valor que indicará si el procesamiento se realizó correctamente.

 

Segundo Paso – Definición del formulario padre

Al igual que en el anterior post el formulario padre deberá implementar una interfaz, la cual permitirá desacoplar la dependencia durante la comunicación.

public partial class Form1 : Form, IForm
{
    public Form1()
    {
        InitializeComponent();
    }

    #region IForm Members

    public bool LoadDataGridView(DataTable dataTableParam)
    {
        DataGridView1.DataSource = dataTableParam;

        return true;
    }

    #endregion


    private void Button1_Click(object sender, EventArgs e)
    {
        Form2 form2 = new Form2();

        form2.MdiParent = this.MdiParent;

        form2.Opener = this;

        form2.Show();
    }

}

Debe remarcarse algunos detalles, como es en este caso el uso de la propiedad MdiParent, que por supuesto siempre hará referencia al FormPrincipal, y que este ha sido declarado como MdiContainer

También hay que marcar la línea 26, en donde se visualiza el uso de la propiedad adicional, en este caso llamada Opener, es en esta donde se asignara el form que realizo la apertura del formulario.

 

Tercer paso – Definición del formulario hijo

Este formulario simplemente tendrá un botón que realizará el cierre de si mismo, pero durante esta operación se generarán los datos que serán pasados al formulario que se registro en la propiedad Opener.

Si bien este es un ejemplo, la misma técnica podría ser utilizada para realizar distintas acciones y pasaje de datos entre formularios.

public partial class Form2 : Form
{
    public IForm Opener { get; set; }

    public Form2()
    {
        InitializeComponent();
    }

    private DataTable LoadDataTable()
    {

        DataTable dt = new DataTable();

        dt.Columns.Add("Id");
        dt.Columns.Add("Nombre");

        for(int i=0 ; i <= 4; i++){
            DataRow row = dt.NewRow();

            row["Id"] = i;
            row["Nombre"] = String.Format("Nombre {0}", i);

            dt.Rows.Add(row);

        }

        return dt;
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        this.Close();
    }

    private void Form2_FormClosing(object sender, FormClosingEventArgs e)
    {
        DataTable dataTable = LoadDataTable();

        bool estadoOperacion = this.Opener.LoadDataGridView(dataTable);

        e.Cancel = !estadoOperacion;
    }

}

La línea 3 define la propiedad Opener cuya utilización se visualizo en el código del Form1, la declaración de la misma utiliza el concepto de propiedades autoimplementadas.

La interacción entre formularios retorna un resultado que el formulario hijo utilizara para saber si debe proseguir con el cierre del formulario o cancelarlo.

En este simple ejemplo el formulario padre retorna siempre un estado satisfactorio de la operación, pero podría ser utilizado para capturar errores e informarlo al formulario hijo de esta situación.

 

Conclusión


La interacción entre formulario en un entorno MDI requiere de ciertas modificaciones al uso normal de iteración entre estos, igualmente la idea básica no es afectada.

Solo la imposibilidad de definir un formulario como owner añadió cierta complejidad al ejemplo, pero fue salvada con el uso de propiedades.

 

[C#] 
[VB.NET] 

Comunicar formularios de forma desacoplada

Introducción


El objetivo de esta guía es demostrar como posibilitar la comunicación de formulario de una forma optima, haciendo uso de buenas practicas, y además bajando al mínimo el acoplamiento entre los formulario.

Para cumplir el objetivo es que se hará uso de interfaces, las cuales permitirán desacoplar la comunicación entre formulario.

 

Comunicación simple entre formularios


En este ejemplo se confeccionara una utilidad simple, básicamente se procederá a la apertura de un formulario, y la escritura cuadros de texto.

 

Primer Paso – Creación de los formularios

Lo primero que será necesario crear son los dos formulario los cuales intervendrán en la comunicación, esta operación se hace de forma simple como seguramente ya es conocida por todos. Los mismos contaran con sus respectivos cuadros de texto, y botones que permitirán las acciones entre ellos

 

Segundo Paso - Definición de la interfaz

En al interfaz se definirá la acción que será llevada a cabo en la comunicación, en este caso sera algo simple como copiar el contenido del formulario hijo al padre

Para ello se ha creado un archivo de nombre IForm.cs, el cual contendrá dicha definición.

public interface IForm
{
	void ChangeTextBoxText(string text);
}

como podrá preciarse es muy simple solo define el contrato que permitirá luego desacoplar las interfaces

 

Tercer Paso – Implementación de la interfaz, llamada al formulario hijo

La interfaz anteriormente creada será implementada por el formulario del cual se quiere recibir la acción, en este caso será el form principal, o sea quien realiza la llamada.

public partial class Form1 : Form, IForm
{
    public Form1()
    {
        InitializeComponent();
    }

    #region IForm Members

    public void ChangeTextBoxText(string text)
    {
        TextBox1.Text = text;
    }

    #endregion

    private void Button1_Click(object sender, EventArgs e)
    {
        Form2 form2 = new Form2();
        form2.Show(this);
    }
}

Hay que resaltar varias partes en esta sección de código:

- se notara en la primer línea como el formulario define la implementación de la interfaz

- la región establecida en el código, denota la implementación concreta de la misma, entre las líneas 8 al 15

También hay que remarcar como se realiza la apertura del formulario hijo, debe notarse el uso del parámetro en el método Show(), es allí donde se especifica quien será el owner, en este caso se hace uso de this ya que el formulario donde estamos en ese momento será quien realiza la apertura.

 

Cuarto Paso – Comunicación desde el formulario hijo

En esta ultima sección se visualizara como el formulario hijo envía datos al padre.

public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        IForm formInterface = this.Owner as IForm;

        if(formInterface != null)
            formInterface.ChangeTextBoxText(TextBox1.Text);
    }

}

En el código se puede apreciar claramente el uso de la propiedad Owner junto al this que en este caso representa al Form2.

Es interesante además remarcar la necesidad de castear la propiedad, en este caso particular se realiza mediante el uso de “as”
Es muy útil hacer uso de este ya que ante una imposibilidad o error de casteo la aplicación no producirá un error, en este caso simplemente la variable obtendrá el valor null, es por ello que se hace uso del “if” para preguntar si pudo realizar la conversión de tipos.

 

Conclusión


Bueno después de analizar el ejemplo verán como por medio de un simple interfaz se logra desacoplar las dependencias entre formulario facilitando la comunicación entre ellos, la simple definición de un contrato asegura que el formulario hijo pueda interactuar ya no solo con un forma padre único, sino con cualquier otro formulario que lo utilice e implemente la interfaz.

[C#]
[VB.NET]

domingo, 7 de junio de 2009

C# - jqModal – Alternar popup en un mismo link

 

En el desarrollo de este ejemplo se pretenderá abordad dos situaciones que fueron encontradas en jqModal:

  • Solucionar un problema que se produce en los cuadro de popup al renderizar por segunda vez una grilla que era generada de forma dinámica.
  • Teniendo un link permitir alterar de cuadro de dialogo, pero siempre haciendo uso del mismo link
Prueba 1 – Problema trigger popup jqModal y grilla desde $.ajax

Esta prueba la encontraran en el ejemplo bajo la carpeta “jqModal1”.

La pagina por defecto es Principal.aspx, la cual posee simplemente un botón que carga una grilla que es obtenida mediante la funcionalidad $.ajax.

Cuando esta grilla es cargada por primera vez, al presionar sobre los botones que esta renderiza, se podrá observar el despliegue de popup.

Pero el problema se presenta si se vuelve a recargar la grilla, lo cual produciendo que el evento definido en el trigger del popup de jqModal deje de funcionar.

Se debe notar que al recargar la grilla esta pasa nuevamente por el método javascript registerPopUp(), pero esto no parece tener efecto.

Prueba 2 – Solución al problema del trigger de jqModal

En este segundo ejemplo se encontró la solución al problema planteado en la primera prueba.

Simplemente se reemplazo la forma en cómo el trigger es registrado, haciendo uso de método de jqModal: jqmAddTrigger

En esta ocasión por más que se realicen recargas de la grilla, los mensajes popup seguirán funcionando.

Según se observa parece ser que este método de jqModal fuerza la actualización del registro de los cuadro de popup.

Prueba 3 – Alternar popup en el mismo link

La tercera prueba va un poco as mas allá en el uso de popup de jqModal, en esta se pretende lograr que cada link pueda alternar  el popup que utilice, después de cada llamada.

El objetivo es tener un link (que en este caso será una imagen), en donde al presionarlo la primera vez muestre un cuadro de dialogo, pero al presionarlo por segunda vez muestre otro distinto, es mas al cerrar este debería volver al mostrar el original, o sea alternar de cuadro de dialogo entre cada pulsación del link.

En la implementación se realizar la apertura de los cuadros sin hacer uso de triggers, este es un detalle a remarcar, ya que la asignación de evento de apertura del cuadro se realizara mediante evento click del link.

Este ejemplo lo encontraran en la carpeta “jqModal3”, deberán especificar la pagina “Principal.aspx” como default en la ejecución.

Observaran como se registra los eventos mediante las líneas:

var itemselected = null;
var clickPopUp1 = function() {   
	itemselected = $(this);   
	$('#popup-info1').jqmShow();
}
var clickPopUp2 = function() {   
	itemselected = $(this);   
	$('#popup-info2').jqmShow();
}

Se debe puntualizar en la utilización de la variable “itemselected”, esta es muy importante ya que permite detectar cual es el objeto especifico que realiza la llamada.

Cuando se hace uso de triggers, o el método jqmAddTrigger es el propio jqModal el que guarda el ítem que lanzo el evento bajo la variable “t”, que es retornada en los eventos.

Cuando se hace uso de los callback de evento del propio jqModal se cuenta con varios objetos que ayudan a controlar el dialogo:

  • w: (jQuery object) The dialog element
  • c: (object) The config object (dialog's parameters)
  • o: (jQuery object) The overlay
  • t: (DOM object) The triggering element

Se podrían hacer algo como lo siguiente:

onHide: function(h) {      
	h.o.remove();     
	$(h.t).removeClass("linkpopup1");     
	$(h.t).toggleClass("linkpopup2");     
	$(h.t).unbind("click").click(clickPopUp2);     
	$(h.t).children("img").attr("src", "../appimages/ok.ico");   
	
	h.w.hide();
},

Es aquí en donde el “h.t” representa el objeto que lanzo el evento, pero en este caso por hacer uso de eventos por fuera de “jqm” (por no definir un “trigger”) es necesario mantener el objeto que lanzo el evento en una variable por separado, en este caso “itemselected”, ya que “h.t” tendrá el valor “undefined”.

Se debe analizar además como se hace la conversión de los cuadros en el link:

var closePopup1 = function(hash) {    
	hash.o.remove();  
	
	$(itemselected).removeClass("linkpopup1");    
	$(itemselected).toggleClass("linkpopup2");    
	$(itemselected).unbind("click").click(clickPopUp2);    
	$(itemselected).children("img").attr("src", "../appimages/ok.ico"); 
	
	hash.w.hide();
}

La línea  hash.o.remove(); que deber quitar el “overlay”. La línea hash.w.hide(); oculta el cuadro de dialogo.

El resto de las líneas trabajan sobre el ítem que lanzo el evento:

  • cambiando la clase que determina el tipo de cuadro despliegan
  • cambia el evento que debe lanzar, primeramente eliminando actual y luego registrando el nuevo.
  • cambia la imagen asignada al ítem, debe recordarse que el objeto que lanza el evento es el link.

En este caso no se aplico pero se podría consulta cual es la clase de un ítem para determinar si cambiar de evento o no, haciendo uso de estas líneas:

if (!($(itemselected).is('.linkpopup1')))   
	$(itemselected).unbind("click").click(clickPopUp2);
Conclusion

Como se ha observado en las distintas pruebas se ha resuelto un problema que se presentaba en jqModal cuadro se recarga la información de forma dinámica.

Y implemento la alteración de de los cuadros de dialogo afectando solo a un link, permitiendo esto hacer nuestras aplicaciones mucho más dinámicas.

[Ejemplo]
[Documento]