June 2008 - Artículos

Los chicos de SecondNug han tenido la amabilidad de invitarme a participar en el evento online que tendrá lugar el próximo día 1 de julio, en el que tendremos como eje central los controles de cuadrícula de datos.

José Manuel Alarcón en la parte ASP.NET con GridView, y un servidor en la parte WindowsForms con DataGridView, abordaremos todas aquellas características y trucos que puedan resultar de interés para intentar sacar el mayor partido posible a este tipo de controles.

Para apuntarse al evento podéis utilizar el siguiente enlace.

Esperamos contar con vuestra asistencia y que el evento os resulte interesante.

Un saludo.

 

Como ya hemos tratado anteriormente en este blog, el control ModalPopupExtender, incluido en el Ajax Control Toolkit, permite la construcción de paneles con un funcionamiento semejante al de una ventana de diálogo.

Tomando como base este comportamiento, una posible finalidad a la que podríamos destinar el uso de este control consistiría en crear un cuadro de diálogo, con una funcionalidad similar a la ofrecida por la clase MessageBox de Windows Forms, o la función alert de JavaScript; es decir, presentar al usuario una ventana con un título, un texto representativo del mensaje, un icono y uno o dos botones para proporcionar la respuesta al mensaje.

Supongamos que nos encontramos desarrollando una página Web en la que se solicita al usuario un nombre y clave para realizar un proceso de identificación, pero en el caso de que los datos introducidos no sean correctos, necesitamos mostrar un pequeño aviso en forma de cuadro de diálogo informativo del problema ocurrido.

Para el código de marcado correspondiente al formulario de introducción de los datos emplearemos el siguiente código fuente.

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div>
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <table border="2">
                <tr>
                    <td colspan="2" align="center">
                        <asp:Label ID="Label1" runat="server" Text="Datos de acceso" Font-Bold="True" Font-Underline="True" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:Label ID="Label2" runat="server" Text="Nombre:" />
                    </td>
                    <td>
                        <asp:TextBox ID="txtNombre" runat="server" />
                    </td>
                </tr>
                <tr>
                    <td>
                        <asp:Label ID="Label3" runat="server" Text="Clave:" />
                    </td>
                    <td>
                        <asp:TextBox ID="txtClave" runat="server" />
                    </td>
                </tr>
                <tr>
                    <td colspan="2" align="center">
                        <asp:Button ID="btnEntrar" runat="server" Text="Entrar" Style="margin: 5px;" OnClick="btnEntrar_Click" />
                        <br />
                        <asp:Label ID="lblAccesoPermitido" runat="server" Text="Acceso permitido" Visible="False" />
                    </td>
                </tr>
            </table>
        </ContentTemplate>
    </asp:UpdatePanel>        
</div>
</form>

Cabe destacar el control Label lblAccesoPermitido, que inicialmente no se visualiza, ya que será utilizado para mostrar un literal al usuario cuando haya introducido correctamente los datos de acceso. También es importante señalar los elementos ScriptManager y UpdatePanel añadidos a la página, que nos servirán para dotar a esta de la funcionalidad AJAX necesaria en el ejemplo.

En tiempo de ejecución, esta página Web mostrará el siguiente aspecto.

El code-behind correspondiente al botón btnEntrar es el siguiente.

protected void btnEntrar_Click(object sender, EventArgs e)
{
    // si los datos de acceso son correctos
    // se muestra el literal que indica dicho estado
    if (this.txtNombre.Text == "Elena" && this.txtClave.Text == "123")
    {
        this.lblAccesoPermitido.Visible = true;
    }
}

En lo que respecta a la caja de mensajes, su composición estará formada por un conjunto de controles Button, Label, Image, etc., contenidos dentro de un Panel, al que aplicaremos un estilo para mejorar su presentación.

<style type="text/css">
    .CajaDialogo
    {
        background-color: #99ffcc;
        border-width: 4px;
        border-style: outset;
        border-color: Yellow;
        padding: 0px;
        width: 275px;
        font-weight: bold;
        font-style: italic;
    }
    .CajaDialogo div
    {
        margin: 7px;
        text-align: center;
    }
   ….
   ….
</style>
<asp:Panel ID="pnlMensaje" runat="server" CssClass="CajaDialogo" style="display: none;">
    <table border="0" width="275px" 
        style="margin: 0px; padding: 0px; background-color: #0033CC; color: #FFFFFF;">
        
        <tr>
            <td align="center">
                <asp:Label ID="Label5" runat="server" Text="¡ Atención !" />
            </td>
            <td width="12%">
                <asp:ImageButton ID="btnCerrar" runat="server" ImageUrl="Cerrar.jpg" 
                    Style="vertical-align: top;" ImageAlign="Right" />
            </td>
        </tr>
    </table>
    <div>
        <asp:Image ID="imgIcono" runat="server" ImageUrl="Exclama.jpg" BorderColor="Black"
            BorderStyle="Solid" BorderWidth="1px" ImageAlign="Middle" />
        &nbsp;&nbsp;
        <asp:Label ID="Label7" runat="server" Text="Datos de usuario incorrectos" />
    </div>
    <div>
        <asp:Button ID="btnAceptar" runat="server" Text="Aceptar" />
    </div>
</asp:Panel>

En la siguiente figura podemos apreciar el aspecto de nuestra caja de mensajes.

A continuación añadiremos al formulario Web el control ModalPopupExtender, que será el encargado de visualizar el Panel anterior con el comportamiento de una caja modal de mensajes.

<cc1:ModalPopupExtender ID="mpeMensaje" runat="server" TargetControlID="btnEntrar"
    PopupControlID="pnlMensaje" 
    BackgroundCssClass="FondoAplicacion" 
    OkControlID="btnAceptar"
    OnOkScript="mpeMensajeOnOk()" />

En caso de que pulsemos el botón btnAceptar del cuadro de diálogo, se ejecutará la siguiente función JavaScript, que está asociada a dicho botón mediante la propiedad OnOkScript del ModalPopupExtender. La tarea de dicha función consistirá en borrar el contenido de los TextBox de la página, situando el foco en el primero de ellos.

<script language="javascript" type="text/javascript">
function mpeMensajeOnOk() { var txtNombre = document.getElementById("txtNombre"); var txtClave = document.getElementById("txtClave"); txtNombre.value = ""; txtClave.value = ""; txtNombre.focus(); }

</script>

Si por el contrario pulsamos el ImageButton btnCerrar -que simula el botón de cierre de una ventana-, el diálogo se cerrará sin realizar acción alguna.

Mientras que la caja de mensajes se visualice, el resto de elementos de la página estarán deshabilitados; para que visualmente esto se perciba mejor, vamos a crear un estilo que asignaremos a la propiedad BackgroundCssClass del extensor. Como resultado, el control ModalPopupExtender aplicará este estilo a todos los elementos de la página que no sean los correspondientes al Panel.

<style type="text/css">
    ….
    ….
    .FondoAplicacion
    {
        background-color: Gray;
        filter: alpha(opacity=70);
        opacity: 0.7;
    }
</style>
 
Una vez terminado todo este trabajo ya podemos poner en ejecución nuestro programa. Sin embargo vamos a encontrarnos con el siguiente problema: cuando pulsemos el botón btnEntrar, el ModalPopupExtender siempre va a mostrar el cuadro de mensajes, aunque los datos de acceso sean correctos y no deba por tanto visualizarse el cuadro modal. Este comportamiento es motivado por el hecho de haber asignado el ID del botón a la propiedad TargetControlID del ModalPopupExtender.
 
<asp:Button ID="btnEntrar" runat="server" Text="Entrar" Style="margin: 5px;" OnClick="btnEntrar_Click" />
....
....
<cc1:ModalPopupExtender ID="mpeMensaje" runat="server" TargetControlID="btnEntrar"

Como ya comentamos al comienzo de este artículo, lo deseable sería que el diálogo se mostrara solamente cuando los valores de los TextBox no cumplan la condición especificada en el code-behind asociado al botón. Para conseguirlo vamos a recurrir a un pequeño truco, consistente en asignar a la propiedad TargetControlID un control con el cual el usuario no pueda interaccionar, de manera que será nuestro code-behind quien controle cuándo visualizar el mensaje.

Primeramente añadiremos al formulario Web un control Label, que permanecerá oculto mediante la asignación del estilo "display: none".

<asp:Button ID="btnEntrar" runat="server" Text="Entrar" Style="margin: 5px;" OnClick="btnEntrar_Click" />

<asp:Label ID="lblOculto" runat="server" Text="Label" Style="display: none;" />

A continuación asignaremos este control a la propiedad TargetControlID.

<cc1:ModalPopupExtender ID="mpeMensaje" runat="server" TargetControlID="lblOculto"
....

Con esto conseguiremos que la pulsación del botón btnEntrar no sea la que produzca la apertura del cuadro de diálogo, ¡pero todavía necesitamos seguir abriéndolo!, aunque claro está, de forma controlada por nosotros, y para ello vamos a recurrir a un método del ModalPopupExtender cuyo nombre lo dice todo: Show.

Empleado desde el evento Click del control btnEntrar, el método Show abrirá el cuadro de mensaje solamente cuando los datos introducidos por el usuario no se ajusten a los especificados.

protected void btnEntrar_Click(object sender, EventArgs e)
{
    // si los datos de acceso son correctos
    // se muestra el literal que indica dicho estado
    if (this.txtNombre.Text == "Elena" && this.txtClave.Text == "123")
    {
        this.lblAccesoPermitido.Visible = true;
    }
    else
    {
        // si los datos de acceso no son válidos
        // mostrar el cuadro de mensaje
        this.mpeMensaje.Show();
    }
}

En la siguiente figura podemos observar nuestra caja de mensajes en plena ejecución.

Y ya para finalizar, queremos remarcar el hecho de haber utilizado el estilo "display: none" para ocultar el control lblOculto, en detrimento de la propiedad Visible a False, ya que en el caso de haber utilizado esta última opción, el efecto conseguido habría sido que el cuadro de mensaje no se hubiera visualizado, aun cuando se cumplieran las condiciones adecuadas para ello.

Esperando que os resulte de utilidad, el código fuente del ejemplo queda disponible en los siguientes enlaces: C# y VB.

Un saludo.

Publicado por Luis Miguel Blanco | 20 comment(s)
Archivado en: ,,

En la primera entrega  de este artículo mostrábamos una sencilla técnica en el uso del control MaskedTextBox, consistente en completar un número con ceros a la izquierda. En esta segunda parte vamos a complicar la estrategia en la introducción del valor en la caja de texto, ya que será en el momento en el que el usuario vaya escribiendo cuando tendremos que ir añadiendo dinámicamente los ceros a la izquierda del número, dotándole de un comportamiento similar en algunos aspectos al de la calculadora.

La solución que proponemos mediante esta técnica pasa por la manipulación de los eventos de teclado que recibe el control: KeyDown, KeyPress y KeyUp; donde será nuestro código el encargado de calcular la longitud en dígitos del número introducido, y añadir ceros a la izquierda hasta completar la longitud de la máscara de entrada.

En primer lugar, al igual que hicimos en la entrega anterior, durante la carga del formulario estableceremos en el control MaskedTextBox la máscara de entrada para que acepte valores numéricos, mientras que el carácter marcador de dicha entrada será el cero.

private void frmDuranteEdicion_Load(object sender, EventArgs e)
{
    this.maskedTextBox1.Mask = "99999";
    this.maskedTextBox1.PromptChar = '0';
}

Seguidamente escribiremos en el manipulador del evento Enter del control -desencadenado cuando este obtiene el foco- el código necesario para situar el cursor de escritura al final del texto, ya que esto facilitará la introducción y el formateo dinámico del número a la hora de añadirle los ceros a la izquierda.

private void maskedTextBox1_Enter(object sender, EventArgs e)
{
    SendKeys.Send("{END}");
}

A continuación pasaremos al primero de los eventos de teclado que se producen: KeyDown. Aquí debemos inhabilitar las pulsaciones que puedan interferir en la correcta introducción de los valores en el MaskedTextBox. Dichas teclas corresponderían a Inicio, Izquierda, Arriba y Abajo; y para deshabilitarlas comprobaremos el valor de la propiedad KeyEventArgs.KeyCode que recibe este evento, el cual contiene un elemento de la enumeración Keys. En caso de que haya coincidencia con alguna de las teclas mencionadas, asignaremos true a la propiedad KeyEventArgs.SupressKeyPress, que como su nombre indica, permite suprimir la pulsación de tecla efectuada, evitando que llegue al controlador de teclado.

Cuando manipulamos una pulsación de teclado para personalizar algún tipo de comportamiento en la edición de una caja de texto, tal y como hemos hecho ahora, habitualmente debemos asignar el valor true a la propiedad KeyEventArgs.Handled, para que el entorno de ejecución sepa que hemos alterado el funcionamiento normal de este evento. Sin embargo, en este caso en particular no es necesario, ya que la asignación que hemos realizado a la propiedad KeyEventArgs.SupressKeyPress se encarga de establecer el valor correcto en Handled.

private void maskedTextBox1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.KeyCode == Keys.Left | e.KeyCode == Keys.Home | 
        e.KeyCode == Keys.Up | e.KeyCode == Keys.Down )
    {
        e.SuppressKeyPress = true;
    }
}

Nuestro siguiente paso nos lleva al evento KeyPress, el cual nos permite saber, a través de su parámetro KeyPressEventArgs, el carácter correspondiente a la tecla pulsada. La mecánica seguida en este evento -que podemos ver esquemáticamente en la próxima figura-será la siguiente: puesto que estamos introduciendo los caracteres tecleados desde el final de la caja de texto, necesitamos comprobar si a la izquierda del número todavía hay ceros; en caso afirmativo, podemos añadir un nuevo valor al control, pero antes debemos comprobar que la tecla pulsada corresponde a un número, e insertarlo al final del cuadro de texto, desplazando el resto de valores y eliminando el primer cero a la izquierda existente.

La operación más inmediata que posiblemente pensaríamos llevar a cabo sería obtener el contenido de la propiedad Text del MaskedTextBox, para comprobar el valor del primer elemento de la cadena. Sin embargo, debemos tener en cuenta que la configuración de máscara -propiedad TextMaskFormat con el valor IncludeLiterals-que estamos usando con el control, no incluye los caracteres de marcador de entrada de valores, por lo que los ceros que son visualizados a la izquierda del número que introduzcamos, no formaran realmente parte del texto contenido en el control.

Para evitar este problema vamos a hacer uso de un objeto MaskedTextProvider, que obtenemos de la propiedad del mismo nombre existente en el control. La clase MaskedTextProvider proporciona un conjunto de miembros que nos van a permitir manejar con mayor flexibilidad el valor contenido en la caja de texto.

Por ejemplo, el método MaskedTextProvider.ToDisplayString sí que devuelve el texto al completo que visualizamos en el control, por lo que se convierte en el candidato perfecto para comprobar si el primer elemento es un cero; en caso afirmativo, usaremos el método MaskedTextProvider.VerifyChar para verificar si el carácter que pretendemos insertar en la última posición del control cumple con los requisitos de la máscara de entrada, en nuestro caso que sea un dato numérico.

VerifyChar devuelve un tipo bool, que nos informa si el carácter es válido para ser insertado en la posición indicada, pero a veces eso no resulta suficiente, y por ese motivo, el último parámetro -pasado por referencia- es un tipo enumerado MaskedTextResultHint, que contendrá información adicional acerca del resultado de este método.

Si el valor introducido es adecuado, volcaremos el texto del control en una colección Queue<char>, que representa una lista de tipo FIFO, para poder, de un modo más cómodo, quitar el primer elemento y añadir al final el que acaba de ser introducido por el usuario; volviendo a traspasar la colección al control.

private void maskedTextBox1_KeyPress(object sender, KeyPressEventArgs e)
{
    MaskedTextProvider mtpMskTxtPrv = this.maskedTextBox1.MaskedTextProvider;

    if (mtpMskTxtPrv.ToDisplayString().Substring(0, 1) == mtpMskTxtPrv.PromptChar.ToString())
    {
        MaskedTextResultHint mtrhResultado;

        if (mtpMskTxtPrv.VerifyChar(e.KeyChar, mtpMskTxtPrv.Mask.Length - 1, out mtrhResultado))
        {
            Queue<char> queNumero = new Queue<char>(mtpMskTxtPrv.ToDisplayString().ToCharArray());
            queNumero.Dequeue();
            queNumero.Enqueue(e.KeyChar);

            this.maskedTextBox1.Text = new string(queNumero.ToArray());
        }
    }
}

Para concluir con el proceso debemos controlar, en el evento KeyUp del MaskedTextBox, las pulsaciones de las teclas Suprimir y Retroceso, de forma que eliminen el último dígito existente en la caja de texto del control.

Volveremos, en esta ocasión, a utilizar el objeto MaskedTextProvider, llamando a su método ToDisplayString para obtener la cadena contenida en el control, de la que eliminaremos el último carácter y rellenaremos con espacios a la izquierda, todo ello en la misma línea de código. La forma de obtener la posición a borrar y la longitud que debe tener la cadena resultante la averiguaremos mediante la propiedad Mask.Length del control.

Por otro lado, si borramos todos los números y nos quedamos solamente con los ceros marcadores de entrada, lo que sucederá es que la caja de texto estará realmente vacía. Para obligar a que el cursor de escritura se mantenga al final del control aunque pulsemos la tecla Retroceso, enviaremos al controlador de teclado una pulsación de la tecla Fin mediante la clase SendKeys.

private void maskedTextBox1_KeyUp(object sender, KeyEventArgs e)
{
    MaskedTextProvider mtpMskTxtPrv = this.maskedTextBox1.MaskedTextProvider;

    if (e.KeyCode == Keys.Delete | e.KeyCode == Keys.Back)
    {
        this.maskedTextBox1.Text = mtpMskTxtPrv.ToDisplayString().Remove(
            mtpMskTxtPrv.Mask.Length - 1, 1).PadLeft(mtpMskTxtPrv.Mask.Length);

        if (this.maskedTextBox1.Text.Length == 0)
        {
            SendKeys.Send("{END}");
        }
    }

    this.lblNumero.Text = this.maskedTextBox1.MaskedTextProvider.ToDisplayString();
}

En todo momento visualizaremos el valor completo introducido mediante un control Label, al que asignaremos el resultado del método MaskedTextProvider.ToDisplayString.

La siguiente figura muestra el control en ejecución.

Y con esto concluimos la segunda entrega de este artículo dedicado a la manipulación del control MaskedTextBox, de la que también tenemos el código fuente disponible en los siguientes enlaces  C# y VB.

Espero que os resulte de utilidad.

Un saludo.

Publicado por Luis Miguel Blanco | 8 comment(s)
Archivado en: ,

Tal y como reza el título de este artículo, durante el desarrollo de una aplicación Windows Forms puede haber ocasiones en las que necesitemos que una caja de texto admita solamente valores numéricos, lo cual podemos resolver muy fácilmente mediante un control MaskedTextBox sobre el que establezcamos la máscara de entrada apropiada; pero como requerimiento adicional, supongamos que dicho número debe tener una cantidad determinada de dígitos, que si no son introducidos por el usuario, deberán ser completados con ceros a la izquierda del número significativo.

Para cumplir con este requisito vamos a proponer dos técnicas, que variarán en función de la necesidad de visualizar en el control MaskedTextBox el valor del número completamente formateado durante su fase de edición.

En ambos casos estableceremos la máscara de entrada -propiedad Mask-del control a la cadena "99999", para que admita solamente números; esta operación podemos hacerla desde el asistente que incorpora el control en su etiqueta inteligente, como vemos en la siguiente figura.

O bien mediante código, por ejemplo, en el evento Load del formulario.

private void frmSalirFoco_Load(object sender, EventArgs e)
{
    this.maskedTextBox1.Mask = "99999";
}

En esta primera entrega del artículo explicaremos cómo completar el contenido del control cuando este pierda el foco, mediante una técnica consistente en aplicar el cero como carácter marcador sobre el MaskedTextBox, excepto durante el tiempo en el que el usuario se encuentre editando su valor.

Como primer paso, en el evento Load del formulario, además de asignar la cadena de máscara del modo anteriormente mencionado, estableceremos el cero como marcador mediante la propiedad PromptChar.

private void frmSalirFoco_Load(object sender, EventArgs e)
{
    this.maskedTextBox1.Mask = "99999";
    this.maskedTextBox1.PromptChar = '0';
}

Seguidamente pasaremos al momento en que el control recibe el foco, situación esta que podemos manipular mediante el evento Enter. Para facilitar la edición por parte del usuario, en el manipulador de este evento asignaremos el guión bajo ( _ ) como carácter marcador de posición, y eliminaremos los posibles espacios en blanco que pudiera tener el control, para así dejar solamente el valor real del número a editar.

private void maskedTextBox1_Enter(object sender, EventArgs e)
{
    this.maskedTextBox1.PromptChar = '_';
    this.maskedTextBox1.Text = this.maskedTextBox1.Text.TrimStart();
}

Al concluir la edición y pasar el foco a otro control se producirá el evento Leave sobre el MaskedTextBox. En el manipulador de este evento reasignaremos a la propiedad Text del control el valor que actualmente tiene, pero aplicando a dicha propiedad el método PadLeft, para que la rellene con espacios en blanco. Como la longitud total de la cadena -incluyendo los espacios y el número- debe ser igual a la longitud de la máscara de entrada del control, pasaremos como parámetro a PadLeft la propiedad MaskedTextBox.Mask.Length; por lo que el control, como consecuencia de esta asignación, sustituirá los espacios en blanco por el marcador establecido en la propiedad PromptChar, que asignaremos en la siguiente línea de código. A continuación se muestra el código que debemos escribir en la clase del formulario para conseguir el resultado descrito mediante esta técnica.

private void maskedTextBox1_Leave(object sender, EventArgs e)
{
    this.maskedTextBox1.Text = this.maskedTextBox1.Text.PadLeft(this.maskedTextBox1.Mask.Length);
    this.maskedTextBox1.PromptChar = '0';
}

En la siguiente figura podemos apreciar los distintos estados que va tomando el control: antes de su edición visualiza ceros; al entrar a editar, escribimos un número, mostrándose guiones bajos para aquellas posiciones no editadas; y al salir, en el caso de que no hayamos completado todos los dígitos posibles, se rellenarán con ceros a la izquierda aquellas posiciones vacías.

Los proyectos conteniendo el código de los ejemplos de las dos entregas del artículo están disponibles en los siguientes enlaces para C# y VB.

Espero que os sirva de ayuda.

Un saludo.

 

Publicado por Luis Miguel Blanco | 5 comment(s)
Archivado en: ,