You are on page 1of 31

TextBox con formato

Algunas de las cosas ms interesantes que encontr a la hora de programar con .NET fueron la flexibilidad y enorme transparencia de su biblioteca de clases. Esto nos abre un abanico de posibilidades verdaderamente inmenso, sobre todo si lo comparamos con las versiones anteriores de Visual Basic. Ciertamente se puede trabajar en .NET igual que se haca antes en VB, programando las respuestas correspondientes a los eventos que iban sucediendo. Sin embargo, y aqu est la gran ventaja, tambin podemos ahorrarnos gran parte del trabajo si ajustamos el comportamiento de los controles a nuestras necesidades, evitando as tener que programar procedimientos de evento una y otra vez en infinidad de ventanas y aplicaciones. Esto es exactamente lo que hemos pretendido con el control FormattedTextBox. Est heredado del control TextBox de la biblioteca de clases de .NET, con lo cual incorporamos todos sus miembros sin necesidad de volver a programarlos, y despus le aadimos nuevos mtodos y propiedades y modificamos aquellos de la clase base cuyo comportamiento vamos a cambiar para ajustar el control a nuestras necesidades. La idea es tener un control TextBox que permita al usuario definir distintos formatos de entrada sin necesidad de que este tenga que manejar el evento KeyPress para controlar si la tecla pulsada es vlida o no lo es. Lo mejor es programar este control en una biblioteca de clases para que sea reutilizable en todas nuestras aplicaciones hechas en .NET. Estas son las caractersticas nuevas del control FormattedTextBox: Propiedades nuevas: Format: que sirve para especificar el formato de entrada en la caja de texto. Los formatos que acepta estn enumerados y son los siguientes: SpacedAlphaNumeric: Acepta todos los caracteres y, por lo tanto, se comporta igual que un TextBox normal. Este es el formato por defecto. NoSpacedAlphaNumeric: Acepta todos los caracteres a excepcin del espacio. SpacedAlphabetic: Acepta solamente letras maysculas y minsculas y el espacio. No acepta nmeros, ni signos. NoSpacedAlphabetic: Acepta solamente letras maysculas y minsculas. UnsignedNumber: Acepta solamente nmeros enteros sin signo. SignedNumber: Acepta nmeros enteros con signo. El signo solamente puede ser - (si no se pone se asume que es positivo), y nicamente puede ponerse al principio. UnsignedFloatingPointNumber: Acepta nmeros decimales sin signo con coma decimal flotante. Obviamente, solo se puede poner una coma. En caso de que hubiera una escrita no permite escribir otra hasta que la anterior sea borrada.

SignedFloatingPointNumber: Igual que el anterior, pero adems acepta el signo - nicamente si se pone al principio. UnsignedFixedPointNumber: Acepta nmeros decimales sin signo con coma decimal fija. El nmero de decimales que se admitirn se debe especificar en la propiedad Decimals. SignedFixedPointNumber: Igual que el anterior, pero adems acepta el signo - nicamente si se pone al principio. HexadecNumber: Acepta nmeros en formato hexadecimal, es decir, en base 16. OctalNumber: Acepta nmeros en formato octal, es decir, en base 8. BynaryNumber: Acepta nmeros en formato binario, es decir, en base 2. UserDefined: Acepta solamente los dgitos especificados en la propiedad UserValues. Decimals: Sirve para especificar el nmero de decimales. Solamente para los formatos UnsignedFixedPointNumber y SignedFixedPointNumber. DecSeparator: Sirve para especificar el carcter que se usar como separador decimal. Solo admite el punto o la coma. UserValues: Sirve para especificar la cadena de caracteres de entrada vlidos cuando la propiedad Format sea UserDefined. Mtodos nuevos: ToDouble(): Devuelve el contenido de la caja de texto como un valor de tipo double considerando el formato actual del control, es decir, si se est usando HexadecNumber, el mtodo lo pasa a base decimal y despus lo convierte a double. Si no se puede convertir retorna 0. ToInt64(): Devuelve el contenido de la caja de texto como un valor de tipo long considerando el formato actual del control. Si no se puede convertir retorna 0. ToHexadecimal(): Devuelve una cadena con el nmero contenido en la caja de texto en formato hexadecimal considerando el formato actual del control. ToOctal(): Devuelve una cadena con el nmero contenido en la caja de texto en formato octal considerando el formato actual del control. ToBynary(): Devuelve una cadena con el nmero contenido en la caja de texto en formato binario considerando el formato actual del control. Miembros sobreescritos (override) de la clase base: Propiedad Text: Lgicamente, cuando se intente asignar control.Text="algo", habr que filtrar la cadena, aceptando nicamente los caracteres que sean vlidos segn el formato que se est utilizando.

Mtodo WndProc: Este es el mtodo que se ocupa de gestionar los mensajes de Windows. Los mensajes producidos por un carcter no vlido deben ser ignorados. As, evitamos tambin que estos caracteres que no son vlidos provoquen la ejecucin del evento KeyPress. Ahora bien, si queremos que todos los caracteres (incluidos aquellos que no son vlidos para el formato actual) generen el evento KeyPress bastara con haber sobreescrito el mtodo DefWndProc en lugar de este. Bien este el el cdigo (en C#) del control FormattedTextBox: using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms;

namespace TextBoxConFormato { #region Enumeracin de formatos /// <summary> /// Listado de todos los formatos que es capaz de gestionar la /// caja de texto /// </summary> public enum tbFormats { /// <summary> /// Todos los caracteres y nmeros con espacios. Valor por /// defecto de la propiedad Format /// </summary> SpacedAlphaNumeric, /// <summary>

/// Todos los caracteres y nmeros sin espacios /// </summary> NoSpacedAlphaNumeric, /// <summary> /// Slo las letras con espacios /// </summary> SpacedAlphabetic, /// <summary> /// Slo las letras sin espacios /// </summary> NoSpacedAlphabetic, /// <summary> /// Slo nmeros enteros sin signo /// </summary> UnsignedNumber, /// <summary> /// Slo nmeros enteros con signo /// </summary> SignedNumber, /// <summary> /// Slo nmeros con coma decimal flotante sin signo /// </summary> UnsignedFloatingPointNumber, /// <summary> /// Slo nmeros con coma decimal flotante con signo /// </summary>

SignedFloatingPointNumber, /// <summary> /// Slo nmeros con coma decimal fija sin signo. El nmero /// de decimales se debe especificar en la propiedad Decimals /// </summary> UnsignedFixedPointNumber, /// <summary> /// Slo nmeros con coma decimal fija con signo. El nmero /// de decimales se debe especificar en la propiedad Decimals /// </summary> SignedFixedPointNumber, /// <summary> /// Slo nmeros en formato hexadecimal /// </summary> HexadecNumber, /// <summary> /// Slo nmeros en formato octal /// </summary> OctalNumber, /// <summary> /// Slo nmeros en formato binario /// </summary> BynaryNumber, /// <summary> /// Definido por usuario /// </summary>

UserDefined } #endregion

/// <summary> /// Caja de texto que permite el control automto del formato /// de entrada del texto /// </summary> public class FormattedTextBox : System.Windows.Forms.TextBox { private System.ComponentModel.Container components = null;

#region Campos protected y private

// Almacena el valor de la propiedad Format protected tbFormats format;

/* Mete las teclas TAB, RETORNO e INTRO en la cadena * ControlKeys para aceptar siempre estas teclas */ protected string ControlKeys=((char) 8).ToString()+ ((char) 9).ToString()+((char) 13).ToString();

// Almacena el valor de la propiedad UserValues protected string userValues="";

// Almacena el valor de la propiedad DecSeparator

protected char decSeparator='.';

// Almacena el valor de la propiedad Decimals protected byte decimals=0;

// Almacena los dgitos vlidos para algunos formatos private string okValues;

#endregion

#region Constructor y mtodo protected Dispose public FormattedTextBox():base() { // Llamada requerida por el Diseador de formularios Windows.Forms. InitializeComponent();

// Inicializa el formato por defecto this.Format=tbFormats.SpacedAlphabetic; }

/// <summary> /// Limpiar los recursos que se estn utilizando. /// </summary> protected override void Dispose( bool disposing ) {

if( disposing ) { if( components != null ) components.Dispose(); } base.Dispose( disposing ); } #endregion

#region Component Designer generated code /// <summary> /// Mtodo necesario para admitir el Diseador, no se puede modificar /// el contenido del mtodo con el editor de cdigo. /// </summary> private void InitializeComponent() { components = new System.ComponentModel.Container(); } #endregion

#region Mtodos privados de apoyo

/// <summary> /// Valida el mensaje WM_CHAR recibido desde WndProc. /// </summary> ///

/// <param name="m"> /// Mensaje WM_CHAR recibido en WndProc /// </param> /// /// <returns> /// true si la pulsacin es vlida /// false si la pulsacin no es vlida /// </returns> private bool Validar(ref Message m) { /* Para algunos formatos habr que aceptar determinados * caracteres dependiendo del texto que contenga y/o * de la posicin del cursor */

string values="";

switch (this.format) { case tbFormats.SpacedAlphaNumeric: return true;

case tbFormats.NoSpacedAlphaNumeric: if (m.WParam.ToInt32()!=(int) ' ') return true; else return false;

case tbFormats.SignedNumber: values="1234567890"+this.ControlKeys;

this.ComprobarSigno(ref values);

break; case tbFormats.UnsignedFloatingPointNumber: values="1234567890"+this.ControlKeys;

/* Si es una coma o un punto se convierte al separador * decimal establecido en la propiedad DecSeparator */ if (m.WParam==(IntPtr) ',' || m.WParam==(IntPtr) '.') m.WParam=(IntPtr) this.DecSeparator;

this.ComprobarComa(ref values);

break; case tbFormats.SignedFloatingPointNumber: values="1234567890"+this.ControlKeys;

if (m.WParam==(IntPtr) ',' || m.WParam==(IntPtr) '.') m.WParam=(IntPtr) this.DecSeparator;

this.ComprobarComa(ref values); this.ComprobarSigno(ref values);

break; case tbFormats.UnsignedFixedPointNumber: values=this.ControlKeys;

if (m.WParam==(IntPtr) ',' || m.WParam==(IntPtr) '.') m.WParam=(IntPtr) this.DecSeparator;

this.ComprobarPosicion(ref values);

break; case tbFormats.SignedFixedPointNumber: values=this.ControlKeys;

if (m.WParam==(IntPtr) ',' || m.WParam==(IntPtr) '.') m.WParam=(IntPtr) this.DecSeparator;

this.ComprobarSigno(ref values);

this.ComprobarPosicion(ref values);

break; default: values=okValues; break; }

if (values.IndexOf((char) m.WParam)>=0) return true; else return false; }

/// <summary> /// Comprueba si el formato actual es de algn tipo de /// nmero decimal /// </summary> /// /// <returns> /// true si el formato es decimal /// false si el formato no es decimal /// </returns> private bool EsFormatoDecimal() { return ((int) this.format>=6 && (int) this.format<=9); }

/// <summary> /// Comprueba si se puede poner la coma con un formato /// de nmero decimal /// </summary> ///

/// <param name="values"> /// Cadena con los dgitos aceptados para el formato. Si /// se puede poner el separador decimal, el mtodo lo aade /// a values, que se pasa por referencia /// </param> private void ComprobarComa(ref string values) { if (base.Text.IndexOf(this.decSeparator)>=0) { if (base.SelectedText.IndexOf(this.decSeparator)>=0) values+=this.decSeparator.ToString(); } else values+=this.decSeparator.ToString(); }

/// <summary> /// Comprueba si se puede poner el singo en un formato /// numrico segn la posicin del cursor /// </summary> /// /// <param name="values"> /// Cadena con los dgitos aceptados para el formato. Si /// se puede poner el signo, el mtodo lo aade /// a values, que se pasa por referencia /// </param>

private void ComprobarSigno(ref string values) { if (base.Text.IndexOf('-')>=0) { if (base.SelectedText.IndexOf('-')>=0) values+='-'.ToString(); } else { if (base.SelectionStart==0) values+='-'.ToString(); } }

/// <summary> /// Comprueba si se puede seguir escribiendo nmeros en un /// formato FixedPointNumber segn la posicin del cursor /// </summary> /// /// <param name="values"> /// Cadena con los dgitos aceptados para el formato. Si /// se puede poner un nmero, el mtodo los aade /// a values, que se pasa por referencia /// </param> private void ComprobarPosicion(ref string values) {

if (base.Text.Length-(base.SelectionStart+base.SelectionLength)<=this.decimals) this.ComprobarComa(ref values);

int pos=base.Text.IndexOf(this.DecSeparator);

if (pos>=0) { if (base.SelectionStart>pos) { if (base.SelectionLength>0 || base.Text.Length-pos<=this.decimals) values+="0123456789"; } else values+="0123456789"; } else values+="0123456789"; }

/// <summary> /// Actualiza el separador decimal en el texto ya escrito /// </summary> private void ActualizarSeparador() { char[] s=this.Text.ToCharArray();

base.Text="";

for (int i=0; i<s.Length;i++) { if (s[i]==',' || s[i]=='.') s[i]=this.DecSeparator;

base.Text+=s[i].ToString(); } }

/// <summary> /// Cambia a base decimal un nmero escrito en cualquier base /// </summary> /// /// <param name="Base"> /// Base en la que est escrito el nmero /// </param> /// /// <returns> /// El nmero en base decimal como tipo long /// </returns> private long BaseADecimal(int Base) { char[] s=this.Text.ToUpper().ToCharArray(); long res=0;

double digito;

for (int i=s.Length-1; i>=0; i--) { try { // Si el dgito es un nmero, lo obtenemos digito=Double.Parse(s[i].ToString()); } catch { /* Si el dgito es una letra, calculamos su * valor en base decimal */ digito=(double) ((int) s[i] - (int) 'A' + 10); } res+=(long) (Math.Pow((double) Base,(double) (s.Length-1-i))* digito); }

return res; }

/// <summary> /// Cambia a cualquier base un nmero escrito en base decimal /// </summary> /// /// <param name="num">

/// nmero que hay que cambiar /// </param> /// /// <param name="Base"> /// base a la que hay que cambiar /// </param> /// /// <returns> /// cadena con el nmero en la nueva base /// </returns> private string DecimalABase(long num, int Base) { int resto=0; string res="";

do { resto=(int) (num % (long) Base);

if (resto<10) /* Si el resto es menor que 10 lo ponemos en la * cadena */ res=resto.ToString()+res; else /* Si es mayor que diez calculamos la letra que * le corresponde */

res=((char) ((int) 'A' - 10 + resto)).ToString()+res;

num/=Base;

} while (num!=0);

return res; }

#endregion

#region Propiedad Text sobreescrita (override)

/// <summary> /// Devuelve o establece el texto del control /// </summary> public override string Text { get { return base.Text; } set { /* En lugar de poner la cadena de un slo golpe se * valida uno a uno cada dgito del texto.

* As, el mtodo validar se ocupar * de filtrar slamente los dgitos vlidos segn * el formato del texto */ const int WM_CHAR=258; char[] s=value.ToCharArray(); Message m;

base.Text="";

foreach (char c in s) { m=Message.Create(this.Handle,WM_CHAR,(IntPtr) c,(IntPtr) 0); if (this.Validar(ref m)) base.Text+=c.ToString(); } } } #endregion

#region Mtodo WndProc sobreescrito (override)

/// <summary> /// Este el el mtodo que procesa los mensajes de Windows /// para el control. Aqu es donde se valida que dicho /// mensaje sea vlido segn el formato. La pulsacin se /// enviar al control solamente cuando se efecte la

/// llamada al WndProc de la clase base, que es el que /// realmente enviar el mensaje al control. En caso de /// no hacer la llamada, ser como si nunca se hubiera /// pulsado esa tecla, puesto que no se procesa. /// /// Si la pulsacin no es vlida no se produce el evento /// KeyPress. Si, a pesar de todo queremos que s se /// produzca el evento habra que sobreescribir el mtodo /// DefWndProc, en lugar de WndProc /// </summary> /// /// <param name="m"> /// mensaje que recibe del sistema operativo /// </param> protected override void WndProc(ref Message m) { /* Cuando se pulsa una tecla, Windows lanza el mensaje * WM_CHAR, cuyo valor es 258 */ const int WM_CHAR=258;

if (m.Msg==WM_CHAR) { // Se valida la pulsacin en Validar if (this.Validar(ref m)) base.WndProc(ref m);

/* En caso de que validar devolviera false no se * ejecutara ninguna llamada a base.WndProc, por * lo que la pulsacin es ignorada */ } else base.WndProc(ref m); } #endregion

#region Propiedades pblicas nuevas

/// <summary> /// Devuelve o establece el nmero de decimales para un /// formato FixedPointNumber /// </summary> public byte Decimals { get { return this.decimals; } set { this.decimals=value; } }

/// <summary> /// Devuelve o establece el carcter de separacin decimal /// </summary> public char DecSeparator { get { return this.decSeparator; } set { if (value==',' || value=='.') { this.decSeparator=value;

/* Este if comprueba si se est utilizando un * formato decimal y modifica el antiguo separador * por el nuevo en caso afirmativo */ if (this.EsFormatoDecimal()) { char[] s=this.Text.ToCharArray();

base.Text=""; for (int i=0; i<s.Length;i++) { if (s[i]==',' || s[i]=='.')

s[i]=this.DecSeparator;

base.Text+=s[i].ToString(); } } } } }

/// <summary> /// Devuelve o establece el formato de entrada en el TextBox /// El texto que tuviera el cuadro de texto se modificar /// para ajustarse al nuevo formato /// </summary> public tbFormats Format { get { return this.format; } set { this.format=value;

/* Para algunos formatos los caracteres aceptados * se determinan directamente en este switch */

switch (this.format) { case tbFormats.BynaryNumber: okValues="01"; break; case tbFormats.HexadecNumber: okValues="0123456789AaBbCcDdEeFf"; break; case tbFormats.NoSpacedAlphabetic: okValues="abcdefghijklmnopqrstuvwxyz"; okValues+=okValues.ToUpper(); break; case tbFormats.OctalNumber: okValues="01234567"; break; case tbFormats.SpacedAlphabetic: okValues="abcdefghijklmnopqrstuvwxyz"; okValues+=okValues.ToUpper()+" "; break; case tbFormats.UnsignedNumber: okValues="0123456789"; break; case tbFormats.UserDefined: okValues=userValues; break; default:

okValues=""; break; }

okValues+=this.ControlKeys;

/* Modificamos la cadena que contiene forzando la * ejecucin del bloque set de la propiedad Text * sobreescrita en esta clase */ this.Text=base.Text; } }

/// <summary> /// Devuelve o establece los dgitos vlidos en el TextBox /// cuando Format est establecido a UserDefined /// </summary> public string UserValues { get { return this.userValues; } set {

this.userValues=value;

if (this.format==tbFormats.UserDefined) this.Text=base.Text; } }

#endregion

#region Metodos nuevos de conversin del contenido

/// <summary> /// Devuelve la cadena de la propiedad Text como un valor /// de tipo double siempre que sea posible. En caso de que /// dicha cadena no se reconozca como un nmero devolver 0 /// </summary> /// <returns></returns> public double ToDouble() { switch (this.format) { case tbFormats.HexadecNumber: return (double) this.BaseADecimal(16); case tbFormats.OctalNumber: return (double) this.BaseADecimal(8); case tbFormats.BynaryNumber:

return (double) this.BaseADecimal(2); case tbFormats.SpacedAlphabetic: case tbFormats.NoSpacedAlphabetic: case tbFormats.SpacedAlphaNumeric: case tbFormats.NoSpacedAlphaNumeric: case tbFormats.UserDefined: try { return Double.Parse(this.Text); } catch { return 0D; } case tbFormats.SignedNumber: case tbFormats.UnsignedNumber: return Double.Parse(this.Text); default: char[] s=this.Text.ToCharArray(); string text="";

for (int i=0; i<s.Length; i++) { if (s[i]=='.') s[i]=','; text+=s[i].ToString();

return Double.Parse(text,System.Globalization.NumberStyles.Float); } }

/// <summary> /// Devuelve la cadena de la propiedad Text como un valor /// de tipo long siempre que sea posible. En caso de que /// la cadena no se reconozca como un nmero devolver 0 /// </summary> /// <returns></returns> public long ToInt64() { return (long) this.ToDouble(); }

/// <summary> /// Devuelve una cadena con el nmero contenido en la /// propiedad Text en formato Octal. En caso de que la /// cadena no se reconozca como un nmero devolver 0 /// </summary> /// <returns></returns> public string ToOctal() { return this.DecimalABase(this.ToInt64(),8);

/// <summary> /// Devuelve una cadena con el nmero contenido en la /// propiedad Text en formato Hexadecimal. En caso de que la /// cadena no se reconozca como un nmero devolver 0 /// </summary> /// <returns></returns> public string ToHexadecimal() { return this.DecimalABase(this.ToInt64(),16); }

/// <summary> /// Devuelve una cadena con el nmero contenido en la /// propiedad Text en formato Binario. En caso de que la /// cadena no se reconozca como un nmero devolver 0 /// </summary> /// <returns></returns> public string ToBynary() { return this.DecimalABase(this.ToInt64(),2); }

#endregion

} }

You might also like