domingo, 11 de agosto de 2013

AutoComplete TextBox usando Listas Genericas Parte 2

Este ejemplo es la continuación de  AutoComplete TextBox usando Listas Genericas en donde se mostro la forma de usar el AutoCompleteStringCollection de un TextBox usando listas genéricas de propiedades.

En este articulo pretendo explicarles y compartirles la manera de recuperar valores mostrados en un control Textbox con AutoCompleteCustomSource establecida a una fuente de datos, seleccionando el valor en la lista desplegada en el TextBox y mandando datos adicionales a controles del form llamador.

3

El ejemplo es aplicable para aquellos casos en los que no se conocen los parámetros de búsqueda de ciertos registros, por ejemplo, en un punto de venta muchas ocasiones algunos artículos no cuentan con el código de barras o UPC como lo quieran llamar, y no nos sabemos el Id del articulo; en estos casos se requiere de una forma de búsqueda por Descripción, es ahí donde entra el AutoComplete del TextBox ya que podremos buscar el articulo por su nombre y devolver el código de barras del articulo al formulario de ventas para así proceder a la búsqueda de lo demás datos.

En este ejemplo se muestra la manera de lograr esto usando listas genéricas.

Para lograr la comunicación usaremos una interfaz:

   1: namespace AutoCompleteUsingListofT
   2: {
   3:     public interface IMunicipio
   4:     {
   5:  
   6:         void GetValue(int numero, string nombre);
   7:  
   8:     }
   9: }

Para mas información:


Interfaces (Guía de programación de C#)


Para cargar los datos usaremos una clase de Propiedades:



   1: namespace AutoCompleteUsingListofT
   2: {
   3:     public class DataProperties
   4:     {
   5:         public int Numero { get; set; }
   6:         public string Nombre { get; set; }
   7:     }
   8: }

En hacemos que el Form1 herede de la Interfaz para poder recibir los datos:



   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace AutoCompleteUsingListofT
   5: {
   6:     //
   7:     //Heredamos de la interfaz IMunicipio
   8:     //
   9:     public partial class Form1 : Form, IMunicipio
  10:     {
  11:         public Form1()
  12:         {
  13:             InitializeComponent();
  14:         }
  15:  
  16:         //
  17:         //Usamos el metodo contenido dentro de la Interfaz
  18:         //
  19:         public void GetValue(int numero, string nombre)
  20:         {
  21:             //
  22:             //Recuperamos los valores
  23:             //
  24:             textBox1.Text = Convert.ToString(numero);
  25:             textBox2.Text = nombre;
  26:         }
  27:  
  28:         private void button1_Click(object sender, EventArgs e)
  29:         {
  30:             SearchForm frm = new SearchForm(this) {MdiParent = this.MdiParent};
  31:             frm.Show();
  32:         }
  33:     }
  34: }

Creamos una función encargada de llenar una Lista genérica:



   1: /// <summary>
   2: ///  Obtiene informacion de la tabla especificada y devuelve una lista generica de propiedades
   3: /// con los datos recuperados
   4: /// </summary>
   5: /// <autor> Jose Luis Garcia Bautista </autor>
   6: /// <returns>Lista Generica del tipo DataProperties</returns>
   7: public static List<DataProperties> CargarDatos()
   8: {
   9:     //
  10:     // Declaramos una variable del tipo List<Of DataProperties> para cargar los datos recuperados de la BD
  11:     List<DataProperties> lista = new List<DataProperties>();
  12:     //
  13:     //Abrimos la conexión a nuestra Bd usando el archivo de configuracion App.Config
  14:     using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["GeografiaConnectionString"].ToString()))
  15:     {
  16:         //
  17:         //Abrimos la conexión a la Bb
  18:         cnx.Open();
  19:         //
  20:         //Creamos una variable constante para establecer la instruccion SQL
  21:         const string sql = "SELECT * FROM Municipio ORDER BY Nombre ASC";
  22:         //
  23:         //Creamos el objeto Command propio del motor de BD que estemos usando en este caso SqlCeCommand
  24:         SqlCeCommand cmd = new SqlCeCommand(sql, cnx);
  25:         //
  26:         //Creamos el objeto DataReader que se encargara de proporcionarnos las filas de datos obtenidas
  27:         SqlCeDataReader dr = cmd.ExecuteReader();
  28:         //
  29:         // Preguntamos si el objeto DataReader correspondiente contiene datos
  30:         while (dr.Read())
  31:         {
  32:             //
  33:             //Instanciamos la clase contenedora de las propiedades para mepear los datos recuperados
  34:             //de la BD
  35:             DataProperties item = new DataProperties
  36:             {
  37:                 //
  38:                 //Convertimos al tipo de dato correcto
  39:                 Numero = Convert.ToInt32(dr["Numero"]),
  40:                 Nombre = Convert.ToString(dr["Nombre"]),
  41:             };
  42:             //
  43:             //Agregamos el nuevo item a la lista
  44:             lista.Add(item);
  45:         }
  46:  
  47:     }
  48:     //
  49:     //retornamos la lista 
  50:     return lista;
  51: }

Llenamos el AutoCompleteStringCollection con los datos de la lista recuperados por la función CargarDatos():



   1: /// <summary>
   2: /// Carga los item de una lista generica al objeto AutoCompleteStringCollection usado por algunos controles para
   3: /// cumplir con la funcion AutoComplete
   4: /// </summary>
   5: /// <autor> Jose Luis Garcia Bautista </autor>
   6: /// <returns>Regresa AutoCompleteStringCollection</returns>
   7: public static AutoCompleteStringCollection LoadAutocomplete()
   8: {
   9:     //
  10:     //Instanciamos el objeto que se usara para el AutoComplete
  11:     //
  12:     AutoCompleteStringCollection stringdata = new AutoCompleteStringCollection();
  13:     //
  14:     // llamamos a la funcion cargar datos para llenar la lista
  15:     //
  16:     _lista = CargarDatos();
  17:  
  18:     //
  19:     //Ejecutamos un ciclo foreach que recorrera item por item de la lista devuelta por CargarDatos()
  20:     //
  21:     foreach (DataProperties item in _lista)
  22:     {
  23:         //
  24:         //Por cada item encontrado obtenemos el valor de la propiedad Nombre y lo asignamos
  25:         //a AutoCompleteStringCollection
  26:         //
  27:         stringdata.Add(Convert.ToString(item.Nombre));
  28:     }
  29:     //
  30:     //Devolvemos el AutoCompleteStringCollection con los datos cargados
  31:     //
  32:     return stringdata;
  33:  
  34: }

En el Load del form es donde mandaremos a llamar a las funciones anteriores:



   1: /// <summary>
   2: /// Se encarga de cargar el control TextBox con los datos de la lista
   3: /// </summary>
   4: /// <autor> Jose Luis Garcia Bautista </autor>
   5: /// <param name="sender"></param>
   6: /// <param name="e"></param>
   7: private void Form1_Load(object sender, EventArgs e)
   8: {
   9:     try
  10:     {
  11:         //
  12:         //Establecemos las propiedades del control TextBox
  13:         //
  14:         textBox1.AutoCompleteCustomSource = LoadAutocomplete();
  15:         //
  16:         //Para esta propiedad tenemos diferentes opciones (None, Suggest, Append, SuggestAppend)
  17:         // 
  18:         //
  19:         textBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
  20:  
  21:         textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
  22:     }
  23:     catch (Exception er)
  24:     {
  25:         //En caso de ocurrir alguna Excepcion atrapamos el mensaje y mostramos al usuario
  26:         MessageBox.Show(string.Format("Error : {0}", er.Message), "Error inesperado", MessageBoxButtons.OK,
  27:                         MessageBoxIcon.Error);
  28:     }
  29: }

Ahora para poder enviar el valor perteneciente a la selección del TextBox usamos el evento TextChanged y usaremos una expresión Lambda para poder obtener los valores de la lista de propiedades:



   1: private void textBox1_TextChanged(object sender, EventArgs e)
   2: {
   3:     DataProperties item = _lista.FirstOrDefault(x => x.Nombre == textBox1.Text);
   4:  
   5:     if (item == null)
   6:         return;
   7:  
   8:     if (Caller != null) Caller.GetValue(item.Numero, item.Nombre);
   9: }


Observen, como seleccionando un item de la lista se envía automáticamente el valor de Numero y Nombre a formulario llamador:


4


Saludos desde Monterrey, Nuevo León México!


Aquí les dejo el proyecto de ejemplo desarrollado en C# y la versión en Vb.Net (próximamente).


Ejemplo en C#


Ejemplo en Vb.Net


Nota: El proyecto fue desarrollado en Vs2010 usando una Bd SqlCompact V3.5 y Framework 4.0

lunes, 29 de julio de 2013

Restricción de Usuarios

A petición de un Usuario del Foro de C# les prepare un pequeño ejemplo sobre la restricción de usuarios en un sistema informático, en donde se valida la presencia del Usuario y la concordancia del Password proporcionado con los datos almacenados en la tabla Usuarios.

Para este ejercicio se usa estos datos contenidos en la Bd que esta adjunto al proyecto:
Usuarios

Tabla de configuración del Nivel de Acceso:
NivelAcceso

Teniendo en cuenta la información anterior procedamos a ver el código fuente del ejemplo:
Un de los aspectos mas importantes para poder utilizar el Login esta en el Main de la clase Program que es donde configuraremos que Formulario que deberá de arrancar como inicio, es decir, el formulario que hará la función de Logeo a la Bd.
   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace RestriccionDeUsuariosCSharp
   5: {
   6:     static class Program
   7:     {
   8:         /// <summary>
   9:         /// Punto de entrada principal para la aplicación.
  10:         /// </summary>
  11:         [STAThread]
  12:         static void Main()
  13:         {
  14:             //Application.EnableVisualStyles();
  15:             //Application.SetCompatibleTextRenderingDefault(false);
  16:             //Application.Run(new Form1());
  17:  
  18:             Application.EnableVisualStyles();
  19:             Application.SetCompatibleTextRenderingDefault(false);
  20:  
  21:             Login frm = new Login();
  22:             frm.ShowDialog();
  23:  
  24:             if (frm.DialogResult == DialogResult.OK)
  25:                 Application.Run(new Form1());
  26:         }
  27:     }
  28: }

El Formulario Login será el encargado de realizar la autenticación a la Bd:
Login

Dentro procedemos a crear las Funcion autenticar:

   1: /// <summary>
   2: /// Funcion encargada de validar la existencia del Usuario y Password
   3: /// </summary>
   4: /// <Autor>Jose Luis Garcia Bautista</Autor>
   5: /// <param name="usuario"> Nombre del usuario en la BD</param>
   6: /// <param name="password"> Password perteneciente al Nombre de usuario proporcionado</param>
   7: /// <returns>True si ambos datos conincide con algun registro de la Bd</returns>
   8: public static bool Autenticar(string usuario, string password)
   9: {
  10:     //Utilizamos el Bloque Using...end Using para administrar la conexion
  11:     //este se encarga de cerrar la conexion y destruir todos los objetos encerrados en el
  12:     using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["UserConnectionString"].ToString()))
  13:     {
  14:         //
  15:         //Abrimos la conexion a la Bd
  16:         //
  17:         cnx.Open();
  18:         //
  19:         //Creamos la Consulta SQl utilizando parametros, en donde unicamente recuperaremos el NivelDeAcceso
  20:         //con el que cuenta el Usuario siempre cuando Exista y se hayan proporcionado las credenciales validas
  21:         //
  22:         const string sqlAction = "SELECT NivelAcceso FROM Usuarios WHERE Usuario = @username AND Password = @pass";
  23:         //
  24:         //Creamos el objento Command propio del motor de la Bd que estamos usando
  25:         //
  26:         SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx);
  27:         //
  28:         //Asignamos valores a la coleccion de parametros
  29:         //
  30:         cmd.Parameters.AddWithValue("@username", usuario);
  31:         cmd.Parameters.AddWithValue("@pass", password);
  32:         //
  33:         //Recuperamos la cantidad de registros coincidentes con el Usuario y Password por logica
  34:         //solo podremos recuperar un solo registro
  35:         //
  36:         int tipoUsuario = Convert.ToInt32(cmd.ExecuteScalar());
  37:         //
  38:         //Si el tipodeusuario es Mayor a cero significa que la Validacion fue cumplida y el usuario
  39:         //tiene un nivel de acceso
  40:         //
  41:         if (tipoUsuario > 0)
  42:         {
  43:             //
  44:             //Enviamos el Nivel de acceso a la propiedad NivelAcceso de la Clase Usuarios
  45:             //
  46:             Usuario.NivelAcceso = tipoUsuario;
  47:             //
  48:             //retornamos True ya que la validacion a sido Exitosa
  49:             //
  50:             return true;
  51:         }
  52:     }
  53:     //
  54:     //Si la validacion no fue cumplica retornamos False para evitar continuar con el proceso
  55:     //
  56:     return false;
  57: }

Utilizamos la función Autenticar:

   1: private void btnaceptar_Click(object sender, EventArgs e)
   2: {
   3:     try
   4:     {
   5:  
   6:         //Mandamos a llamar a la funcion Autenticar mandandole los parametros requeridos
   7:         //Autenticar(ByVal usuario As String, ByVal password As String)
   8:  
   9:         {
  10:             if (Autenticar(txtusuario.Text.Trim(), txtpassword.Text.Trim()))
  11:             {
  12:                 //Si el resultado de la funcion es True entonces enviamos comor resultado un Ok
  13:                 DialogResult = DialogResult.OK;
  14:             }
  15:             else
  16:             {
  17:                 //Si la funcion autenticar envio un False entonces mostramos el mensaje y mandamos un Abort
  18:                 MessageBox.Show("Usuario o Password no validos");
  19:                 DialogResult = DialogResult.Abort;
  20:             }
  21:         }
  22:     }
  23:     catch (Exception ex)
  24:     {
  25:         MessageBox.Show(string.Format("Error : {0}", ex.Message), "Error inesperado", MessageBoxButtons.OK, MessageBoxIcon.Error);
  26:     }
  27: }

Dentro del Form1 procedemos a validar el Nivel de acceso del usuario para habilitar e inhabilitar las opciones del Menú:

   1: using System;
   2: using System.Windows.Forms;
   3:  
   4: namespace RestriccionDeUsuariosCSharp
   5: {
   6:     public partial class Form1 : Form
   7:     {
   8:         public Form1()
   9:         {
  10:             InitializeComponent();
  11:         }
  12:  
  13:         private void Form1_Load(object sender, EventArgs e)
  14:         {
  15:             //Preguntamos por el contenido de la variable publica
  16:             //NivelAcceso contenido en la clase Usuario
  17:  
  18:             {
  19:                 switch (Usuario.NivelAcceso)
  20:                 {
  21:                         //
  22:                         //Opciones validar para un Administrador
  23:                         //
  24:                     case 1:
  25:                         //Configuramos las opciones
  26:                         MenuInvitado.Enabled = false;
  27:                         MenuMiembro.Enabled = false;
  28:                         break;
  29:  
  30:                         //
  31:                         //Opciones validas para un Miembro
  32:                         //
  33:                     case 2:
  34:                         MenuAdmisnitrador.Enabled = false;
  35:                         MenuMiembro.Enabled = false;
  36:                         break;
  37:  
  38:                         //
  39:                         //Opciones validas para un Invitado
  40:                         //
  41:                     case 3:
  42:                         MenuInvitado.Enabled = false;
  43:                         MenuAdmisnitrador.Enabled = false;
  44:                         break;
  45:                 }
  46:             }
  47:         }
  48:     }
  49: }

Si el proceso de Login fue cubierto satisfactoriamente podremos ver el formulario Form1 con las restricciones configuradas:

Menu

El ejemplo proporcionado cuenta con muchas opciones de mejora como.
  • No utilizar una propiedad publica para compartir del Nivel de acceso de Usuario y en cambio usar el Patron Singleton para compartir información en todo el proyecto sin el riesgo de que este sea cambiado por una instancia diferente, es decir, únicamente se instancia una sola vez y nada mas. Para mas información del Patron Singleton: El Patrón Singleton

  • El uso de un Algoritmo de encriptación como Hash para poder encriptar la información de Password dando con esto una mayor seguridad a la aplicación, para mayor informacion: Desmitificando la Encriptación (Parte II)
Esas son las dos principales carencias del Ejercicio, debido a que esta primera entrega únicamente tiene como objetivo orientar sobre el proceso de Login pero, en una entrega posterior se agregaran estas dos funcionalidades.

Nota: El proyecto fue desarrollado en Vs2010 usando una Bd SqlCompact V3.5 y Framework 4.0