Blog alskare

.Net y lo que surja

Archivos de la categoría ‘C#’

Calculo de Semana Santa con C#

Publicado por alskare en 05/04/2010

Hacía ya bastante tiempo que no tocaba el blog, así que ya va siendo hora de añadir algún que otro post. A ver si tengo tiempo estos días para poder hacer un par de entradas que tengo en mente, aunque el quehacer diario nos impide dedicarnos todo lo que queremos.

En esta ocasión le toca el turno a unos cálculos que he tenido que realizar para poder calcular ciertos días de la Semana Santa. Para documentarme, debo reconocer que tan sólo he echado mano de nuestra querida wikipedia (http://es.wikipedia.org/wiki/C%C3%A1lculo_de_la_fecha_de_Pascua#C.C3.A1lculo), así que ha sido bastante sencillo dicho cálculo. Como siempre, intento explicar un poco los pasos seguidos para lograr el fin.

A primera vista, los cálculos se ven sencillos, puesto que se trata de conseguir los residuos de una serie de divisiones. Quizás el único hándicap puede venir representado por la tabla de constantes que se muestra en la página de la Wikipedia. ¿Cómo lo solvento?. Pues, como siempre intento hacer, sin complicarme la vida, es decir, una estructura dentro de la clase que me permita almacenar los valores y un método que los cargue:

#region Constantes cálculo
private struct ParConstantes
{
    public int M { get; set; }
    public int N { get; set; }
}

private ParConstantes getPar(int anio)
{
    ParConstantes p = new ParConstantes();
    if (anio < 1583) { throw 
            new ArgumentOutOfRangeException("El año deberá ser superior a 1583"); }
    else if (anio < 1700) { p.M = 22; p.N = 2; }
    else if (anio < 1800) { p.M = 23; p.N = 3; }
    else if (anio < 1900) { p.M = 23; p.N = 4; }
    else if (anio < 2100) { p.M = 24; p.N = 5; }
    else if (anio < 2200) { p.M = 24; p.N = 6; }
    else if (anio < 2299) { p.M = 25; p.N = 0; }
    else { throw 
        new ArgumentOutOfRangeException("El año deberá ser inferior a 2299"); }
    return p;
}
#endregion

 

Así, una vez está solventado el problema de la tabla de las constantes, lo único que queda es el tema de los cálculos. En esta ocasión, dejo la clase completa, puesto que no tiene mayor complicación.

using System;


namespace jnSoftware.Calculos
{

    /// <summary>
    /// Cálculo de Semana Santa
    /// </summary>
    class SemanaSanta
    {
        private int a;
        private int b;
        private int c;
        private int d;
        private int e;
        private DateTime pascuaResurreccion;

        private int anio;


        /// <summary>
        /// Constructor de la clase
        /// </summary>
        /// <param name="anio">Entero que representa el año del que se quiere calcular la 
        /// semana santa.</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce cuando se intenta calcular
        /// la semana santa de un año no contemplado.</exception>
        public SemanaSanta(int anio)
        {
            try
            {
                this.anio = anio;
                calculaDomingoPascua();
            }
            catch { throw; }
        }


        /// <summary>
        /// Cálculo del domingo de Pascua o domingo de Resurrección.
        /// </summary>
        private void calculaDomingoPascua()
        {
            ParConstantes p = getPar(anio);
            a = anio % 19;
            b = anio % 4;
            c = anio % 7;
            d = (19 * a + p.M) % 30;
            e = (2 * b + 4 * c + 6 * d + p.N) % 7;

            if (d + e < 10)
            {
                pascuaResurreccion = new DateTime(anio, 3, d + e + 22);
            }
            else
            {
                pascuaResurreccion = new DateTime(anio, 4, d + e - 9);
            }

            // Excepciones
            if (pascuaResurreccion == new DateTime(anio, 4, 26))
                pascuaResurreccion = new DateTime(anio, 4, 19);

            if (pascuaResurreccion == new DateTime(anio, 4, 25) && d == 28 && e == 6 && a > 10)
                pascuaResurreccion = new DateTime(anio, 4, 18);
        }


        #region Constantes cálculo
        private struct ParConstantes
        {
            public int M { get; set; }
            public int N { get; set; }
        }

        private ParConstantes getPar(int anio)
        {
            ParConstantes p = new ParConstantes();
            if (anio < 1583) { throw 
                    new ArgumentOutOfRangeException("El año deberá ser superior a 1583"); }
            else if (anio < 1700) { p.M = 22; p.N = 2; }
            else if (anio < 1800) { p.M = 23; p.N = 3; }
            else if (anio < 1900) { p.M = 23; p.N = 4; }
            else if (anio < 2100) { p.M = 24; p.N = 5; }
            else if (anio < 2200) { p.M = 24; p.N = 6; }
            else if (anio < 2299) { p.M = 25; p.N = 0; }
            else { throw 
                new ArgumentOutOfRangeException("El año deberá ser inferior a 2299"); }
            return p;
        }
        #endregion


        #region Propiedades públicas

        public DateTime MiercolesCeniza
        {
            get { return SabadoSanto.AddDays(7 * -6 - 3 ); }
        }
        
        public DateTime ViernesDolores
        {
            get { return pascuaResurreccion.AddDays(-9); }
        }

        public DateTime DomingoRamos
        {
            get { return pascuaResurreccion.AddDays(-7); }
        }

        public DateTime JuevesSanto
        {
            get { return pascuaResurreccion.AddDays(-3); }
        }

        public DateTime ViernesSanto
        {
            get { return pascuaResurreccion.AddDays(-2); }
        }

        public DateTime SabadoSanto
        {
            get { return pascuaResurreccion.AddDays(-1); }
        }

        public DateTime DomingoResurreccion
        {
            get { return pascuaResurreccion; }
        }

        #endregion

    }
    
}

Tal como puede apreciarse, tan sólo se trata de calcular el Domingo de Resurrección ( o Domingo de Pascua) y, a partir de ahí, realizar la diferencia en días para obtener el resto.

No sé si me he dejado alguna otra fecha así que animo, como siempre ha hecho el lector de este blog, a completar, entre todos, las posibles fechas que puedan ser calculadas a partir de las fechas aquí calculadas.

Publicado en C# | 2 Comentarios »

Gestionar cuentas de Outlook desde C#

Publicado por alskare en 06/12/2009

Tras haberme peleado un tiempo con el tema de poder configurar la firma de Outlook desde una aplicación propia, tema que puede encontrarse en el post http://alskare.wordpress.com/2009/11/09/aadir-firma-en-outlook/, me quedé con las ganas de poder profundizar un poco más en el tema de cómo se gestiona el tema de las cuentas de correo en el registro del sistema, más que nada, porque estoy más que seguro que en breve necesitaré modificar no sólo la firma en la cuenta predeterminada.

Aunque la idea inicial es la de partir del artículo anterior, rápidamente me doy cuenta de alguna imprecisión cometida por mi parte. Imagino que las prisas y las ganas de acabar rápido logran que, en un primer intento, te dejes algún cabo suelto. En este caso, opto por dejar el artículo anterior tal cuál está y así me permitiré, desde aquí, realizar todas las correcciones o anotaciones que considere necesarias.

Una clave del registro controla las cuentas

La principal imprecisión a la que me refería es cuando indicaba que el primer byte de una determinada clave del registro “apuntaba” a la dirección de la cuenta por defecto. Después de haber intentado algún cambio en mi propio ordenador, casi me quedo sin poder entrar en Outlook al producir un error nada más entrar.

- ¿Qué has hecho para poder provocar este error?.
- Casi nada, tan sólo cambiar directamente, desde el registro, el primer byte que se muestra (el de la cuenta predeterminada) por el de otra cuenta válida
- Así, ¿sin más?. ¿Qué ha ocurrido?
- Poca cosa. No me di cuenta que el valor de dicha entrada no es un byte, son 4 bytes en formato Little Endian. Y además, esta misma clave contiene el orden en el que se encuentran las cuentas de correo. Fíjate en el ejemplo siguiente, mi cuenta por defecto es la que está en la clave 0000000b, la siguiente cuenta en la lista de Outlook es la 00000005 y así sucesivamente.

 registro

- ¿has conseguido arreglarlo?.
- Siempre que toco el registro, hago antes una copia de la rama (menos mal que no le digo que he tenido que descubrir lo del Litte Endian porque no conseguía entrar en Outlook)

Lectura de las cuentas configuradas

Una vez que conocemos tanto la cuenta predeterminada como el resto de cuentas de Outlook, tan sólo nos hace falta hacer una lectura de las mismas, así que empezamos creando una lectura, precisamente de los nombres de las cuentas.

/// <summary>
/// Obtiene una lista con las claves de las cuentas de Outlook
/// </summary>
/// <returns></returns>
public string[] getOutlookAccounts()
{
    RegistryKey rkRaiz = Registry.CurrentUser;

    string ramaCuentas =
          "Software\\Microsoft\\Windows NT"
           + "\\CurrentVersion\\Windows Messaging Subsystem"
           + "\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676";

    List<string> listaCuentas = new List<string>();
    RegistryKey rkCuentas = rkRaiz.OpenSubKey(ramaCuentas);

    // Lectura del valor que contiene las cuentas de Outlook
    object oCuentas = rkCuentas.GetValue("{ED475418-B0D6-11D2-8C3B-00104B2A6676}");
    if (oCuentas != null && oCuentas.GetType().Equals(typeof(byte[])))
    {
        byte[] cuentas = (byte[])oCuentas;
        for (int i = 0; i < cuentas.Length; i += 4)
        {
            // Traspaso de Little Endian a Big Endian
            StringBuilder sb = new StringBuilder();
            sb.Append(((byte)(cuentas.GetValue(i + 3))).ToString("X2").ToLower());
            sb.Append(((byte)(cuentas.GetValue(i + 2))).ToString("X2").ToLower());
            sb.Append(((byte)(cuentas.GetValue(i + 1))).ToString("X2").ToLower());
            sb.Append(((byte)(cuentas.GetValue(i))).ToString("X2").ToLower());

            listaCuentas.Add(sb.ToString());
        }
    }
    return listaCuentas.ToArray();
}

Leer información de una cuenta

Una vez tenemos una lista con las cuentas de correo existentes, podemos empezar a pensar en cómo extraer la información de cada una de las cuentas. Para ello, nos creamos una clase auxiliar para poder almacenar la información.

namespace jnSoftware.Outlook.Accounts
{

    /// <summary>
    /// Clase que representa una cuenta de correo de Outlook en el registro
    /// </summary>
    public class MailAccount
    {

        /// <summary>
        /// Obtiene el orden en el que se encuentra la cuenta de correo en la lista de
        /// Outlook
        /// </summary>
        public int OrderList { get; internal set; }


        /// <summary>
        /// Obtiene el nombre de la clave que contiene la información de la cuenta en 
        /// el registro
        /// </summary>
        public string AccountID { get; internal set; }


        /// <summary>
        /// Obtiene un valor que indica si la cuenta es la predeterminada
        /// </summary>
        public bool IsDefaultAccount
        { get { return OrderList == 0; } }


        /// <summary>
        /// Obtiene la cuenta de correo asociada a la cuenta
        /// </summary>
        public string Email { get; internal set; }


        /// <summary>
        /// Obtiene el nombre de la cuenta de correo.
        /// </summary>
        public string AccountName { get; internal set; }


        /// <summary>
        /// Obtiene el valor establecido como firma para nuevos mensajes
        /// </summary>
        public string NewSignature { get; internal set; }


        /// <summary>
        /// Obtiene el valor establecido como firma para los mensajes que 
        /// se responden
        /// </summary>
        public string ReplySignature { get; internal set; }
             
    }
}

A partir de ahora, la imaginación al poder. Además de los métodos que se nos pueden ocurrir en un primer momento, la verdad es que, pensando un poco en detalle, las posibilidades son amplias. Incluyamos algún ejemplo:

  • Obtener una lista con la información de todas las cuentas
  • Obtener información de una cuenta concreta (por Id o por cuenta de correo)
  • Obtener información de la cuenta de correo predeterminada
  • Cambiar la firma de una cuenta concreta (no sólo la cuenta predeterminada)

Partiendo de los puntos anteriores desarrollo una clase que permita realizar los trabajos mencionados, aunque me imagino que, en el momento que tenga un poco de tiempo acabaré creando alguna aplicación en WinForms que permita realizar estos cambios de un modo más amigable. De momento, con la librería actual se pueden realizar muchos cambios desde, por ejemplo, un inicio de sesión en una red.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;


namespace jnSoftware.Outlook.Accounts
{

    /// <summary>
    /// Gestión de cuentas de correo de Outlook
    /// </summary>
    public class MailAccounts
    {


        private Dictionary<string, MailAccount> cuentasOutlook;

        // Las cuentas de correo dependen directamente de HKCU
        private RegistryKey rkRaiz
        { get { return Registry.CurrentUser; } }

        // De la rama siguiente dependen todas las cuentas de correo definidas
        // en Outlook
        private string ramaCuentas =
               "Software\\Microsoft\\Windows NT"
                + "\\CurrentVersion\\Windows Messaging Subsystem"
                + "\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676";



        /// <summary>
        /// Obtiene las cuentas de correo de Outlook definidas en el registro
        /// </summary>
        /// <returns></returns>
        public List<MailAccount> GetAccounts()
        {
            readInfoAccounts();
            return new List<MailAccount>(cuentasOutlook.Values);
        }


        /// <summary>
        /// Obtiene información de una cuenta de correo determinada
        /// </summary>
        /// <param name="accountID">Identificador de la cuenta de correo. Se corresponde 
        /// con el nombre de la rama del registro</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce la excepción cuando no 
        /// se encuentra la cuenta especificada en <paramref name="accountID"/></exception>
        public MailAccount GetAccount(string accountID)
        {
            readInfoAccounts();
            try
            {
                return cuentasOutlook[accountID];
            }
            catch (KeyNotFoundException ex)
            {
                throw new ArgumentOutOfRangeException("No existe la cuenta solicitada", ex);
            }
            catch { throw; }
        }


        /// <summary>
        /// Obtiene información de la cuenta establecida por defecto
        /// </summary>
        public MailAccount GetDefaultAccount()
        {
            string[] cuentas = getOutlookIdAccounts();
            return GetAccount(cuentas[0]);
        }


        /// <summary>
        /// Obtiene información de una cuenta de correo en base a su dirección electrónica
        /// </summary>
        /// <param name="email">Cuenta de correo de la que se quiere recuperar la información</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce cuando  no se encuentra la 
        /// cuenta de correo especificada en <paramref name="email"/>.</exception>
        public MailAccount GetAcountByEmail(string email)
        {
            readInfoAccounts();

            MailAccount retVal = null;
            // Como en la Uni, desarrollamos un algoritmo de búsqueda
            foreach (MailAccount cuenta in cuentasOutlook.Values)
            {
                if (cuenta.Email == email)
                {
                    retVal = cuenta;
                    break;
                }
            }
            if (retVal != null)
            { return retVal; }
            else { throw new ArgumentOutOfRangeException("Cuenta de correo no encontrada"); }
        }


        /// <param name="email">Cuenta de correo de la que se quiere establecer la firma</param>
        /// <param name="newSignature">Nombre de la firma que quiere establecerse en la cuenta de 
        /// correo para mensajes nuevos</param>
        /// <param name="replySignature">Nombre de la firma que quiere establecerse en la cuenta 
        /// de correo para mensajes respondidos</param>
        public void SetSignature(string email, string newSignature, string replySignature)
        {
            MailAccount cuenta = GetAcountByEmail(email);

            // TODO : Queda pendiente una comprobación para que existan las firmas 
            // en la carpeta de firmas.

            RegistryKey rkCuenta = rkRaiz.OpenSubKey(ramaCuentas + "\\" + cuenta.AccountID, true);
            rkCuenta.SetValue("New Signature", GetFirmaCodificada(newSignature));
            rkCuenta.SetValue("Reply-Forward Signature", GetFirmaCodificada(replySignature));
        }


        /// <summary>
        /// Realiza la lectura de las cuentas definidas en el registro
        /// </summary>
        private void readInfoAccounts()
        {
            cuentasOutlook = new Dictionary<string, MailAccount>();
            string[] cuentas = getOutlookIdAccounts();
            for (int i = 0; i < cuentas.Length; i++)
            {
                MailAccount m = getInfoAccount(cuentas[i]);
                m.OrderList = i;
                cuentasOutlook.Add(m.AccountID, m);
            }
        }


        /// <summary>
        /// Recupera la información de una cuenta determinada.
        /// </summary>
        /// <param name="accountID"></param>
        /// <returns></returns>
        private MailAccount getInfoAccount(string accountID)
        {
            MailAccount m = new MailAccount();
            m.AccountID = accountID;

            RegistryKey rkCuentas = rkRaiz.OpenSubKey(ramaCuentas + "\\" + accountID, false);
            m.Email = binaryToString(rkCuentas.GetValue("Email"));
            m.NewSignature = binaryToString(rkCuentas.GetValue("New Signature"));
            m.ReplySignature = binaryToString(rkCuentas.GetValue("Reply-Forward Signature"));
            m.AccountName = binaryToString(rkCuentas.GetValue("Account Name"));

            return m;
        }


        /// <summary>
        /// Obtiene una lista con las claves de las cuentas de Outlook
        /// </summary>
        /// <returns></returns>
        private string[] getOutlookIdAccounts()
        {
            List<string> listaCuentas = new List<string>();
            RegistryKey rkCuentas = rkRaiz.OpenSubKey(ramaCuentas, false);

            // Lectura del valor que contiene las cuentas de Outlook
            object oCuentas = rkCuentas.GetValue("{ED475418-B0D6-11D2-8C3B-00104B2A6676}");
            if (oCuentas != null && oCuentas.GetType().Equals(typeof(byte[])))
            {
                byte[] cuentas = (byte[])oCuentas;
                for (int i = 0; i < cuentas.Length; i += 4)
                {
                    // Traspaso de Little Endian a Big Endian
                    StringBuilder sb = new StringBuilder();
                    sb.Append(((byte)(cuentas.GetValue(i + 3))).ToString("X2").ToLower());
                    sb.Append(((byte)(cuentas.GetValue(i + 2))).ToString("X2").ToLower());
                    sb.Append(((byte)(cuentas.GetValue(i + 1))).ToString("X2").ToLower());
                    sb.Append(((byte)(cuentas.GetValue(i))).ToString("X2").ToLower());

                    listaCuentas.Add(sb.ToString());
                }
            }
            return listaCuentas.ToArray();
        }


        /// <summary>
        /// Decodifica una cadena del registro en una cadena ASCII.
        /// </summary>
        /// <param name="cadena">Cadena del registro. Devuelve string.empty si la 
        /// cadena estuviese vacía.</param>
        /// <returns></returns>
        private string binaryToString(object cadena)
        {
            StringBuilder sb = new StringBuilder();
            if (cadena != null)
            {
                if (cadena.GetType().Equals(typeof(byte[])))
                {
                    byte[] entrada = (byte[])cadena;
                    ASCIIEncoding ascii = new ASCIIEncoding();

                    // Bucle para eliminar los bytes vacíos que se colocan en el registro
                    for (int i = 0; i < entrada.Length; i++)
                    {
                        if (entrada[i] != 0)
                        {
                            sb.Append(ascii.GetString(entrada, i, 1));
                        }
                        i++;
                    }
                }
            }
            return sb.ToString();
        }


        /// <summary>
        /// Codifica el nombre de la firma en un array de Bytes
        /// </summary>
        /// <returns>La codificación se hace tal cómo lo hace Outlook</returns>
        private static byte[] GetFirmaCodificada(string nombreFirma)
        {
            // Codificación del nombre de la firma
            ASCIIEncoding ascii = new ASCIIEncoding();
            byte[] firmaSinCeros = ascii.GetBytes(nombreFirma);

            // La firma de Outlook contiene un byte vacío por cada byte del nombre,
            // así que incluyo un List para no complicarme la vida y añadirle los 
            // bytes vacíos.
            List<byte> firmaConCeros = new List<byte>();

            // Se añaden los ceros a la firma
            foreach (byte b in firmaSinCeros)
            {
                firmaConCeros.Add(b);
                firmaConCeros.Add(new byte());
            }

            // No sé la razón, pero la firma le añade dos bytes más
            firmaConCeros.Add(new byte());
            firmaConCeros.Add(new byte());

            return firmaConCeros.ToArray();
        }


    }       // class
}           // namespace

 

En esta ocasión no dejo las librerías listas para la descarga puesto que así intento obligarme a crear una tercera entrega dedicada a Outlook en la que intentaré que se puedan establecer algunos cambios desde una aplicación WinForms.

Publicado en C#, Outlook | 1 comentario

C#. Generador de Passwords aleatorios

Publicado por alskare en 13/10/2009

Estoy más que seguro que cualquiera de nosotros hemos sido usuarios en cualquier ocasión de algún tipo de herramienta similar a la que intento crear en estos momentos. De hecho, con una búsqueda bastante genérica en Google surgen multitud de rutinas en cualquier lenguaje. No obstante, como soy de aquellas personas que prefiere crear sus propias rutinas antes de hacer un “Copia y Pega” de lo que han hecho otros, me dispongo a la creación de una utilidad que me permita obtener contraseñas de una manera aleatoria.

¿Qué necesito?

En principio, sólo necesito una rutina que me permita generar un password de 8 caracteres, y que incluya, al menos, un número, un carácter no alfanumérico y algún carácter en mayúscula. Podemos apreciar enseguida que, aunque la necesidad inminente es bastante sencilla, lo mejor es poder parametrizar estos elementos a los que podemos llamar “Caracteres especiales”:

  • Números
  • Mayúsculas
  • Símbolos

Debo reconocer que, a la hora de parametrizar valores, soy un amante de los porcentajes y, en este principio, baso muchas de las rutinas creadas. Es decir, si genero una rutina que me permita crear una contraseña de 10 caracteres, prefiero decir que tenga un 20% de números, un 20% de mayúsculas y otro 20% de símbolos para que la contraseña tenga: 2 números + 2 letras mayúsculas + 2 símbolos + 4 caracteres normales en minúscula. De esta manera, si tengo que ampliar la longitud de la contraseña a 20 caracteres, automáticamente cambiará el número de caracteres especiales, manteniendo la proporción.

Otro de los puntos a tener en cuenta es que los caracteres que pueden emplearse no siempre serán válidos en todos los entornos, por tanto, aunque no me he preocupado de parametrizar este punto, sí que he tenido la precaución de “separarlo” por si alguien tiene que añadir/eliminar algún carácter no compatible, por ejemplo, con un determinado servidor de correo.

string caracteres = "abcdefghijklmnopqrstuvwxyz";
string numeros = "0123456789";
string simbolos = "%$#@+-=&";

¿Cómo enfoco el resultado?

En mi caso, con contraseñas de 8 caracteres suelo pasar. De hecho, debo reconocer que la necesidad de la creación de una rutina que cree passwords automáticamente viene dada por una aplicación Web que tengo que crear en la que, cuando un usuario pierde una contraseña, se le enviará por e-correo y, la primera vez que entre, tendrá que cambiarla, así que prima más la “estética” de la contraseña que la seguridad. Lo que pasa es que, a la gente le gusta más ver símbolos raros que cualquier nombre.

Pues eso, que la idea inicial es la de crear un constructor estándar con 8 caracteres, de los cuales tendrá un 20% de caracteres especiales. Otro constructor que permita crear una contraseña con una longitud variable y otro que permita definir todo:

public GeneradorPassword()
{ }

public GeneradorPassword(int longitudCaracteres)
{ }

public GeneradorPassword(int longitudCaracteres, 
                         int porcentajeMayusculas, 
                         int porcentajeSimbolos, 
                         int porcentajeNumeros)
{ }

En cuanto al sistema de crear la rutina, no me complico tampoco la vida:

  1. Creo una cadena con la longitud de la contraseña todo en minúscula
  2. Obtengo el número de caracteres especiales a añadir
  3. Obtengo una posición aleatoria para colocar los caracteres especiales
  4. Reemplazo los caracteres especiales en las posiciones obtenidas.

El resultado

Dejo la rutina tal cuál me ha quedado:

using System;
using System.Collections.Generic;
using System.Text;

namespace jnSoftware.Utiles
{
    /// <summary>
    /// Clase que permite la generación de una contraseña. 
    /// La contraseña contiene un número de caracteres fijos y permite especificar el porcentaje
    /// de caracteres en mayúsculas y símbolos que se quieren obtener
    /// </summary>
    public class GeneradorPassword
    {


        /// <summary>
        /// Enumeración que permite conocer el tipo de juego de carácteres a emplear
        /// para cada carácter
        /// </summary>
        private enum TipoCaracterEnum { Minuscula, Mayuscula, Simbolo, Numero }

        #region Campos

        private int porcentajeMayusculas;
        private int porcentajeSimbolos;
        private int porcentajeNumeros;
        Random semilla;

        // Caracteres que pueden emplearse en la contraseña
        string caracteres = "abcdefghijklmnopqrstuvwxyz";
        string numeros = "0123456789";
        string simbolos = "%$#@+-=&";

        // Cadena que contiene el password generado
        private StringBuilder password;

        #endregion



        #region Propiedades

        /// <summary>
        /// Obtiene o establece la longitud en carácteres de la contraseña a obtener
        /// </summary>
        public int LongitudPassword { get; set; }

        /// <summary>
        /// Obtiene o establece el porcentaje de carácteres en mayúsculas que 
        /// contendrá la contraseña
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un valor que no coincida con un porcentaje</exception>
        public int PorcentajeMayusculas
        {
            get { return porcentajeMayusculas; }
            set
            {
                if (value < 0 || value > 100)
                    throw new ArgumentOutOfRangeException("El porcentaje es un número entre 0 y 100");
                porcentajeMayusculas = value;
            }
        }


        /// <summary>
        /// Obtiene o establece el porcentaje de símbolos que contendrá la contraseña
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un valor que no coincida con un porcentaje</exception>
        public int PorcentajeSimbolos
        {
            get { return porcentajeSimbolos; }
            set
            {
                if (value < 0 || value > 100)
                    throw new ArgumentOutOfRangeException("El porcentaje es un número entre 0 y 100");
                porcentajeSimbolos = value;
            }
        }

        /// <summary>
        /// Obtiene o establece el número de caracteres numéricos que contendrá la contraseña
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un valor que no coincida con un porcentaje</exception>
        public int PorcentajeNumeros
        {
            get { return porcentajeNumeros; }
            set
            {
                if (value < 0 || value > 100)
                    throw new ArgumentOutOfRangeException("El porcentaje es un número entre 0 y 100");
                porcentajeNumeros = value;
            }
        }

        #endregion


        #region Constructores
        /// <summary>
        /// Constructor. La contraseña tendrá 8 caracteres, incluyendo una letra mayúscula, 
        /// un número y un símbolo
        /// </summary>
        public GeneradorPassword()
            : this(8)
        { }


        /// <summary>
        /// Constructor. La contraseña tendrá un 20% de caracteres en mayúsculas y otro tanto de 
        /// símbolos
        /// </summary>
        /// <param name="longitudCaracteres">Longitud en carácteres de la contraseña a obtener</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un porcentaje de caracteres especiales mayor de 100</exception>
        public GeneradorPassword(int longitudCaracteres)
            : this(longitudCaracteres, 20, 20, 20)
        { }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="longitudCaracteres">Longitud en carácteres de la contraseña a obtener</param>
        /// <param name="porcentajeMayusculas">Porcentaje a aplicar de caracteres en mayúscula</param>
        /// <param name="porcentajeSimbolos">Porcenta a aplicar de símbolos</param>
        /// <param name="porcentajeNumeros">Porcentaje de caracteres numéricos</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce al intentar introducir
        /// un porcentaje de caracteres especiales mayor de 100</exception>
        public GeneradorPassword(int longitudCaracteres, int porcentajeMayusculas, int porcentajeSimbolos, int porcentajeNumeros)
        {
            LongitudPassword = longitudCaracteres;
            PorcentajeMayusculas = porcentajeMayusculas;
            PorcentajeSimbolos = porcentajeSimbolos;
            PorcentajeNumeros = porcentajeNumeros;

            if (PorcentajeMayusculas + porcentajeSimbolos + PorcentajeNumeros > 100)
                throw new ArgumentOutOfRangeException(
                "La suma de los porcentajes de caracteres especiales no puede superar el " +
                "100%, es decir, no puede ser superior a la longitud de la contraseña");
            semilla = new Random(DateTime.Now.Millisecond);
        }

        #endregion


        #region Métodos públicos

        /// <summary>
        /// Obtiene el password
        /// </summary>
        /// <returns></returns>
        public string GetNewPassword()
        {
            GeneraPassword();
            return password.ToString();
        }


        /// <summary>
        /// Permite establecer el número de caracteres especiales que se quieren obtener
        /// </summary>
        /// <param name="numeroCaracteresMayuscula">Número de caracteres en mayúscula</param>
        /// <param name="numeroCaracteresNumericos">Número de caracteres numéricos</param>
        /// <param name="numeroCaracteresSimbolos">Número de caracteres de símbolos</param>
        public void SetCaracteresEspeciales(
            int numeroCaracteresMayuscula
            , int numeroCaracteresNumericos
            , int numeroCaracteresSimbolos)
        {
            // Comprobación de errores
            if (numeroCaracteresMayuscula
                    + numeroCaracteresNumericos
                    + numeroCaracteresSimbolos > LongitudPassword)
                throw new ArgumentOutOfRangeException(
                    "El número de caracteres especiales no puede superar la longitud del password");

            PorcentajeMayusculas = numeroCaracteresMayuscula * 100 / LongitudPassword;
            PorcentajeNumeros = numeroCaracteresNumericos * 100 / LongitudPassword;
            PorcentajeSimbolos = numeroCaracteresSimbolos * 100 / LongitudPassword;
        }



        /// <summary>
        /// Constructor. La contraseña tendrá 8 caracteres, incluyendo una letra mayúscula, 
        /// un número y un símbolo
        /// </summary>
        public static string GetPassword()
        {
            // Se crea un método estático para facilitar el uso
            GeneradorPassword gp = new GeneradorPassword();
            return gp.GetNewPassword();
        }


        #endregion


        #region Métodos de cálculo

        /// <summary>
        /// Método que genera el password. Primero crea una cadena de caracteres 
        /// en minúscula y va sustituyendo los caracteres especiales
        /// </summary>
        private void GeneraPassword()
        {
            // Se genera una cadena de caracteres en minúscula con la longitud del 
            // password seleccionado
            password = new StringBuilder(LongitudPassword);
            for (int i = 0; i < LongitudPassword; i++)
            {
                password.Append(GetCaracterAleatorio(TipoCaracterEnum.Minuscula));
            }

            // Se obtiene el número de caracteres especiales (Mayúsculas y caracteres) 
            int numMayusculas = (int)(LongitudPassword * (PorcentajeMayusculas / 100d));
            int numSimbolos = (int)(LongitudPassword * (PorcentajeSimbolos / 100d));
            int numNumeros = (int)(LongitudPassword * (PorcentajeNumeros / 100d));

            // Se obtienen las posiciones en las que irán los caracteres especiales
            int[] caracteresEspeciales =
                    GetPosicionesCaracteresEspeciales(numMayusculas + numSimbolos + numNumeros);
            int posicionInicial = 0;
            int posicionFinal = 0;

            // Se reemplazan las mayúsculas
            posicionFinal += numMayusculas;
            ReemplazaCaracteresEspeciales(caracteresEspeciales,
                 posicionInicial, posicionFinal, TipoCaracterEnum.Mayuscula);

            // Se reemplazan los símbolos
            posicionInicial = posicionFinal;
            posicionFinal += numSimbolos;
            ReemplazaCaracteresEspeciales(caracteresEspeciales,
                 posicionInicial, posicionFinal, TipoCaracterEnum.Simbolo);

            // Se reemplazan los Números
            posicionInicial = posicionFinal;
            posicionFinal += numNumeros;
            ReemplazaCaracteresEspeciales(caracteresEspeciales,
                 posicionInicial, posicionFinal, TipoCaracterEnum.Numero);
        }



        /// <summary>
        /// Reemplaza un caracter especial en la cadena Password
        /// </summary>
        private void ReemplazaCaracteresEspeciales(
                                        int[] posiciones
                                        , int posicionInicial
                                        , int posicionFinal
                                        , TipoCaracterEnum tipoCaracter)
        {
            for (int i = posicionInicial; i < posicionFinal; i++)
            {
                password[posiciones[i]] = GetCaracterAleatorio(tipoCaracter);
            }
        }



        /// <summary>
        /// Obtiene un array con las posiciones en las que deberán colocarse los caracteres
        /// especiales (Mayúsculas o Símbolos). Es importante que no se repitan los números
        /// de posición para poder mantener el porcentaje de dichos carácteres
        /// </summary>
        /// <param name="numeroPosiciones">Valor que representa el número de posiciones
        /// que deberán crearse sin repetir</param>
        private int[] GetPosicionesCaracteresEspeciales(int numeroPosiciones)
        {
            List<int> lista = new List<int>();
            while (lista.Count < numeroPosiciones)
            {
                int posicion = semilla.Next(0, LongitudPassword);
                if (!lista.Contains(posicion))
                {
                    lista.Add(posicion);
                }
            }
            return lista.ToArray();
        }


        /// <summary>
        /// Obtiene un carácter aleatorio en base a la "matriz" del tipo de caracteres
        /// </summary>
        private char GetCaracterAleatorio(TipoCaracterEnum tipoCaracter)
        {
            string juegoCaracteres;
            switch (tipoCaracter)
            {
                case TipoCaracterEnum.Mayuscula:
                    juegoCaracteres = caracteres.ToUpper();
                    break;
                case TipoCaracterEnum.Minuscula:
                    juegoCaracteres = caracteres.ToLower();
                    break;
                case TipoCaracterEnum.Numero:
                    juegoCaracteres = numeros;
                    break;
                default:
                    juegoCaracteres = simbolos;
                    break;
            }

            // índice máximo de la matriz char de caracteres
            int longitudJuegoCaracteres = juegoCaracteres.Length;

            // Obtención de un número aletorio para obtener la posición del carácter
            int numeroAleatorio = semilla.Next(0, longitudJuegoCaracteres);

            // Se devuelve una posición obtenida aleatoriamente
            return juegoCaracteres[numeroAleatorio];
        }

        #endregion

    }
}

 

La rutina puede probarse gracias a los amigos de velasco.biz: Probar rutina

Publicado en C#, Helpers, NetFramework | 3 Comentarios »

C# Personalizar la ordenación de listas con IComparable

Publicado por alskare en 19/09/2009

Hace poco, designios extraños de la vida, me ha tocado hacer una ordenación de una serie de objetos string tal como se hacía en la antigüedad. Cuando hablo de la antigüedad, tan sólo dejo correr un poco la memoria hasta los tiempos en que vestía con un jersey de cuello alto y unos pantalones cortos. Entonces, al pasar por los pasillos de las aulas, no era nada extraño escuchar una especie de canto gregoriano (pero a lo pobre) en el que una panda de alumnos que pensaban más en el pájaro del árbol de enfrente que en lo que estaban haciendo, solían recitar: a, b, c, ch (che), d, e , f, g, h, i, j, k, l, m, n, ñ, o, p, q, r, rr (erre doble), s, t, u, v, w, x, y, y zeta.

Bueno, el caso es que esta vez no me ha tocado hacer nada sobre la rr, pero sí que he tenido que hacer un sistema de ordenación en el que quedase contemplado la letra che. Por cierto, dicen que la curiosidad mata al gato y, como soy muy cotilla, se me ha ocurrido buscar la letra en la R.A.E y, ¡sorpresa!, ¡Todavía existe! (http://buscon.rae.es/draeI/SrvltConsulta?TIPO_BUS=3&LEMA=che).

Siempre me voy por las ramas, así que… al lío:

Vamos a crear un ejemplo con la socorrida clase Persona y, como haremos uso de una colección List<Persona>, le implementamos la interfaz IComparable. Aunque ya sabemos que el resultado no será el que andamos buscando, nos dará una idea de cómo nos realizaría la ordenación cualquier colección que nos permita realizar una ordenación de los objetos Persona.

using System;
using System.Collections.Generic;

namespace OrdenacionStrings
{
    class Ordenando
    {
        static void Main(string[] args)
        {
            List<Persona> gente = new List<Persona>();
            gente.Add(new Persona("Jaime", "Cebado Sánchez"));
            gente.Add(new Persona("Victoria", "Chacón Gómez"));
            gente.Add(new Persona("Anibal", "Chacón Gómez"));
            gente.Add(new Persona("Andrés", "Cullera Ramón"));
            gente.Add(new Persona("Juan Manuel", "Damián Rodriguez"));

            gente.Sort();

            foreach (Persona p in gente)
            {
                Console.WriteLine(p.Apellidos + ", " + p.Nombre);
            }
            Console.ReadKey();
        }
    }


    public class Persona : IComparable
    {
        public string Nombre { get; set; }
        public string Apellidos { get; set; }

        public Persona(string nombre, string apellidos)
        {
            Nombre = nombre;
            Apellidos = apellidos;
        }

        public int CompareTo(object obj)
        {
            try
            {
                Persona p = (Persona)obj;
                return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
            }
            catch { return 0; }
        }
    }

}

En el momento en que ejecutemos el código, veremos que la ordenación que hace es la actual, es decir, tendremos un resultado en el cual, los “Chacón” estarán antes de “Cullera”, cuando en realidad deberían salir después, puesto que contienen la letra (o dígrafo) che.

Cebado Sánchez, Jaime
Chacón Gómez, Anibal
Chacón Gómez, Victoria
Cullera Ramón, Andrés
Damián Rodriguez, Juan Manuel

Una de las ventajas de la Interfaz IComparable es que podemos implementar el método CompareTo como nos plazca (gracias, OO) y, con una serie de pequeños cambios en la clase Persona, podemos realizar un control de tal manera que, analizando las dos primeras letras de la cadena, podemos indicarle si la ch irá antes o después de la c.

Así, con una implementación distinta de la anterior del métodos CompareTo, el resultado será el deseado desde el principio:

public int CompareTo(object obj)
 {
     try
     {
         Persona p = (Persona)obj;
         if (!this.Apellidos.ToLower().StartsWith("ch"))
         {
             return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
         }
         else
         {
             if (!p.Apellidos.ToLower().StartsWith("ch"))
             {
                 return (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") * -1);

                 /* la línea anterior intenta simplificar el conjunto de operaciones siguientes.
                  * Sólo se trata de realizar una inversión en la comparación de la ch y la d
                  */ 

                 //if (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") == 1)
                 //    return -1;
                 //else if (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") == -1)
                 //    return 1;
                 //else return 0;
             }
             else
             {
                 return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
             }
         }
     }
     catch { return 0; }
 }
    }

Resultado obtenido:

Cebado Sánchez, Jaime
Cullera Ramón, Andrés
Chacón Gómez, Anibal
Chacón Gómez, Victoria
Damián Rodriguez, Juan Manuel

 

Dejo aquí el código de la clase al completo para no tener que ir buscando entre los “cachitos” de código del post.

using System;
using System.Collections.Generic;

namespace OrdenacionCorrecta
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Persona> gente = new List<Persona>();
            gente.Add(new Persona("Jaime", "Cebado Sánchez"));
            gente.Add(new Persona("Victoria", "Chacón Gómez"));
            gente.Add(new Persona("Anibal", "Chacón Gómez"));
            gente.Add(new Persona("Andrés", "Cullera Ramón"));
            gente.Add(new Persona("Juan Manuel", "Damián Rodriguez"));

            gente.Sort();

            foreach (Persona p in gente)
            {
                Console.WriteLine(p.Apellidos + ", " + p.Nombre);
            }
            Console.ReadKey();
        }
    }


    public class Persona : IComparable
    {
        public string Nombre { get; set; }
        public string Apellidos { get; set; }

        public Persona(string nombre, string apellidos)
        {
            Nombre = nombre;
            Apellidos = apellidos;
        }


        public int CompareTo(object obj)
        {
            try
            {
                Persona p = (Persona)obj;
                if (!this.Apellidos.ToLower().StartsWith("ch"))
                {
                    return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
                }
                else
                {
                    if (!p.Apellidos.ToLower().StartsWith("ch"))
                    {
                        return (string.Compare((p.Apellidos + " " + p.Nombre).ToLower(), "d") * -1);
                    }
                    else
                    {
                        return (this.Apellidos + " " + this.Nombre).CompareTo(p.Apellidos + " " + p.Nombre);
                    }
                }
            }
            catch { return 0; }
        }
    }
    
}

Publicado en C#, NetFramework | 2 Comentarios »

C# Mantener la configuración de tamaño y posición de los formularios

Publicado por alskare en 15/09/2009

No sería la primera vez que los clientes piden que, en los formularios WinForms, se guarde tanto la posición como el tamaño del formulario, incluso después de cerrar la aplicación. Bajo esta premisa, me inicio en un nuevo post que tiene como finalidad, precisamente esto, almacenar la información de tamaño del formulario y recuperarla cada vez que se abra de nuevo el mismo formulario.

Framework.Net nos ofrece una serie de posibilidades amplias para poder guardar información en el área de trabajo del usuario. Una de estas opciones es el namespace System.XML, el cual nos permite poder trabajar con ficheros XML, tanto a nivel de lectura como de escritura. Aprovechando estas características, se me ocurre la creación de un fichero en el que poder almacenar los datos de tamaño y posición del formulario. Ahora bien, el problema de una aplicación es la cantidad de formularios existentes, así que empiezo a pensar en un fichero con una estructura similar a la siguiente:

<?xml version="1.0" encoding="utf-8"?>
<Configuracion>
    <NombreForm1>
        <WindowState>ValorWindowStateForm1</WindowState>
        <Top>ValorTopForm1</Top>
        <Left>ValorTopForm1</Left>
        <Width>ValorTopForm1</Width>
        <Height>ValorTopForm1</Height>
    </NombreForm1>
    <NombreForm2>
        <WindowState>ValorWindowStateForm2</WindowState>
        <Top>ValorTopForm2</Top>
        <Left>ValorTopForm2</Left>
        <Width>ValorTopForm2</Width>
        <Height>ValorTopForm2</Height>
    </NombreForm2>
</Configuracion>

De esta manera, si los datos de cada formulario representa un nodo dentro del archivo XML, puede resultar más o menos sencilla la adaptación de la librería a cualquier otro valor.

Una vez está decidido cuál será el medio de almacenamiento, cabe comenzar a pensar en cómo se realizarán las operaciones de lectura y escritura sobre el fichero XML, así que, recordando las clases de Programación Orientada a Objeto, se me ocurre la idea de crear un formulario base del que heredarán todos aquellos formularios que necesiten almacenar dicha información.

Manos a la obra. Comenzamos creando un nuevo proyecto que será una Biblioteca de controles de WindowsForms, que contendrá un formulario denominado FormularioBase. Éste será un formulario estándar en el que primarán dos cosas:

  1. En el evento FormClosing se almacenarán los datos que interesen en el fichero de configuración
  2. En el evento Load se recuperarán, en caso de existir previamente, los datos de configuración y se aplicarán al formulario

Así, una vez creado el campo que contendrá el fichero de configuración,

private string ficheroXML = Path.Combine(Application.StartupPath, "ConfForm.xml");

nos ponemos a implementar el proceso de almacenamiento de los datos:

/// <summary>
 /// Almacena la información del formulario actual en fichero XML
 /// </summary>
 private void GuardaConfiguracionForm()
 {
     string nombreForm = this.Name;
     XmlNode nodoPrincipal;

     try
     {
         // Intancia del documento XML que permitirá guardar la configuración
         XmlDocument doc = new XmlDocument();

         // Se comprueba que exista el fichero
         if (!File.Exists(ficheroXML))
         {
             // No existe el fichero. Se añaden los nodos iniciales
             doc.AppendChild(doc.CreateComment("Configuración de formularios - älskare, Sep/09"));

             // Creación del nodo principal
             nodoPrincipal = doc.CreateNode(XmlNodeType.Element, "Configuracion", null);

             // Se añade el nodo principal al documento
             doc.AppendChild(nodoPrincipal);
         }
         else
         {
             // El fichero existe previamente. Se recupera la información
             doc.Load(ficheroXML);

             // Lectura del nodo principal
             nodoPrincipal = doc.SelectSingleNode("Configuracion");
         }

         // Se comprueba si existe un nodo con el nombre del form actual
         XmlNode nodoFormActual = nodoPrincipal.SelectSingleNode(nombreForm);
         if (nodoFormActual == null)
             nodoFormActual =
                   nodoPrincipal.AppendChild(
                     doc.CreateNode(XmlNodeType.Element, nombreForm, null));

         // Almacenamiento de los valores
         NodoValor(doc, nodoFormActual, "WindowState", this.WindowState);
         NodoValor(doc, nodoFormActual, "Top", this.Top);
         NodoValor(doc, nodoFormActual, "Left", this.Left);
         NodoValor(doc, nodoFormActual, "Width", this.Width);
         NodoValor(doc, nodoFormActual, "Height", this.Height);


         // Se guarda el fichero de configuración en disco
         XmlTextWriter tw = new XmlTextWriter(ficheroXML, Encoding.UTF8);
         tw.Indentation = 4;
         tw.IndentChar = " "[0];
         tw.Formatting = Formatting.Indented;

         doc.Save(tw);
         doc = null;

         tw.Flush();
         tw.Close();
     }
     catch { }
 }

 /// <summary>
 /// Crea un nuevo par clave-valor en el nodo actual en caso de que no exista
 /// <returns></returns>
 XmlNode NodoValor(XmlDocument doc, XmlNode nodoPadre, string Clave, Object Valor)
 {
     XmlNode nodoActual = nodoPadre.SelectSingleNode(Clave);
     if (nodoActual == null)
         nodoActual = nodoPadre.AppendChild(doc.CreateNode(XmlNodeType.Element, Clave, null));
     nodoActual.InnerText = Valor.ToString();
     return nodoActual;
 }

 

Una vez realizado el proceso de escritura del fichero, falta poder recuperarlo para aplicar los cambios en el formulario cuando se abra.

/// <summary>
/// Realiza una lectura del fichero de configuración y aplica los cambios al form
/// </summary>
private void LecturaConfiguracionForm()
{
    // No se controlan errores. En caso de existir cualquier error
    // no se modifica nada en el formulario actual
    try
    {
        string nombreForm = this.Name;
        XmlDocument doc = new XmlDocument();
        doc.Load(ficheroXML);

        // Lectura del nodo principal
        XmlNode nodoPrincipal = doc.SelectSingleNode("Configuracion");

        // Lectura del nodo del formulario actual
        XmlNode nodoActual = nodoPrincipal.SelectSingleNode(nombreForm);

        // Lectura del modo de presentación de la ventana
        this.WindowState = (FormWindowState)Enum.Parse(typeof(FormWindowState), GetValor(nodoActual, "WindowState").ToString(), false);

        if (this.WindowState == FormWindowState.Normal)
        {
            this.Top = Convert.ToInt32(GetValor(nodoActual, "Top"));
            this.Left = Convert.ToInt32(GetValor(nodoActual, "Left"));
            this.Width = Convert.ToInt32(GetValor(nodoActual, "Width"));
            this.Height = Convert.ToInt32(GetValor(nodoActual, "Height"));
        }
    }
    catch { }
}

/// <summary>
/// Obtiene el valor de la clave
/// </summary>
private object GetValor(XmlNode nodoPadre, string clave)
{
    XmlNode valor = nodoPadre.SelectSingleNode(clave);
    return valor.LastChild.Value;
}

Implementadas tanto la escritura como la lectura de los valores, quedará aplicar dichos cambios al formulario actual. En esta parte, debo reconocer que no he conseguido eliminar un efecto un tanto raro que se da al abrir el formulario, sobre todo, si el formulario a mostrar está contenido en otro en modo MDI. Seguiremos trabajando sobre el tema para ver si consigo eliminar dicho efecto antiestético.

public FormularioBase()
{
    this.SuspendLayout();
    InitializeComponent();
    this.ResumeLayout();

    this.Load += new EventHandler(FormularioBase_Load);
    this.FormClosing += new FormClosingEventHandler(FormularioBase_FormClosing);
}

void FormularioBase_Load(object sender, EventArgs e)
{
    LecturaConfiguracionForm();
}
void FormularioBase_FormClosing(object sender, FormClosingEventArgs e)
{
    GuardaConfiguracionForm();
}

Bueno, pues ya está. A partir de ahora, cada vez que cree un nuevo WinForms, tan sólo añadiré una referencia a la librería en la que tengo este formulario base y, por supuesto, haré que cada formulario herede de FormularioBase.

public partial class Form2 : jnSoftware.Utiles.FormularioBase 
    {
        public Form2()
        {
            InitializeComponent();
        }
    }

 

Si le interesa a alguien, dejo enlace en el que se puede descargar un ejemplo de uso: descargar.

Publicado en C#, Controles, WinForms | 2 Comentarios »

Personalizar el HTMLEditor de AjaxControlToolkit

Publicado por alskare en 10/09/2009

Primeros pasos

Una de las novedades que nos ha traído el Ajax Control Toolkit (May 2009 Release) ha sido un editor HTML para nuestras páginas la mar de práctico. Hasta el momento, cada vez que he tenido que hacer uso de algún control similar con la finalidad de que el usuario pueda escribir negritas o subrayados, siempre he tenido que echar mano de algún componente externo. Debo reconocer que alguno de estos controles han sido realmente buenos y han cumplido a la perfección el objetivo. No obstante, al hacer uso de componentes de varios desarrolladores, llega un momento que el tamaño de la aplicación crece y, sobre todo, cualquier actualización se eterniza. Todo esto, sin contar con que cada control suele incluir sistemas propios de configuración (no será la primera vez que pierdo horas buscando dónde se indica un path para, por ejemplo, añadir una colección de botones propia).

Al ver por vez primera el HTMLEditor debo reconocer que me entusiasmé, puesto que veía la gran utilidad que tenía tener englobado el control con el resto de extensores que suelo usar a menudo. Propiedades y métodos más o menos conocidos, personalización de opciones…todo con una misma “filosofía”!!!!

Este entusiasmo inicial comenzó a decaer nada más empezar a utilizarlo (igual es que nos contagiamos de los clientes y cada día nos volvemos más exigentes). Y, por supuesto, nada más empezar a utilizarlo significa hacer uso de una estructura mínima para poder hacer uso del mismo

Estructura básica para hacer uso del HTMLEditor

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit.HTMLEditor"
    TagPrefix="AjaxCT" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>HTML Editor - Pruebas iniciales</title>
    <style type="text/css" media="all">
        * {
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="sm" runat="server" />
    <div>
        <h1>HTML Editor</h1>
        <AjaxCT:Editor ID="MiEditor" runat="server"  />
    </div>
    </form>
</body>
</html>

Con este código conseguimos añadir un control HTMLEditor en una de nuestras páginas. Apreciamos que no se ha cambiado un ápice de la “estructura original”, es decir, que el resultado que obtendremos también será el de una estructura básica:

image

Primeros pasos y… Primeros problemas

Una vez pasada la euforia inicial, cuando empezamos a aplicar el control en algún tipo de aplicación, comenzamos a apreciar que, en ocasiones, un control tan “extenso” puede llegar a ser demasiado para situaciones concretas. Por ejemplo, en ocasiones he contado con un espacio ínfimo para crear un control de este estilo y, en estos casos, hay que empezar a apurar botones (otra situación podría ser la de eliminar el tamaño de letra para evitar que los usuarios creen mensajes en los foros al estilo HOIGAN).

Así, de buenas a primeras, me marco dos objetivos iniciales:

  1. Aprender a personalizar el número de botones que aparecerán en las barras de herramientas del control
  2. Aprender a mostrar/ocultar estas barras de herramientas en tiempo de diseño/ejecución.

Primeras soluciones

Mirando un poco el explorador de objetos, veo que la clase AjaxControlToolkit.HTMLEditor.Editor contiene un par de métodos que me llaman la atención:

  • FillTopToolbar
  • FillBottonToolbar.

Así, en el ejemplo siguiente vamos a crear un editor que incluya, exclusivamente, los botones de formato estándar Negrita, Cursiva y Subrayado. Por tanto, empezaremos creando una nueva clase en la carpeta App_Code, la cual, heredará directamente del editor original.

using AjaxControlToolkit.HTMLEditor;

namespace jnSoftware.WebControls
{
    public class jnHTMLEditor : Editor
    {

        /// <summary>
        /// Método en el que se colocarán los elementos de la barra superior del control
        /// </summary>
        protected override void FillTopToolbar()
        {
            // Botón de negrita
            this.TopToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.Bold());
            // Botón de cursiva
            this.TopToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.Italic());
            // Botón de subrayado
            this.TopToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.Underline());
        }


        /// <summary>
        /// Método en el que se colocarán los elementos de la barra inferior del control
        /// </summary>
        protected override void FillBottomToolbar()
        {
            // Vista Diseño
            this.BottomToolbar.Buttons.Add( 
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.DesignMode());
            // Vista Previa
            this.BottomToolbar.Buttons.Add(
                new AjaxControlToolkit.HTMLEditor.ToolbarButton.PreviewMode());

            // base.FillBottomToolbar();
        }
    }
}

A partir de aquí, ya vemos que podemos empezar a personalizar la barra de herramientas, tanto superior, como inferior. De hecho, gracias al IDE de VisualStudio es realmente sencillo y cómodo añadir el resto de botones deseados.

image

 

Agregando propiedades

En alguna que otra ocasión, he hecho uso de los editores de texto mostrando u ocultando las barras de herramientas, dependiendo del contexto. Así, como lo necesito para el editor, lo que hago es crear una propiedad que permita mostrar u ocultar las barras de herramientas:

using AjaxControlToolkit.HTMLEditor;

namespace jnSoftware.WebControls
{
    public class jnHTMLEditor : Editor
    {

        /// <summary>
        /// Muestra u oculta la barra de herramientas superior
        /// </summary>
        public bool ShowTopToolBar
        {
            get { return showTopToolBar; }
            set { showTopToolBar = value; }
        }
        private bool showTopToolBar = true ;


        /// <summary>
        /// Muestra u oculta la barra de botones inferior
        /// </summary>
        public bool ShowBottonToolbar
        {
            get { return showBottomToolbar; }
            set { showBottomToolbar = value; }
        }
        private bool showBottomToolbar = true;


        /// <summary>
        /// Método en el que se colocarán los elementos de la barra superior del control
        /// </summary>
        protected override void FillTopToolbar()
        {
            if (showTopToolBar)
            {
                // Botón de negrita
                this.TopToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.Bold());
                // Botón de cursiva
                this.TopToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.Italic());
                // Botón de subrayado
                this.TopToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.Underline());
            }
        }

        /// <summary>
        /// Método en el que se colocarán los elementos de la barra inferior del control
        /// </summary>
        protected override void FillBottomToolbar()
        {
            if (ShowBottonToolbar)
            {
                // Vista Diseño
                this.BottomToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.DesignMode());
                // Vista Previa
                this.BottomToolbar.Buttons.Add(
                    new AjaxControlToolkit.HTMLEditor.ToolbarButton.PreviewMode());
            }
        }
    }
}

Una vez creada la clase, bastará con hacer alguna pequeña modificación en el fichero .ASPX para que se muestre el control creado:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Namespace="jnSoftware.WebControls" TagPrefix="jnControles" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>HTML Editor - Pruebas iniciales</title>
    <style type="text/css" media="all">
        * {
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="sm" runat="server" />
    <div>
        <h1>HTML Editor</h1>
        <jnControles:jnHTMLEditor ID="MiEditor" runat="server"  />
    </div> 
    </form>
</body>
</html>

 

image

Publicado en ASP.Net, C#, Controles | 4 Comentarios »

 
Seguir

Get every new post delivered to your Inbox.