Salir del foco en un control MaskedTextBox vacío

Cuando necesitamos guiar al usuario en la introducción de una fecha dentro de un formulario, una buena opción puede ser recurrir al control MaskedTextBox. El modo de configurar este control, para que los valores admisibles sean solamente numéricos y en una determinada secuencia, pasa por asignar una cadena de formato o máscara a su propiedad Mask. No obstante esto no es suficiente, ya que podríamos introducir números que finalmente no produjeran un valor de fecha coherente. Por ello, y como forma de asegurarnos que el valor tecleado es verdaderamente una fecha, podemos asignar el tipo de dato a validar en la propiedad ValidatingType.


 


private void Form1_Load(object sender, EventArgs e)
{
// establecer configuración inicial del MaskedTextBox
this.maskedTextBox1.ValidatingType = typeof(DateTime);
this.maskedTextBox1.Mask = «00-00-0000»;
}

El uso de la propiedad ValidatingType de forma aislada no significa que el control MaskedTextBox vaya a avisarnos él solito cuando el usuario escribe un valor que no corresponde a una fecha. Es el programador quien debe codificar esta lógica de validación, ya que la asignación de un valor a la mencionada propiedad, conlleva que debemos escribir un manipulador para el evento TypeValidationCompleted, donde comprobaremos, utilizando el parámetro TypeValidationEventArgs.IsValidInput, si el contenido del control corresponde al valor para el que hemos establecido la validación,. Si esta propiedad devuelve false, podemos asignar true a la propiedad TypeValidationEventArgs.Cancel, forzando a que el foco permanezca en el control hasta que el usuario no escriba una fecha correcta.


 


private void maskedTextBox1_TypeValidationCompleted(object sender, TypeValidationEventArgs e)
{
// si hay valor en el control, comprobar si es una fecha válida
if (!e.IsValidInput)
{
this.errorProvider1.SetError(this.maskedTextBox1, «Fecha incorrecta»);
e.Cancel = true;
}
else
{
this.errorProvider1.Clear();
}
}

 



Pero supongamos que la fecha no es un dato obligatorio en nuestro formulario, en ese caso esta técnica de validación puede ser un arma de doble filo, ya que el usuario, una vez que haya entrado en el control, no podrá abandonarlo dejándolo vacío; está obligado a escribir una fecha válida.


Una forma de solucionar el problema consistiría en comprobar, al comienzo del evento TypeValidationCompleted, si el control está vacío, y en caso afirmativo, salir de la ejecución del evento sin realizar las comprobaciones de fecha. El código sería similar al siguiente.


 


private void maskedTextBox1_TypeValidationCompleted(object sender, TypeValidationEventArgs e)
{
// si no hay valor en el control, salir
if (this.maskedTextBox1.Text.Length == 0)
{
return;
}
//….

A priori parece correcto, pero sigue sin funcionar, ya que aunque no hayamos tecleado valor alguno en el MaskedTextBox, su propiedad Text devuelve el valor correspondiente a 6 caracteres -los espacios en blanco y guiones de separación de fecha- fruto de la máscara establecida al control.


Para evitar que se tengan en cuenta estos valores, debemos excluirlos antes de comprobar el contenido del control utilizando su propiedad TextMaskFormat y uno de los valores de la enumeración MaskFormat de la siguiente manera.


 


this.maskedTextBox1.TextMaskFormat = MaskFormat.ExcludePromptAndLiterals;

Ahora ya podemos dejar vacío el control, pero nos encontramos con un nuevo inconveniente, porque resulta que si escribimos una fecha correcta, el evento TypeValidationCompleted la considera incorrecta. Misterio 8-¿?.


La respuesta a este comportamiento la hallamos en la asignación que acabamos de hacer a la propiedad TextMaskFormat del control, ya que a partir de ese momento, si escribimos por ejemplo un valor como «25-08-2004», la propiedad Text contendrá realmente el valor «25082004», que el control no puede convertir a fecha.


¿Cómo evitamos esta nueva contrariedad?, pues muy sencillo, una vez que en el evento TypeValidationCompleted hayamos comprobado si el control está o no vacío, restauramos el valor original de la propiedad TextMaskFormat, que por defecto es IncludeLiterals.


El modo de conseguir este objetivo pasa por utilizar una variable con ámbito a nivel de la clase del formulario, en la que volcamos el valor del formato de máscara original al entrar en el evento TypeValidationCompleted, y lo restauramos en el evento Validating, que se produce a continuación del primero. El código al completo, incluyendo estas últimas modificaciones, podemos verlos a continuación.


 


public partial class Form1 : Form
{
MaskFormat mfMaskFormat;

//….

private void Form1_Load(object sender, EventArgs e)
{
// establecer configuración inicial del MaskedTextBox
this.maskedTextBox1.ValidatingType = typeof(DateTime);
this.maskedTextBox1.Mask = «00-00-0000»;
}

private void maskedTextBox1_TypeValidationCompleted(object sender, TypeValidationEventArgs e)
{
// guardar el formato original de máscara del control
mfMaskFormat = this.maskedTextBox1.TextMaskFormat;

// establecer formato de máscara que elimine todos los caracteres
// excepto los de la propia fecha
this.maskedTextBox1.TextMaskFormat = MaskFormat.ExcludePromptAndLiterals;

// si no hay valor en el control, salir
if (this.maskedTextBox1.Text.Length == 0)
{
return;
}

// si hay valor en el control, comprobar si es una fecha válida
if (!e.IsValidInput)
{
this.errorProvider1.SetError(this.maskedTextBox1, «Fecha incorrecta»);
e.Cancel = true;
}
else
{
this.errorProvider1.Clear();
}
}

private void maskedTextBox1_Validating(object sender, CancelEventArgs e)
{
// restaurar la máscara de formato original
this.maskedTextBox1.TextMaskFormat = mfMaskFormat;
}
}


El código fuente de este artículo puede descargarse en los siguiente enlaces: C# y VB.


Espero que os pueda resultar de utilidad.


Un saludo.

10 Comentarios

  1. anonymous

    Alguna vez lo probaste con un maskedText bindeado??…porque no me funciona RCTM.

  2. lmblanco

    Hola Polizont

    Sí en efecto, cuando aplicas DataBinding sobre un control MaskedTextBox, este comportamiento descrito en el post no funciona correctamente, ya que el BindingSource debe añadir validaciones internas adicionales.

    Un saludo.

  3. anonymous

    Aunque soy newbie, veo el ejemplo como muy bueno. Tengo una situación en la que tomo un nvarchar de un archivo de parámetros y lo pongo en un MaskedTextBox con formato numérico 9999. Cuando hago el Fill, todo ok, muestra correctamente el número. Pero cuando hago el EndEdit, aún sin haber editado el MaskedTextBox, obtengo una excepción de intento de inserción DBNull en la base de datos. Me resulta extraño, porque entiendo que la propiedad que se utiliza es MaskedTextBox.Text
    Gracias desde ya y saludos.

  4. lmblanco

    Hola Víctor

    Si este proceso que me comentas lo haces utilizando data binding automático debes tener en cuenta que el enlace automático de datos añade código propio que pudiera estar interfiriendo en tu actualización de datos.

    Si se trata de un proceso de actualización de datos con operaciones en las que necesites tener un mayor control de la situación, quizá sería más conveniente que te plantearas realizar la actualización de forma manual.

    Un saludo,
    Luismi

  5. anonymous

    Muy provechoso tu articulo…

    Una Preguntica.. si se quere crear una clase de tipo Maskedtextbox para encapsular esta funcionalidad como podria funcionar con mfMaskFormat si este es global para el formulario o al menos como podra declarar esta variable localmente .. Muchas gracias

  6. lmblanco

    Hola Carlos

    Gracias por tu interés en el artículo.

    Para incorporar esta funcionalidad en una aplicación, una vez creada una clase que herede de MaskedTextBox, sería necesario implementar nuestro propio evento TypeValidationComplete. En el siguiente enlace tienes más información acerca de este evento y las situaciones en que se produce.

    http://msdn.microsoft.com/es-es/library/system.windows.forms.maskedtextbox.typevalidationcompleted.aspx

    Un saludo.
    Luismi

  7. anonymous

    podrias explicarlo mejor… o com mas detalle si hay q agregar alguna libreria o algo…

    pq para vb 2005 el this. no lo acepta
    gracias.. y disculpa por si el comentario no aplica

  8. lmblanco

    Hola Dana

    Los bloques de código fuente de ejemplo que aparecen en este post están escritos en C#, de ahí que si intentas pegarlos en un proyecto VB te esté apareciendo un error.

    Al final del texto del post tienes dos enlaces con los ejemplos para cada lenguaje, descarga los correspondientes a VB y te deberían funcionar en un proyecto VB.

    Un saludo.
    Luismi

  9. corsario007

    Excelente ayuda, no tenia clara la funcionalidad del MaskFormat.ExcludePromptAndLiterals ya que estaba tratando de hacer la validacion con error provider y me hacia nudos…. gracias mil

  10. ulises gomez bonilla

    Amigo muchas gracias me ha servido de mucho tu publicacion. Saludos bro que estes bien (Y).

Responder a Cancelar respuesta

Tema creado por Anders Norén