miércoles, 12 de mayo de 2010

[WinForms] ComboBox Add ítems - SelectedValue

 

Introducción


Suele suceder cuando se quiere representar información en ciertos controles como ser combos, o listas, que no se conozca cual es la mejor forma para trabajarlos obteniendo un acceso rápido a sus ítems.

Una claro caso es el uso de combo cuando se necesite agregar ítems de forma manual, y no se dispone de una lista u objetos de datos para bindearlo.

Como proceder en estos casos? bueno este articulo intentara mostrar como trabajar con este control, mostrando como bindearlo y como seleccionar un valor programáticamente.

 

Cargar ítems al ComboBox


El primer paso será la carga de los ítems al combo, en este caso serán valores manuales los que se dispones, estos no proceden de una consulta a una tabla, o de algún servicio para recuperarlos, sino que son ítems que uno quieres desplegar en el control.

La mejor forma para hacerlo es usando una clase, específicamente creada para este caso, podría ser algo como esto:

public class Item
{
    public string Name {get; set;}
    public int Value{get; set;}

    public Item(string name, int value)
    {
        Name = name; 
        Value = value;
    }
    public override string ToString()
    {
        return Name;
    }
}

Es una clase muy simple que solo tiene dos propiedades, las cuales recibirá en el constructor los valores que uno decida cuando cree cada instancia.

La carga de cada ítem será realizada en el Load del formulario:

private void Form1_Load(object sender, EventArgs e)
{
    List<Item> lista = new List<Item>();

    lista.Add(new Item("Blue", 1));
    lista.Add(new Item("Red", 2));
    lista.Add(new Item("Green", 10));
    lista.Add(new Item("Yellow", 5));

    comboBox1.DisplayMember = "Name";
    comboBox1.ValueMember = "Value";
    comboBox1.DataSource=lista;

    //
    // Se asigna el evento para control el cambio de seleccion
    //
    comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged);

}

Como se visualiza la carga no respeta un orden especifico, ni sus valores de clave están en orden, esto es porque uno decide como se crea cada valor.

Lo que si es importante es que cada ítem no se agrega directo en el ComboBox, sino que se añade a la lista genérica definida del tipo de la clase creada anteriormente.

Al final se procede a bindear la lista al combo y se define los nombres de las propiedades que representan la visualización y el valor.

 

Asignar la selección de un ítem especifico


Un punto interesante es que será posible asignar directamente un valor al combo de forma programática y este seleccionara el ítem en la lista si es que esta presente.

private void btnSeleccionarValor_Click(object sender, EventArgs e)
{
    int valor = 0;
    if (!int.TryParse(txtValor.Text, out valor))
        return;

    comboBox1.SelectedValue = valor;
}

private void btnSeleccionarTexto_Click(object sender, EventArgs e)
{
    comboBox1.Text = txtTexto.Text;
}

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    Item seleccion = comboBox1.SelectedItem as Item;

    if (seleccion == null)
        return;

    MessageBox.Show(string.Format("Se ha seleccionado Color: {0} Valor: {1}", 
                                                seleccion.Name , 
                                                seleccion.Value ));

}

En el código se aprecia dos métodos de selección, ya sea por valor o texto, pero ambos trabajan de forma distintas.

En la selección por valor, si es asignado número que no en encuentra en la lista, el combo pasara a tomar el el SelectedIndex = –1, o sea no tendrá ningún valor activo, el SelectedItem pasara a devolver un null.

En cambio en la por Texto, si el valor no se encuentra en la lista dejara el que esta, no cambiara la selección.

 

[C#]
[VB.NET]

miércoles, 5 de mayo de 2010

Crystal Reports – Parameters – Como asignarlos desde código .net

 

Introducción


Un problema frecuente cuando se usa Crystal Reports es el pasaje de información por medio de parámetros al reporte.

El uso de parámetros es un aspecto importante para la creación de reportes, y suele suceder que no es tan intuitiva, ni directa la forma en como se debe programar esta funcionalidad.

 

Definición del parámetro en Crystal


En este ejemplo se usara el parámetro como filtro de los datos del reporte, por lo tanto se creara en el mismo momento en que se define la información.

Para esto se usara la opción “DataBase Expert”:

En el cuadro de dialogo que se despliega, se deber crear una nueva conexión a la base de datos Access usada en el reporte:

Haciendo uso en este cuadro, no directamente la tabla mostrada, sino la opción “Add Command”, la cual permitirá definir una consulta personalizada.

Es por eso que desplegara el cuadro:

 

Y se definirá un parámetro mediante la opción de la derecha.

Solo queda definir la consulta, haciendo uso del parámetro:

Seguramente cuando se acepte el dialogo, mostrara el mensaje pidiendo el ingreso de un valor al parámetro creado, se puede ingresar un valor cualquiera, o simplemente aceptar.

En cualquier momento la consulta escrita podrá ser modificada.

Luego de arrastrar los campos al diseñador del Crystal, podría visualizarse algo como lo siguiente:

En el “Field Explorer” se puede inspeccionar tanto los campos de la consulta, como los parámetros definidos para el reporte.

 

Pasaje de parámetros desde código


Al momento de ejecutar la aplicación será necesario pasarle al reporte desde el código el filtro que se ha definido, sino se hace seguramente el propio Crystal desplegara un cuadro pidiendo el ingreso de un valor.

En el siguiente código se muestra como llevar a cabo esta operación:

 

private void Form1_Load(object sender, EventArgs e)
{
          
		  //
          // Creo el parametro y asigno el nombre
          //
          ParameterField param = new ParameterField();
          param.ParameterFieldName = "CargoParam";

          //
          // creo el valor que se asignara al parametro
          //
          ParameterDiscreteValue discreteValue = new ParameterDiscreteValue();
          discreteValue.Value = "Developer";
          param.CurrentValues.Add(discreteValue);

          //
          // Asigno el paramametro a la coleccion
          //
          ParameterFields paramFiels = new ParameterFields();
          paramFiels.Add(param);

          //
          // Asigno la coleccion de parametros al Crystal Viewer
          //
          crystalReportViewer1.ParameterFieldInfo = paramFiels;

          //
          // Creo la instancia del reporte
          //
		  crListado report = new crListado();

          //
          // Cambio el path de la base de datos
          //
		  string rutadb = Path.Combine(Application.StartupPath, "TestDb.mdb");
		  report.DataSourceConnections[0].SetConnection("", rutadb, false);

          //
          // Asigno el reporte a visor
          //
		  crystalReportViewer1.ReportSource = report;

}

 

[C#]
[VB.NET]

martes, 4 de mayo de 2010

[Linq] DataSet – Agrupar y totalizar

 

Introducción


En este articulo analizaremos como trabajar con los datos procedentes de un datatable, realizando operaciones por medio de la ayuda de linq, y luego confeccionado un datatable distinto, cuya estructura de campos difiere de la original.

Para esta operación se contara con un datatable con documento de clientes, y un campo total que informa cuantos de estos se tienen registrados.

La idea es obtener en otro datatable la cantidad total agrupando todos los clientes.

 

Agrupar con Linq


Para esta operación se realizaría una agrupación mediante la funcionalidad que provee linq.

private void button1_Click(object sender, EventArgs e)
{
    IEnumerable<IGrouping<string, DataRow>> query = from item in Datos().AsEnumerable()
                                                    group item by item["Documento"].ToString() into g
                                                    select g;


    DataTable resultado = Transformar(query);

    dataGridView1.AutoGenerateColumns = true;
    dataGridView1.DataSource = resultado;

}

Como se observa en la query de linq se agrupa por medio del campo “Documento”, y el resultado de esta operación se alojo en “g”, valor que es devuelto en la consulta.

Si bien podría haberse igualado toda la consulta a una variable definida como “var”, esto hubiera imposibilitado el pasaje de la información a un método de transformación, para la creación del nuevo datatable, es por eso que se crea el IEnumerable<>, que contiene el IGrouping<> también genérico.

 

Creación del nuevo DataTable


En esta operación en una primera implementación es algo manual, ya que toma el resultado de la consulta linq e itera para cargar el nuevo datatable, y en su camino realiza las operaciones de calculo.

private DataTable Transformar(IEnumerable<IGrouping<string, DataRow>> datos)
{
    //
    // Se define la estructura del DataTable
    //
    DataTable dt = new DataTable();
    dt.Columns.Add("Documento");
    dt.Columns.Add("CantRegistros");
    dt.Columns.Add("Total");

    //
    // Se recorre cada valor agruparo por linq y se vuelca el resultado 
    // en un nuevo registro del datatable
    //
    foreach (IGrouping<string, DataRow> item in datos)
    {
        DataRow row2 = dt.NewRow();
        row2["Documento"] = item.Key;
        row2["CantRegistros"] = item.Count();
        row2["Total"] = item.Sum<DataRow>(x => Convert.ToInt32(x["Total"]));

        dt.Rows.Add(row2);
    }

    return dt;
}

Debe observarse como se trabaja con los métodos extendidos, y en ellos se aplica la funcionalidad Lambda, puntualmente al indica que campo debe sumarse.

 

[C#]
 

 

Creación del DataTable con CopyToDataTable()


Una alternativa algo mejor a la transformación de datos podrías ser por medio del uso del método de extensión CopyToDataTable()

DataTableExtensions.CopyToDataTable<(Of <(T>)>)

Hay que remarcar un punto con este método, ya que de forma estándar solo permite convertir a DataTable aquellas consulta linq que devuelvan como resultado una DataRow.

Pero en nuestro ejemplo planteado esto es diferente ya que el datatable a devolver requiere de una transformación de los registro con respecto a los datos originales.

Pero esta situación esta contemplada, ya que es posible implementar un método CopyToDataTable<> genérico, en donde a base de un tipo de dato se arme un datatable.

Para esta implementación, se hará uso de la explicación proporcionada por siguiente articulo:

Cómo implementar CopyToDataTable<T> donde el tipo genérico T no es un objeto DataRow

El código del link de msdn, se ha volcado al archivo de nombre “CopytoDataTable.cs”, se podrá observar en el código de ejemplo adjunto en el articulo.

La implementación de la funcionalidad quedo reducida al siguiente código:

private void button1_Click(object sender, EventArgs e)
{

    var query = from item in Datos().AsEnumerable()
                group item by item["Documento"].ToString() into g
                select new DocumentoResult
                {
                    Documento = g.Key,
                    CantRegistros = g.Count(),
                    Total = g.Sum(x => Convert.ToInt32(x["Total"]))
                };


    DataTable resultado = query.CopyToDataTable<DocumentoResult>();

    dataGridView1.AutoGenerateColumns = true;
    dataGridView1.DataSource = resultado;

}

Esta nueva alternativa ya no necesita iterar por cada registro que dio como resultado la consulta linq, es mas se puede hacer uso del “var”, puesto que no es necesario pasar por parámetro la consulta para trabajarla.

Pero si se necesito de un pequeño cambio, en donde se ha definido una clase, de nombre “DocumentoResult”, la cual define los campos del nuevo DataTable.

public class DocumentoResult
{
    public string Documento { get; set; }
    public int CantRegistros { get; set; }
    public int Total { get; set; }

}

Siendo esta la clase usada en la definición del método genérico CopyToDataTable<>, cuando se usa en la línea:

DataTable resultado = query.CopyToDataTable<DocumentoResult>();

 

[C#]
 

domingo, 2 de mayo de 2010

C# - [Winforms] UserControl - Focus()

 

Introducción

Suele suceder que la implementación de cierta funcionalidad cuando se desarrollan componentes no sea tan obvia como uno se imagina.

Este es el caso de la implementación del método Focus() en un User Control.

User Control - Método Focus

La clase base de la cual hereda el componente es UserControl, y este método en particular no esta marcado para poder realizar un override, es por eso que hay que redefinirlo mediante el uso del modificador new

public new bool Focus()
{
    //
    // Es necesario activar el control para que tome el foco
    //
    this.Select();
    return textBox1.Focus();
}

Un detalle a remarcar en el código es el uso del método Selected(), el cual activa el User Control y permite así que un control contenido en este tome el foco.

 

[C#]
 

C# - [DataGridView] DataGridViewComboBoxColumn – Variar contenido del combobox respecto a la fila

 

Introducción

El objetivo del articulo es demostrar algunos problemas que pueden surgir cuando se necesita que el ComboBox ubicado en las celdas del DataGridView, varíe su contenido de acuerdo al registro que se visualiza.

Para demostrar el problema, se ha definido un dominio del ejemplo intentara cargar una grilla de sucursales, de los cuales mostraran el listado de los teléfonos disponibles.

Por supuesto para cada sucursal los teléfonos disponibles varían, por lo tanto definir el contenido del combo como una única lista no es posible.

Carga del contenido variable en ComboBox

En el evento Load del formulario se procede a la carga de las sucursales, e inmediatamente después de esta operación se invoca a un método creado con el fin de la carga de los combos de cada registro de a grilla.

Este método recorre en un ciclo cada registro, toma el id de la sucursal que se encuentra en la celda del registro actual, y también toma la celda que representa el combo.

Con las anteriores dos selecciones procede a realizar una consulta a los datos y cargar en el combo los teléfonos de esa sucursal.

 

private void frmSucursales_Load(object sender, EventArgs e)
{
    SucursalesEntities sucursalesContext = new SucursalesEntities();

    dgvScursales.AutoGenerateColumns=false;
    dgvScursales.DataSource = sucursalesContext.Sucursales;

    //
    // Luego de bindear la grilla, cargo el contenido de cada fila
    // en el combo de telefonos para cada sucursal
    //
    LoadGrillaComboBoxTelefonos();

}

private void LoadGrillaComboBoxTelefonos()
{
    foreach (DataGridViewRow row in dgvScursales.Rows)
    {
        int idSucursal = Convert.ToInt32(row.Cells["IdSucursal"].Value);

        DataGridViewComboBoxCell comboboxCell = row.Cells["Telefonos"] as DataGridViewComboBoxCell;
        
        SucursalesEntities sucursalesContext = new SucursalesEntities();

        comboboxCell.DataSource = sucursalesContext.Telefonos.Where(x => x.IdSucursal == idSucursal);
        comboboxCell.ValueMember = "Id";
        comboboxCell.DisplayMember = "Numero";

    }
}

Algunos Comentarios

Durante la realización del ejemplo se realizaron varias pruebas, con algunos eventos que se adaptaran a la situación y evitaran tener que recorrer en un “foreach” cada registro del DataGridView.

Después de investigar un poco los únicos dos eventos que se adaptarían a este caso serian el RowsAdded y el CellFormatting, pero ambos trajeron problemas, lo que impidió su uso.

El CellFormatting, tiene como problema que se ejecuta contantemente, ya que es lanzado al redibujar la celda, y como esta operación requiere la consulta a los datos, generaba un efecto de bloqueo en la pantalla, el cual no es deseado.

El RowsAdded, si bien se ejecuta por cada fila agregada en la operación de bindeo de los datos, no había forma interna en el evento de detectar que fila se estaba agregando, ya que el argumento del evento e.RowIndex o e.RowCount, no devuelven un valor correcto.

Como conclusión se dejo de lado los eventos y se hizo uso del “foreach”, implementado esto en un método, cuya utilización se realiza luego que se bindearon los datos en el control.

 

[C#]
[VB.NET]

[Crystal Reports] Exportar DataGridView a Crystal

 

Introducción


Comúnmente cuando uno confecciona un reporte la información que se usara proviene de una tabla o consulta a una base de datos, pero suele suceder que a veces se necesite información de orígenes distintos, como ser el caso de controles del propio formulario, donde el usuario cargado información.

Este el caso planteado en el articulo, como generar un reporte en Crystal Reprots, pero utilizando información que no se encuentra en una base de datos, sino que esta aun contenida en los controles del formulario, no hay una tabla a la cual se puede vincular el reporte.

Para resolver este problema se hará uso de DataSet Tipados, los cuales no solo brindaran el esquema necesario para definir la estructura de datos que usara el reporte, sino que además será contenedor de los datos que luego se asignaran como origen de datos.

 

Información Adicional


Como referencia adicional podría mencionar dos excelente artículos que han sido de mucha ayuda, los cuales detallan muy bien como hacer uso de los reportes vinculados a DataSet Tipados, pero en este caso si toman la información mediante una consulta a la db.

Informes Crystal Reports

Creación de Reportes con Crystal Reports en Visual Studio 2005/2008

 

Creación del reporte


Uno de los primeros paso consiste en la creación del reporte, para el mismo es necesario definir los datos o mejor dicho el esquema de los mismos, tarea que será responsabilidad del DataSet Tipado.

Es por ello que en el ejemplo podrán ver un archivo de nombre dtCompra.xsd, este es precisamente el DataSet que se usara en el reporte y definirá la estructura de datos del mismo.

crystal4

En este se han creado manualmente dos DataTable que representan los datos del cliente y los ítems que ha seleccionado en la compra.

Ambas tablas se han creado de forma manual, arrastrando los datatable de la toolbox de la izquierda, y luego agregando sus columnas. Al no existir base de dato alguna, es necesario crear estas de forma manual, no se tiene ayuda del “Solution Explorer” para arrastrar tablas y crearlas dinámicamente, justamente porque no existen.

Una vez creado el esquema de los datos se procede con el reporte, para ello se agrega uno nuevo en el proyecto, en este caso con el nombre de Factura.rpt, y se configuran los datos mediante la opciones del “Field Explorer”, agregando el origen de datos, como muestra la imagen:

crystal1

Del cuadro de dialogo siguiente se selecciona el DataSet creado en el paso previo:

crystal2

Por ultimo, el asistente de Crystal permitirá redefinir como se vinculan las tablas, en este caso por representar información a distinto nivel no existe una relación, pero si se desarrollara algún reporte del estilo maestro-detalles, podría existir un vinculo entre las tablas.

crystal3

Una vez concluidos los pasos para configurar el esquema de datos del reporte, arrastrando los campos al reporte, podría armar un reporte similar a este:

crystal5

 

Carga de los datos en el DataSet Tipado


El siguiente paso consiste en volcar la información ingresada por el usuario en los controles al dataset tipado, creando en dtComrpas.xsd

Para esto se codifico un método responsable de esta tarea:

private dtCompra GenerarFactura()
{
    dtCompra facturacion = new dtCompra();

    //
    // Agrego el registro con la info del cliente
    //
    dtCompra.DatosClienteRow rowDatosCliente = facturacion.DatosCliente.NewDatosClienteRow();
    rowDatosCliente.Nombre = txtNombre.Text;
    rowDatosCliente.Direccion = txtDireccion.Text;
    rowDatosCliente.Telefono = txtTelefono.Text;
    rowDatosCliente.DNI = txtDni.Text;

    facturacion.DatosCliente.AddDatosClienteRow(rowDatosCliente);

    //
    // Itero por cada fila del DataGridView creando el registro 
    // en el DataTabla 
    //
    foreach (DataGridViewRow row in dgvCompras.Rows)
    {
        dtCompra.ComprasRow rowCompra = facturacion.Compras.NewComprasRow();  
        rowCompra.Descripcion = Convert.ToString(row.Cells["Descripcion"].Value);
        rowCompra.PrecioUnitario = Convert.ToInt32(row.Cells["PrecioUnitario"].Value);
        rowCompra.Cantidad = Convert.ToInt32(row.Cells["Cantidad"].Value);

        facturacion.Compras.AddComprasRow(rowCompra);
    }


    return facturacion;
}

Este método es fundamental ya que será el encargado de cargar los datos en una instancia del dataset tipado, para ello tomara el contenido de los controles y los agregara como registros en el DataTable que corresponda.

Es de apreciar como opera con el DataGridView, recorriendo cada fila del mismo, tomando los valores de sus celdas y creando nuevos registros en el datatable que representa las Compras.

 

Visualización del Reporte


Una vez que tenemos los datos cargados en una instancia del DataSet, solo queda asignarlos como origen de datos del reporte.

Para esta operación el reporte será mostrador en un formulario especialmente creado para tal fin.

Pero hay un detalle, los datos han sido creado en un formulario distinto, es por eso usa la técnica por medio del constructor para pasar la información de un formulario a otro. Esto podrá visualizarse en el formulario frmVisorFactura.

public partial class frmVisorFactura : Form
{
    dtCompra _datosreporte;

    private frmVisorFactura()
    {
        InitializeComponent();
    }

    public frmVisorFactura(dtCompra datos): this()
    {
        _datosreporte = datos;
    }

    private void frmVisorFactura_Load(object sender, EventArgs e)
    {
        Factura _factura = new Factura();
        _factura.SetDataSource(_datosreporte);

        crwFactura.ReportSource = _factura;
    }
}

Allí se ha agregado un constructor que acepta como parámetro el dataset que requiere el reporte para visualizarse.

También puede verse como en el evento Load se hace uso de los datos pasados al formulario y como son asignados al DataSource de la instancia del reporte.

Desde el frmCompra (donde el Usuario de la aplicación ingresa los datos), solo basta con invocar al formulario de la siguiente manera:

private void btnReporte_Click(object sender, EventArgs e)
{
    dtCompra datos = GenerarFactura();

    frmVisorFactura frm = new frmVisorFactura(datos);
    frm.Show();
}

En al primer línea se invoca al método que carga la estructura de datos, y en las siguiente se crea la instancia del formulario que contienen el control Crystal Report Viewer, al cual se le pasa en el constructor la información a visualizar.

Hay que remarcar en este punto el porque de todo esta lógica en al comunicación de los formularios. Resulta que uno de ellos es el que contiene la información y el otro es el que permite la visualización, la idea es mantener bien clara la separación de responsabilidades de cada uno, y no tener que acceder desde un formulario a los controles del otro, eso no es correcto, pero si se usa un medio que permita la comunicación, como ser en este caso el dataset tipado ,se puede trabajar en cada formulario de forma transparente y prolija.

 

[C#]
[VB.NET]

sábado, 1 de mayo de 2010

Login – Usando Password con Hash

 


Introducción


Este artículo intentara plantear la forma en que se puede crear un login que pueda ser usado tanto para un entorno Web, como para uno WinForms.

Se vera como mediante la definición de funcionalidad en una capa de Servicio el código de autenticación puede ser reutilizado, en realidad este nombre que le he dado a la capa podría llevar cualquier otro nombre, podrías ser directo la capa de Datos, o un Repository, la idea es mantener la técnica que permita reutilizar la funcionalidad.

Algo particular que encontraran en el ejemplo esta referido al password no se guarda de forma plana en la db, sino que por motivos de seguridad se aplica una función de hash.

La misma tiene una particularidad interesante, ya que el algoritmo solo se puede aplicar en un solo sentido, siempre obteniendo el hash de un texto o mensaje, pero nunca de un valor hash obtener el mensaje original.

Al aplicar el algoritmo hash a un mensaje, el resultado será siempre el mismo, por lo tanto esta técnica utilizada en el login permite que el password este siempre seguro. En el ejemplo verán como en el momento de login se aplica el algoritmo al la entrada del usuario y este valor se busca como password en la tabla, al asegurarse que un mismo texto produce el mismo resultado de hash, si se ingreso correctamente usuario y password este debe coincidí con el presente en la base de datos.


Definición de la capa de Servicio



El primer paso que explicaremos en el articulo es el código común que será reutilizado tanto por la capa web como winform, se trata de la denominada capa de servicio.

La misma básicamente se compone de una fachada estática, con métodos para autenticar o insertar un usuario, por supuesto la funcionalidad adicional para actualizar no se incluyo en el ejemplo, pero podrías agregarse si fuera necesario.

public static bool Autenticar(string usuario, string password)
{
    string sql = @"SELECT COUNT(*)
                      FROM Usuarios
                      WHERE NombreLogin = @nombre AND Password = @password";


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

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("@nombre", usuario);

        string hash = Helper.EncodePassword(string.Concat(usuario, password));
        command.Parameters.AddWithValue("@password", hash);

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

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

    }
}
public static UsuarioEntity Insert(string nombre, string apellido, string nombreLogin, string password)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = nombre;
    usuario.Apellido = apellido;
    usuario.NombreLogin = nombreLogin;
    usuario.Password = password;

    return Insert(usuario);
}

public static UsuarioEntity Insert(UsuarioEntity usuario)
{

    string sql = @"INSERT INTO Usuarios (
                           Nombre
                          ,Apellido
                          ,NombreLogin
                          ,Password)
                      VALUES (
                            @Nombre, 
                            @Apellido, 
                            @NombreLogin,
                            @Password)
                    SELECT SCOPE_IDENTITY()";


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

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Nombre", usuario.Nombre);
        command.Parameters.AddWithValue("Apellido", usuario.Apellido);
        command.Parameters.AddWithValue("NombreLogin", usuario.NombreLogin);

        string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));
        command.Parameters.AddWithValue("Password", password);

        conn.Open();

        usuario.Id = Convert.ToInt32(command.ExecuteScalar());

        return usuario;
    }
}

El método importante en estos dos métodos es el siguiente:

string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));

Esta línea es la que arma el password real que se salvara en el campo de la tabla, como se observa para una seguridad aun mayor se une el nombre del usuario y el password, pasándolos luego por la función que aplica el hash.

internal class Helper
{
    public static string EncodePassword(string originalPassword)
    {
        SHA1 sha1 = new SHA1CryptoServiceProvider();

        byte[] inputBytes = (new UnicodeEncoding()).GetBytes(originalPassword);
        byte[] hash = sha1.ComputeHash(inputBytes);

        return Convert.ToBase64String(hash);
    }
}

En el método Autenticar() como se había comentado no se obtiene el password que originalmente el usuario tienen, sino que se aplica el hash sobre los valores ingresados al momento de realizar el login, ejecutando el query con esta información como filtro.

Si el usuario y password ingresados en la autenticación son los correctos al aplicar el hash retornara la misma cadena que se tiene en la tabla, por lo tanto al ejecutar el query debería devolver el registro del usuario.


Autenticación desde una aplicación Winform



En el método Main() del archivo Program.cs se encontrara la llamada a el formulario que será responsable de al autenticación.

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        
        frmLogin frm = new frmLogin();
        frm.ShowDialog();

        if(frm.DialogResult == DialogResult.OK)
            Application.Run(new frmPrincipal());

    }
}

El formulario de login (frmLogin.cs), tendrá un código muy simple, solo tomara el usuario y password invocara al servicio y según el resultado retornara la respuesta al método Main() para que se abra o no el formulario principal.

private void btnAceptar_Click(object sender, EventArgs e)
{
    string nombre = txtNombre.Text;
    string password = txtPassword.Text;

    if (LoginService.Autenticar(nombre, password))
        this.DialogResult = DialogResult.OK;
    else
        this.DialogResult = DialogResult.Abort;

}

private void btnCancelar_Click(object sender, EventArgs e)
{
    this.DialogResult = DialogResult.Cancel;
}

Adicionalmente se agrego un formulario para la creación de nuevos usuarios, si bien en este no se ha programado la edición completa del usuario, o sea toda la funcionalidad de ABM, el Insert permitirá analizar como se registra un nuevo usuario y se guarda su password.

private void btnAceptar_Click(object sender, EventArgs e)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = txtUsuario.Text;
    usuario.Apellido = txtApellido.Text;
    usuario.NombreLogin= txtNombreLogin.Text;
    usuario.Password = txtPassword.Text;

    usuario = LoginService.Insert(usuario);

    MessageBox.Show(string.Format("Se ha creado el usuario, ID: {0}", usuario.Id));
}

Nuevamente gracias a la funcionalidad encapsulada del Servicio, el código del formulario es muy simple.

 


Autenticación desde una aplicación Web



La autenticación en un entorno web es algo distinta a una aplicación Windows, en este caso se ha configurado en el web.config, las líneas necesarias para que todo el sitio este bajo una autenticación por medio de “Forms”

<authentication mode="Forms">
	<forms name="appNameAuth" path="/" loginUrl="frmLogin.aspx" defaultUrl="Default.aspx" protection="All" />
</authentication>
  
  <authorization>
    <deny users="?" />
  </authorization>

En el archivo de configuración del sitio se especifica cual será la pagina por defecto y cual la de login, de esta forma si no hay usuario autenticado, solo el sitio redirección a estas url.
El formulario web frmLogin.aspx tiene un código muy simple:

protected void ProcessLogin(object sender, EventArgs e)
{
    if (LoginService.Autenticar(txtUser.Text, txtPassword.Text))
    {
        FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked);
    }
    else
        ErrorMessage.InnerHtml = "<b>Usuario o contraseña incorrectos...</b> por favor re-ingrese las credenciales...";
}

Como se observa luego de la autenticación se hace uso de los métodos provistos por .net en el namespace System.Web.Security, para trabajar con al seguridad.
Aquí se esta indicando el nombre del usuario que paso la validación, y esto hace que solo el sitio se redireccione a la pagina marcada por defecto en el web.config.

En la pagina principal del sitio se ha agregado algo de código para mostrar el nombre del usuario autenticado, y dar la posibilidad de un logout.

protected void Page_Load(object sender, EventArgs e)
{
    Label1.Text = string.Format("Bienvenido al Sistema: {0}", Thread.CurrentPrincipal.Identity.Name); 
}

protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
    if (Menu1.SelectedValue == "Salir")
    {
        FormsAuthentication.SignOut();
        FormsAuthentication.RedirectToLoginPage();
    }
}

También se ha creado una pagina para crear nuevos usuarios (Usuarios.aspx)

protected void btnAceptar_Click(object sender, EventArgs e)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = txtNombre.Text;
    usuario.Apellido = txtApellido.Text;
    usuario.NombreLogin = txtLogin.Text;
    usuario.Password = txtPassword.Text;

    usuario = LoginService.Insert(usuario);

    ClearControls();
    lblMessage.InnerHtml = string.Format("Se ha creado el usuario, ID: {0}", usuario.Id);

}

protected void btnCancelar_Click(object sender, EventArgs e)
{
    Response.Redirect("~/Default.aspx");
}

private void ClearControls()
{
    txtNombre.Text="";
    txtApellido.Text="";
    txtLogin.Text="";
    txtPassword.Text="";
}

Código de ejemplo


Como requerimiento será necesario contar con al menos Sql Server Express 2008, para que pueda ejecutarse la aplicación.

La tabla de usuario ya posee un registro para ingresar la primera vez, la información es la siguiente:
Usuario: admin
Password: pass123

Para no tener dos base de datos iguales, se agrego un Build Event en el proyecto web para que copie la db desde el proyecto de Class Library a la carpeta App_Data del sitio Web.

Puede que a veces si se compila repetida veces la compilación lance un error al intentar copiar la db a la carpeta App_Data, cuya operación no pueda hacerlo porque el servicio de sql server la tenga loqueada, en ese caso simplemente ejecuten a pesar de este mensaje de error, ya que funcionara sin problemas.

[C#] 
[VB.NET] 

jueves, 29 de abril de 2010

[jQuery] DropDownList anidados (nested DropDownList)

 

Introducción


La finalidad del articulo es mostrar como utilizando puramente jquery se puede vincular dos dropdownlist de forma tal que al cambiar la selección de uno de ellos se recargue el contenido del siguiente.

En el articulo se hará uso de una invocación a un Page Method, para exponer la información y poder recuperar de forma dinámica.

 

Inicialización de las listas


En el evento Page_Load se proceder a la carga inicial de los combos, esto será útil para que el usuario visualice una selección por defecto

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        ddlPaises.DataSource = GetPaises();
        ddlPaises.DataTextField = "Pais";
        ddlPaises.DataValueField = "Cod";
        ddlPaises.DataBind();

        ddlCiudades.DataSource = GetCiudadesByPais(Convert.ToInt32(ddlPaises.SelectedValue));
        ddlCiudades.DataTextField = "descripcion";
        ddlCiudades.DataValueField = "cod";
        ddlCiudades.DataBind();
    }

    
}


[WebMethod]
public static List<CiudadEntity> GetCiudadesByPais(int pais)
{
    var query = from item in GetCiudades().AsEnumerable()
                where Convert.ToInt32(item["CodPais"]) == pais
                select new CiudadEntity
                { 
                  cod = Convert.ToInt32(item["Cod"]),
                  descripcion = Convert.ToString(item["Ciudad"])
                };

    return query.ToList<CiudadEntity>();

}

Es importante remarcar como uno de los métodos tiene definido el atributo [WebMethod] y además se ha definido como static, este método será clave en la invocación desde el cliente con jQuery.

 

Cambio de selección desde el cliente, con jQuery


La definición del html es muy simple, solo son 2 DropDownList.

<form id="form1" runat="server">
<table>
    <tr>
        <td>Paises:</td>
        <td>
            <asp:DropDownList ID="ddlPaises" runat="server">
            </asp:DropDownList>
        </td>
    </tr>
     <tr><td colspan="2"></td></tr>
    <tr>
        <td>Ciudades:</td>
        <td>
            <asp:DropDownList ID="ddlCiudades" runat="server">
            </asp:DropDownList>
        </td>
    </tr>
</table>

</form>

La definición de script seria la siguiente:

<script language="javascript" type="text/javascript">

    $().ready(function() {

        $("#<%=ddlPaises.ClientID%>").change(function() {

            // armo el objeto que servira de parametro, para ello utilizo una libreria de JSON
            //este parametro mapeara con el definido en el web service
            var params = new Object();
            params.pais = $("#<%=ddlPaises.ClientID%>").val();
            params = JSON.stringify(params);

            $.ajax({
                type: "POST",
                url: "Default.aspx/GetCiudadesByPais",
                data: params,
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                async: false,
                success: LoadCiudades,
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    alert(textStatus + ": " + XMLHttpRequest.responseText);
                }
            });

        });

        $("#<%=ddlCiudades.ClientID%>").change(function() {
            alert("Ha seleccionado: " + $("#<%=ddlCiudades.ClientID%> :selected").text());
        });

    });

    function LoadCiudades(result) {
        //quito los options que pudiera tener previamente el combo
        $("#<%=ddlCiudades.ClientID%>").html("");

        //recorro cada item que devuelve el servicio web y lo añado como un opcion
        $.each(result.d, function() {
            $("#<%=ddlCiudades.ClientID%>").append($("<option></option>").attr("value", this.cod).text(this.descripcion))
        });
    }
</script>

En el $().ready() se asocian a los combos dos eventos para poder atrapar un cambio de selección, es por eso que se usa el change().

Ante el cambio de selección de un país se procede en primer instancia al armado de parámetro que será enviado como filtro el Web Method, el cual se ha visto en el código anterior.

Para esta operación se hará uso de una librería de nombre json2.js, esta permitirá armar con objetos de javascript la información de una forma simple, y mediante el método JSON.stringify() convertir esta en json para ser enviada como parámetro.

En la operación también se toma el valor seleccionado en el combo usando:

$("#<%=ddlPaises.ClientID%>").val()

también se podría haber usado:

$(this).val()

ya que quien lanza el evento es el propio combo de países.

Es importante remarcar en este punto como se seleccionan los controles de asp.net usando en el selector de jquery, como asp.net renombran el atributo id de los controles es necesario usar el ClientID para tener el nombre correcto.

El siguiente paso es la invocación al método del servicio, para lo cual se utiliza el $.ajax(), hay algunas propiedades importantes que deben tenerse en cuenta, una de ellas es la definición de la url a invoca:

url: "Default.aspx/GetCiudadesByPais"

como se vera define directamente la propia pagina web donde se encuentra y luego el nombre del Web Method, pero podría ponerse los método del servicio en otro pagina si se requiere.

En el parámetro

data: params,

se define la variable del paso previo, en esta estará contenida el json que será enviado el web method.

También es importante definir si la invocación será asíncrona o no

async: true

en este caso no se espera a que el servicio web responda para continuar. Aquí no afecta si es es asíncrona o no la invocación ya que no hay código que continúe en la función javascript, pero puede que sea necesario a veces esperar una repuesta para poder tomar una decisión.

También se definen eventos en caso de una ejecución satisfactoria o un error

success: LoadCiudades,

hay varias formas de hacer esto, aquí se decidió usar una función nueva para procesar el contenido resultante de la invocación. Este método recibirá por parámetro de nombre “result” en donde se alojara el json de resultado.

 

Carga del combo de Ciudades


Es interesante analizar la función responsable de la carga de las ciudades proveniente de la invocación

function LoadCiudades(result) {
    //quito los options que pudiera tener previamente el combo
    $("#<%=ddlCiudades.ClientID%>").html("");

    //recorro cada item que devuelve el servicio web y lo añado como un opcion
    $.each(result.d, function() {
        $("#<%=ddlCiudades.ClientID%>").append($("<option></option>").attr("value", this.cod).text(this.descripcion))
    });
}

Esta recibe el resultado, y la primer operación será limpiar el combo, para ello es que lo selecciona y quita el html dentro de este, o sea quita los tag <option> que posea.

Luego toma cada valor devuelto en el json y lo recorre:

$.each(result.d, function() {..}

algo que seguro notaran extraño es el uso de result.d, este “d” es una propiedad del objeto result y allí contiene la información devuelta en la invocación, en este caso la lista de ciudades.

Se se pone un breakpoint en el código javascript, se podrá analizar claramente el valor devuelto en el json

Por supuesto el this dentro de la función representa cada ítem en el loop, y por tratarse de json este esta tipado con los mismas propiedades definida en la clase CiudadEntity devuelta por el web method.

 

 

[C#]
[VB.NET]

jueves, 22 de abril de 2010

C# - [DataGridView] – Pasar Registros entre Grillas

 

Introducción


El articulo explica de que forma se puede seleccionar registros en el control DataGridView y pasarlos a otro, representando este la selección de las filas por el usuario.

En uno de los DataGridView se listaran productos, y por medio de un checkbox en cada fila, se podrá seleccionar que ítems se desean seleccionar.

Botones que indican la dirección serán los encargados de ejecutar la opción de pasaje.

El diseño de la interfaz puede apreciarse en esta imagen:

form1

 

Carga de la lista de productos


Para esta operación se requerirá la ayuda del DataSet tipado el cual define la estructura de la entidad, pero será responsabilidad de la clase con sufijo DAL, la que cargara la información, proveniente de la base de datos.

private void frmPedidos_Load(object sender, EventArgs e)
{
    //
    // Se recupera los datos de los productos desde la tabla
    //
    dtoProductos datos = ProductoDAL.ProductosGetAll();

    //
    // Se bindean los datos a la grilla
    //
    dgvProductos.AutoGenerateColumns = false;
    dgvProductos.DataSource = datos;
    dgvProductos.DataMember = "Productos";

}

Selección de un ítem en la grilla de origen


Hay que destacar que la operación sobre un DataGridView difiere si esta se encuentra previamente enlazada a datos, o no.

En esta primera operación implica la sección de un producto que pasara a una lista no enlazada a datos, por lo tanto se podrá hacer uso de método Add() de la colección de Rows de la grilla.

private void btnSeleccionar_Click(object sender, EventArgs e)
{
    //
    // Se define una lista temporal de registro seleccionados
    //
    List<DataGridViewRow> rowSelected = new List<DataGridViewRow>();

    //
    // Se recorre ca registro de la grilla de origen
    //
    foreach (DataGridViewRow row in dgvProductos.Rows)
    {
        //
        // Se recupera el campo que representa el checkbox, y se valida la seleccion
        // agregandola a la lista temporal
        //
        DataGridViewCheckBoxCell cellSelecion = row.Cells["Seleccion"] as DataGridViewCheckBoxCell;

        if (Convert.ToBoolean(cellSelecion.Value))
        {
            rowSelected.Add(row);
        }
    }

    //
    // Se agrega el item seleccionado a la grilla de destino
    // eliminando la fila de la grilla original
    //
    foreach (DataGridViewRow row in rowSelected)
    {
        dgvSeleccion.Rows.Add(new object[] {false, 
                                            row.Cells["Descripcion"].Value,
                                            row.Cells["PrecioUnitario"].Value,
                                            row.Cells["UnidadMedida"].Value});

        dgvProductos.Rows.Remove(row);
    }

}

Hay que remarcar también que el uso de la lista temporal no es un capricho, sino que es necesaria ya que mientras se recorre una lista por medio de la instrucción “for each”, no pueden removerse elementos. Por esta razón es que en una operación siguiente se recorre la selección y allí si se procede a incluye los ítems en al grilla de destino y quitarla de la original.

Este punto lo he explicado con mas detalle siguiente articulo:

Eliminar ítems en una lista

 

Selección de un ítem en la grilla de destino


Ahora le toca el turno a la operación contraria, y aquí hay un punto que ya se menciono en el paso previo, la lista de origen esta vinculada a datos de productos con lo cual usar el Rows.Add() no es posible.

Para esta operación se agregaran los datos directo en el origen, en este caso el datatable, que se ha creado dentro del DataSet tipado.

private void btnQuitar_Click(object sender, EventArgs e)
{
    //
    // Se define una lista temporal de registro seleccionados
    //
    List<DataGridViewRow> rowSelected = new List<DataGridViewRow>();

    //
    // Se recorre cada fila de la grilla de seleccion y se determian que registros estan checkeados
    //
    foreach (DataGridViewRow row in dgvSeleccion.Rows)
    {
        DataGridViewCheckBoxCell cellSelecion = row.Cells["SeleccionSel"] as DataGridViewCheckBoxCell;

        if (Convert.ToBoolean(cellSelecion.Value))
        {
            rowSelected.Add(row);
        }
    }

    //
    // Se valida si hay algun registro por eliminar
    //
    if (rowSelected.Count > 0)
    {
        //
        // Se recupera el origen de datos que tiene asignada la grilla de productos
        //
        dtoProductos datos = dgvProductos.DataSource as dtoProductos;

        //
        // Se recorre cada item seleccionado y se arma programaticamente la fila del DataTable
        // se elimina el registro de la grilla de selecciones
        //
        foreach (DataGridViewRow row in rowSelected)
        {

            dtoProductos.ProductosRow productoRow = datos.Productos.NewProductosRow();

            productoRow.Descripcion = Convert.ToString(row.Cells["DescripcionSel"].Value);
            productoRow.PrecioUnitario = Convert.ToDecimal(row.Cells["PrecioUnitarioSel"].Value);
            productoRow.UnidadMedida = Convert.ToString(row.Cells["UnidadMedidaSel"].Value);
            datos.Productos.Rows.Add(productoRow);

            dgvSeleccion.Rows.Remove(row);
        }

        //
        // Se binden los datos nuevamente, pero ahora con los nuevos registros
        // agregados del paso anterior
        //
        dgvProductos.AutoGenerateColumns = false;
        dgvProductos.DataSource = datos;
        dgvProductos.DataMember = "Productos";
    }
}

Es necesario analizar como toma los datos originales convirtiendo el contenido de DataSource de la grilla, en este caso a dtoProductos, ya que este es el tipo de datos utilizado en al carga original.

Se toman los datos originales, se procede a la creación de cada row de forma programática, asignando el valor a cada campo, por ultimo se vuelve a bindear la grilla.

 

[C#]
[VB.NET]

domingo, 18 de abril de 2010

C# – Descripción del Mes

 

Introducción

En esta ocasión se describirá las distintas técnicas que pueden aplicarse para obtener la descripción del mes.

Muchas veces sucede que dado una fecha, es necesario mostrar al usuario que mes se ha esta seleccionado, pero no alcanza con mostrar el numero que lo representa, sino que es necesario visualizar la descripción del mismo, y en el idioma del usuario.

Existen varias técnicas y formas de lograrlo, en este articulo se intentara abordad las alternativas que utilizo.

Obtener mes en una cultura especifica, usando método de extensión

El ejemplo de este punto se podrá visualizar en el código del Form1.

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

    private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
    {
        textBox1.Text = dateTimePicker1.Value.Month.MonthName().CapitalizeFirstLetter();
    }


}

public static class DateTimeHelper
{

    public static string MonthName(this int month)
    {
        DateTimeFormatInfo dtinfo = new CultureInfo("es-ES", false).DateTimeFormat;
        return dtinfo.GetMonthName(month);
    }

    public static string CapitalizeFirstLetter(this string value)
    {
        return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(value);
    }
}

En el código se puede observar la declaración de una clase estática, la cual contiene dos métodos:

- MonthName(), define un parámetro del tipo entero el cual representa el numero del mes, del cual se quiere obtener la descripción.

- CapitalizeFirstLetter(), en realidad este es solo un agregado, el cual hará que la descripción del mes tenga la primer letra en mayúsculas. Si la cultura usada es Ingles, esta de por si ya trae la primer letra en mayúscula, pero para idiomas Español, no es así, por eso hay que agrégasela.

En el evento ValueChange del DateTimePicker, se toma la fecha seleccionada y se recupera solo el mes en formato numéricos pero al definir un método de extensión para los enteros al inspeccionar los métodos habilitados estará el que hemos creado, lo interesante por remarcar es que los métodos de extensión permiten invocarlos directamente sin tener que pasar un valor por parámetro.

El único inconveniente de esta implementación es que la cultura, (o se el idioma) en el cual se recupera la descripción del mes, esta fija en el método.

Obtener mes en una cultura especifica, usando método simple

En el Form2 se podrá ver la misma implementación del punto anterior, pero en esta oportunidad se hace uso de método simples.

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

    private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
    {
        textBox1.Text = CapitalizeFirstLetter(MonthName(dateTimePicker1.Value.Month));
    }


    public string MonthName(int month)
    {
        DateTimeFormatInfo dtinfo = new CultureInfo("es-ES", false).DateTimeFormat;
        return dtinfo.GetMonthName(month);
    }

    public string CapitalizeFirstLetter(string value)
    {
        return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(value);
    }

}

En este caso cada método recibe por parámetro el valor que necesita para poder trabajar.

Obtener mes seleccionando la cultura y usando método de extensión

En esta otra implementación iremos un poco mas allá, al permitir que el idioma del mes se obtenga en base a la cultura seleccionada, o la que este definida en la aplicación.

El ejemplo se dividió en dos formulario: Form3 y Form4, ya que era preciso que en la inicialización se pudiera cambiar el Thread.CurrentThread.CurrentCulture, lo cual no se podía lograr si estamos situados en el mismo form.

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

    public Form4(CultureInfo culture) : this()
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;
    }

    private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
    {
        textBox1.Text = MonthNameCurrentCulture(dateTimePicker1.Value.Month);
        
    }


    public string MonthNameCurrentCulture(int month)
    {
        DateTimeFormatInfo dtinfo = Thread.CurrentThread.CurrentCulture.DateTimeFormat;
        return dtinfo.GetMonthName(month);
    }
}

En el Form4 se recibe la cultura por parámetro, ya que es preciso que sea en la inicialización del form donde se especifique, es por ello que se ha creado un constructor nuevo en el Form4.

Lo destacable es que ahora el método MonthNameCurrentCulture() usa la cultura de la aplicación para obtener la descripción del mes, ya no esta fija como en los ejemplos anteriores.

Obtener mes mediante el formato de la fecha

Una otra alternativa valida, se podría usar el formato que se puede aplicar a un tipo DateTime, cuando se lo convierte a string.

En el Form6 justamente se hace uso de esta técnica, aplicando el formato MMMM, el cual devuelve la descripción del mes, para la fecha seleccionada.

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

    private void dateTimePicker1_ValueChanged(object sender, EventArgs e)
    {
        if(rbespanol.Checked)
            textBox1.Text = dateTimePicker1.Value.ToString("MMMM", CultureInfo.CreateSpecificCulture("es-ES")); 
        else if(rbingles.Checked)
            textBox1.Text = dateTimePicker1.Value.ToString("MMMM", CultureInfo.CreateSpecificCulture("en-US")); 
    }

}

Aclaraciones generales

Hay que recordar el agregado del using de System.Globalization, para que reconozca los métodos.

 

[C#]
[VB.NET]

jueves, 15 de abril de 2010

C# - [DataGridView] – Uso del CheckBox - DataGridViewCheckBoxColumn

 

Introducción

El articulo intentara mostrar las algunas forma en que se puede utilizar el checkbox en un control DataGridView.

Detectar el cambio en la selección

Para realizar la tarea se hará uso de dos eventos que pueden ser igual de útiles, el CellValueChange y el CellContentClick

De forma estándar el CellValueChange, se deparará a seleccionar el check de la celda y quitar el foco de la misma, saliendo del modo de edición.

Pero este no ejecuta la acción en el mismo instante en que se origina, sino que hay que salir de la edición de la celda para que el evento ocurra.

private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
	//
	// Solo se trabaja ante los cambios en la columan de los checkbox 
	//
	if (dataGridView1.Columns[e.ColumnIndex].Name == "Seleccion") {
		//
		// Se toma la fila seleccionada
		//
		DataGridViewRow row = dataGridView1.Rows[e.RowIndex];

		//
		// Se selecciona la celda del checkbox
		//
		DataGridViewCheckBoxCell cellSelecion = row.Cells["Seleccion"] as DataGridViewCheckBoxCell;
              
		//
		// Se valida si esta checkeada
		//
		if (Convert.ToBoolean(cellSelecion.Value)) {

                  string mensaje = string.Format("Evento CellValueChanged.\n\nSe ha seccionado, \nDescripcion: '{0}', \nPrecio Unitario: '{1}', \nMedida: '{2}'", 
                                                      row.Cells["Descripcion"].Value, 
                                                      row.Cells["PrecioUnitario"].Value, 
                                                      row.Cells["UnidadMedida"].Value);

			MessageBox.Show(mensaje, "", MessageBoxButtons.OK, MessageBoxIcon.Information);

		}

	}

}

Con respecto al CellContentClick, que si ejecuta la acción en el mismo momento en que el usuario marca, o desmarca, el checkbox, pero me encontré que solo devolvía null en la propiedad Value de la celda.

private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    //
    // Detecta si se ha seleccionado el header de la grilla
    //
    if (e.RowIndex == -1)
        return;

    if (dataGridView1.Columns[e.ColumnIndex].Name == "Seleccion")
    {

        //
        // Se toma la fila seleccionada
        //
        DataGridViewRow row = dataGridView1.Rows[e.RowIndex];

        //
        // Se selecciona la celda del checkbox
        //
        DataGridViewCheckBoxCell cellSelecion = row.Cells["Seleccion"] as DataGridViewCheckBoxCell;


        if (Convert.ToBoolean(cellSelecion.Value))
        {

            string mensaje = string.Format("Evento CellContentClick.\n\nSe ha seccionado, \nDescripcion: '{0}', \nPrecio Unitario: '{1}', \nMedida: '{2}'",
                                                row.Cells["Descripcion"].Value,
                                                row.Cells["PrecioUnitario"].Value,
                                                row.Cells["UnidadMedida"].Value);

            MessageBox.Show(mensaje, "", MessageBoxButtons.OK, MessageBoxIcon.Information);

        }
        else
        {
            string mensaje = string.Format("Evento CellContentClick.\n\nSe ha quitado la seleccion, \nDescripcion: '{0}', \nPrecio Unitario: '{1}', \nMedida: '{2}'",
                                                row.Cells["Descripcion"].Value,
                                                row.Cells["PrecioUnitario"].Value,
                                                row.Cells["UnidadMedida"].Value);

            MessageBox.Show(mensaje, "", MessageBoxButtons.OK, MessageBoxIcon.Information);

        }
    }
}

También se ha intentado hacer uso del evento EditingControlShowing, el cual permitiría detectar la selección en el mismo momento que el usuario realiza la acción, pero esto no fue posible ya que este tipo de columna no provoca el evento necesario para tomar el control CheckBox y asignar el evento SelectedIndexChanged.

Ante este problema con los eventos, y notar que ninguno responde como debería, se encontró un método que resolvió todos los problemas de un solo golpe, se trata del CommitEdit

private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (dataGridView1.IsCurrentCellDirty)
    {
        dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
    }
}

En el evento CurrentCellDirtyStateChanged, se detecta si la grilla esta con algún cambio pendiente, y en caso de estarlo se hace un Commit del mismo para reflejar el valor en los eventos que lo usaran. Esto arregla los dos problemas detectados anteriormente:

- se lanza el evento CellValueChanged, sin tener que quitar el foco de la celda

- ya no se recibe el null en el Value de la celda, en el evento CellContentClick

[C#]
[VB.NET]

Aplicar formato a la selección

Teniendo en cuenta lo contado en la sección anterior, aplicar un formato a la fila seleccionada no debería ser un misterio.

private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
    //
    // Detecta si se ha seleccionado el header de la grilla
    //
    if (e.RowIndex == -1)
        return;

    if (dataGridView1.Columns[e.ColumnIndex].Name == "Seleccion")
    {

        //
        // Se toma la fila seleccionada
        //
        DataGridViewRow row = dataGridView1.Rows[e.RowIndex];

        //
        // Se selecciona la celda del checkbox
        //
        DataGridViewCheckBoxCell cellSelecion = row.Cells["Seleccion"] as DataGridViewCheckBoxCell;

        if (Convert.ToBoolean(cellSelecion.Value))
            row.DefaultCellStyle.BackColor = Color.Green;    
        else
            row.DefaultCellStyle.BackColor = Color.White; 

    }
}

private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
    if (dataGridView1.IsCurrentCellDirty)
    {
        dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
    }
}

Simplemente ante la detección de la selección del usuario se cambia el color de la fila usando el DefaultCellStyle.BackColor.

[C#]
[VB.NET]

lunes, 12 de abril de 2010

C# - [ASP.NET] - JavaScript alert() y confirm() - Como usar los mensajes

 

Introducción

En este articulo analizaremos algunas alternativas utilizadas para trabajar con alertas o pedir confirmación, mediante cuadros de mensaje en un proyecto web.

El uso de javascript en un desarrollo web con asp.net puede no ser tan simple como uno imagina, pues este código es ejecutado en el cliente, mientras que código .net se encuentra en el servidor.

Esta distinción de ambientes donde es ejecutado cada lenguaje puede traer problemas al momento de integrarlo, mas que al hacer uso de controles de brindados en asp.net que son claramente diseñador para trabajar por medio de requerimiento al servidor.

Uso del alert()

El uso del alert() de javascript es muy útil para informar al usuario de un suceso en la aplicación, pero este puede ser usado antes o después del postback o acción del usuario, lo que requiere distintas técnicas en cada caso.

Para demostrar estos casos en el formulario de nombre WebForm1.aspx trataremos el uso de las alertas.

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
    <script type="text/javascript">
    
        function alerta(numero) {
            alert('Se ha presionado el boton: ' + numero);
        }
    
    </script>
    
</head>
<body>

    <form id="form1" runat="server">
        <table>
            <tr>
                <td>
                    <input id="btnMensaje1" type="button" value="Mensaje 1" onclick="alert('Se ha presionado el boton: 1');" />
                </td>
                <td>
                    <asp:Button ID="btnMensaje2" runat="server" Text="Mensaje 2" OnClientClick="alert('Se ha presionado el boton: 2');"  />
                </td>
                <td>
                    <asp:Button ID="btnMensaje3" runat="server" Text="Mensaje 3" OnClientClick="alert('Se ha presionado el boton: 3'); return false;"  />
                </td>
                <td>
                    <asp:Button ID="btnMensaje4" runat="server" Text="Mensaje 4" />
                </td>
             </tr>
             <tr>
                <td>
                    <asp:Button ID="btnMensaje5" runat="server" Text="Mensaje 5" 
                        onclick="btnMensaje5_Click" />
                </td>
                <td>
                    <asp:Button ID="btnMensaje6" runat="server" Text="Mensaje 6" 
                        onclick="btnMensaje6_Click" />
                </td>
                <td>
                    <asp:Button ID="btnMensaje7" runat="server" Text="Mensaje 7" 
                        onclick="btnMensaje7_Click" />
                </td>
                <td>
                    <asp:Button ID="btnMensaje8" runat="server" Text="Mensaje 8" 
                        onclick="btnMensaje8_Click"  />
                </td>
            </tr>
        </table>  
       
    </form>
</body>
</html>

public partial class WebForm1 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        btnMensaje4.Attributes["onclick"] = "alert('Se ha presionado el boton: 4'); return false;";  
    }

    protected void btnMensaje5_Click(object sender, EventArgs e)
    {
        string script = @"<script type='text/javascript'>
                            alerta(5);
                        </script>";

        if(!Page.IsClientScriptBlockRegistered("alerta"))
            Page.RegisterStartupScript("alerta", script);   
    }

    protected void btnMensaje6_Click(object sender, EventArgs e)
    {
        string script = @"<script type='text/javascript'>
                            alerta(6);
                        </script>";

        ScriptManager.RegisterStartupScript(this, typeof(Page), "alerta", script, false) ;
    }

    protected void btnMensaje7_Click(object sender, EventArgs e)
    {
        string script = @"<script type='text/javascript'>
                            alert('Se ha presionado el boton: 7');
                        </script>";

        ScriptManager.RegisterStartupScript(this, typeof(Page), "alerta", script, false);

    }

    protected void btnMensaje8_Click(object sender, EventArgs e)
    {
        try
        {
            throw new Exception("Se ha producido un error no controlado."); 
        }
        catch (Exception ex)
        {
            string script = @"<script type='text/javascript'>
                            alert('{0}');
                        </script>";

            script = string.Format(script, ex.Message);

            ScriptManager.RegisterStartupScript(this, typeof(Page), "alerta", script, false);

        }
    }
}

El código refleja distintas alternativas para trabajar con alertas, desde algunas muy simple al otras mas complejas.

btnMensaje1: Este simplemente es un botón simple de html, se hace uso del evento onclick para desplegar el alert

btnMensaje2: Este es el primer ejemplo con un botón de asp.net, vemos como el evento onclick aquí no esta disponible, se ha reemplazado por su equivalente OnClientClick. Un detalle de este método es que luego de despegar el mensaje el botón realiza el postaback de la pagina invocando los eventos del mismo.

btnMensaje3: A diferencia del anterior este introduce un pequeño truco, el agregado del “return false;”, ante un evento javascript que devuelva false, el postback del control al servidor es impedido

btnMensaje4: Este no define el evento en el html del control, sino que es en el evento Form_Load que se asigna haciendo uso de la colección Attributes. en realidad es otra forma valida de lograr lo mismo.

Suele suceder que a veces se necesita crear script con cierto dinamismo que solo se logra con código .net, de esta forma ese código javascript creado puede asignarse a un evento en un control para ser ejecutado.

btnMensaje5: En este caso se cambia un poco el concepto del ejemplo ya que aquí se muestra le mensaje a la vuelva del postback del evento del botón. Además se esta invocando a un método predefinido en el tag <script ..> del html

Esta técnica es muy útil cuando se quiere desplegar un mensaje luego de una operación realizada en el servidor.

Este hace uso de los métodos Page.IsClientScriptBlockRegistered() y Page.RegisterStartupScript(), paro debe aclararse que estos en la documentación del MSDN esta marcados como obsoletos, ya que hay nueva funcionalidad que los reemplazan.

btnMensaje6: Justamente en la implementación de este botón es que se hace uso de la forma actualizada en que se inyecta código javascript en una pagina.

Este hace uso del método ScriptManager.RegisterStartupScript y es este el que reemplaza a los método del ejemplo del btnMensaje5

btnMensaje7: Este demuestra que no siempre es necesario invocar a una funciona javascript en la pagina para mostrar una alerta, se puede generar código directo que muestre el mensaje.

btnMensaje8: Releja una alternativa muy interesante para visualizar errores que se producen en el servidor durante el procesamiento del postback del evento del botón.

En este caso se lanza un error a propósito que será capturado por el bloque try..catch, dentro de este se aplica las misma técnica descripta para en evento anteriores, solo que el mensaje a mostrar el usuario será el error que se atrapo en el servidor.

 

Uso del confirm()

Este aplica muchas de las técnicas ya reflejadas en el uso del alert(), pero hay ciertos detalles que es bueno remarcar.

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>

    <script type="text/javascript">

        function Confirmacion() {

            var seleccion = confirm("acepta el mensaje ?");

            if (seleccion)
                alert("se acepto el mensaje");
            else
                alert("NO se acepto el mensaje");

            //usado para que no haga postback el boton de asp.net cuando 
            //no se acepte el confirm
            return seleccion;
        
        }
    
    </script>

</head>
<body>
    <form id="form1" runat="server">

        <table>
            <tr>
                <td>
                    <asp:Button ID="btnConfirmacion1" runat="server" Text="Confirmacion 1" OnClientClick="return Confirmacion();"/>
                </td>
                <td>
                    <asp:Button ID="btnConfirmacion2" runat="server" Text="Confirmacion 2"  />
                </td>
                <td>
                    <asp:Button ID="btnConfirmacion3" runat="server" Text="Confirmacion 3" 
                            OnClientClick="return confirm('hacer el postback del control ?');"   />
                </td>
            </tr>
        </table>

    </form>
</body>
</html>

 

public partial class WebForm2 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        btnConfirmacion2.Attributes["onclick"] = "return Confirmacion();"; 
    }
}

btnConfirmacion1: Hace uso de la propiedad OnClientClick para definir el método javascript definido en la pagina.

Un punto importante es el uso de return como antecesor el nombre de la función

OnClientClick="return Confirmacion();"

El objetivo de este es evitar el postback del botón cuando no se acepte el mensaje de confirmación. Si este return es quitado se realiza la invocación al servidor se acepte o no el mensaje de confirmación.

btnConfirmacion2: Aplica la misma técnica del ejemplo anterior, solo que la asignación del evento es realizada desde el servidor.

btnConfirmacion3: Hace uso de código javascript en línea directo en el evento, sin utilizar una función definida para tratar la confirmación.

Es importante notar el uso del return delante el mensaje de confirm(), este tomara el respuesta y asignara true o false, permitiendo o no hace el postback del botón.

 

[C#]
[VB.NET]