January 2008 - Artículos

Una situación con la que podemos encontrarnos frecuentemente consiste en añadir programáticamente texto a un control TextBox, de tipo multilínea más concretamente. Supongamos que al mismo tiempo que vamos realizando esta inserción, queremos que el ScrollBar del control se sitúe al final de su recorrido, de forma que el usuario pueda visualizar el texto recién añadido. Si optamos por concatenar el texto a la propiedad TextBox.Text de la siguiente manera.

 

private void btnAgregarTexto_Click(object sender, EventArgs e)
{
    this.textBox1.Text += "este es un fragmento de texto añadido al control";
}

El texto se añadirá al final del cuadro de texto, que es lo lógico, pero el ScrollBar se posicionará al comienzo, mostrando el principio del texto existente en el control, que no es el comportamiento que necesitamos.

 

Para conseguir nuestro propósito podemos utilizar el método AppendText del TextBox, que además de añadir el texto pasado como parámetro, nos posiciona al final del contenido del control, al igual que su ScrollBar, visualizando el texto que acabamos de añadir por código.

 

private void btnAgregarTexto_Click(object sender, EventArgs e)
{
    this.textBox1.AppendText("este es un fragmento de texto añadido al control");
}

 

Espero que os pueda resultar de utilidad.

Un saludo.

 

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

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.

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

Un medio alternativo de configurar la presentación visual

A pesar de haber finalizado el desarrollo del ejemplo en la entrega anterior de este artículo, y tras conseguir la funcionalidad pretendida, en esta última parte queremos explorar un medio adicional que nos permita establecer la configuración de las propiedades visuales de los LinkButton que conforman los elementos del control DropDownExtender.

En su estado actual, para conseguir que los elementos de la lista proporcionen el grado de interactividad visual necesaria con el usuario, emitimos desde el código servidor de la página -evento Load- un bloque de código javascript hacia el cliente, que asociamos con los eventos onmouseover y onmouseout de las etiquetas <a>, las cuales son el resultado en HTML generado por ASP.NET hacia el navegador para los controles LinkButton.

 

//....
System.Text.StringBuilder sbScript = new System.Text.StringBuilder();

sbScript.Append("function LinkButton_onmouseover(oLnkBtn) \n");
sbScript.Append("{ \n");
sbScript.Append("oLnkBtn.style.backgroundColor = 'LightSkyBlue'; \n");
sbScript.Append("oLnkBtn.style.border = '1px solid DarkBlue'; \n");
sbScript.Append("oLnkBtn.style.padding = '3px 18px 3px 32px'; \n");
sbScript.Append("} \n");
sbScript.Append("function LinkButton_onmouseout(oLnkBtn) \n");
sbScript.Append("{ \n");
sbScript.Append("oLnkBtn.style.backgroundColor = 'LightGreen'; \n");
sbScript.Append("oLnkBtn.style.borderStyle = 'none'; \n");
sbScript.Append("oLnkBtn.style.padding = '4px 19px 4px 33px'; \n");
sbScript.Append("} \n");

this.ClientScript.RegisterClientScriptBlock(this.GetType(),
  "CodigoJS", sbScript.ToString(), true);

//....
oLinkButton.Attributes.Add("onmouseover", "LinkButton_onmouseover(this)");
oLinkButton.Attributes.Add("onmouseout", "LinkButton_onmouseout(this)");

Además de lo anterior, para que los elementos de la lista -al ser esta desplegada- se muestren con un aspecto inicial adecuado, en la propiedad-colección Style de los LinkButton asignaremos un conjunto de valores necesario para este control.

 

// configurar aspecto visual base del elemento de lista
oLinkButton.Style[HtmlTextWriterStyle.TextDecoration] = "none";
oLinkButton.Style[HtmlTextWriterStyle.Display] = "block";
oLinkButton.Style[HtmlTextWriterStyle.Cursor] = "default";
oLinkButton.Style[HtmlTextWriterStyle.BackgroundColor] = "LightGreen";
oLinkButton.Style[HtmlTextWriterStyle.Color] = "DarkBlue";
oLinkButton.Style[HtmlTextWriterStyle.Padding] = "4px 19px 4px 33px";

Observando los fragmentos de código anteriores vemos que algunas propiedades de estilo, como por ejemplo BackgroundColor, son asignadas dos veces: una primera para establecer la apariencia inicial del elemento de la lista y otra para el evento cliente onmouseout. Esta situación nos lleva a investigar una vía por la cual podamos optimizar y centralizar esta configuración en un único punto de nuestro código. Como resultado de nuestras pesquisas conseguiremos un doble objetivo: mejorar el código del ejemplo actual y obtener una variante para abordar este problema en futuros desarrollos, que nos permita disponer de soluciones alternativas. Antes de proseguir, vamos a eliminar del código de la aplicación estos fragmentos de código anteriormente mencionados, para dejar la configuración visual de los LinkButton en su estado inicial.

Podemos mejorar el código de nuestro ejemplo en este aspecto utilizando un bloque de estilos para las etiquetas <a>  como el mostrado a continuación.

 

<head runat="server">
    <title>Cargar datos en DropDownExtender</title>
    
    <style type='text/css'> 
        a
        { 
            color : DarkBlue;
            background-color: LightGreen; 
            border-style : none;
            display : block; 
            text-decoration : none;            
            cursor : default;
            padding : 4px 19px 4px 33px;
        } 
        a:hover 
        { 
            background-color: LightSkyBlue;
            border : 1px solid DarkBlue;
            padding : 3px 18px 3px 32px;            
        } 
    </style> 
</head>

Con esta sencilla modificación, los elementos del DropDownExtender se mostrarían al usuario con las características visuales que ya habíamos implementado anteriormente, pero utilizando una menor cantidad de código.

Peeeeero ¡¡cuidadín!! =8-O, que como efecto colateral, cualquier otro control basado en la etiqueta <a> que tengamos en nuestra página también utilizará este estilo, con las consecuencias no deseadas que de esto se deriva; como vemos en la siguiente imagen, donde el enlace "Mantenimiento de clientes" se muestra igual que los elementos de la lista desplegable.

 

Para evitar este problema asignaremos un nombre a los estilos definidos, estableciendo el mismo nombre en la propiedad CssClass de los controles LinkButton creados para la lista del DropDownExtender, como vemos a continuación.

 

<head runat="server">
    <title>Cargar datos en DropDownExtender</title>
    
    <style type='text/css'> 
        a.ElementoLista
        { 
            color : DarkBlue;
            background-color: LightGreen; 
            border-style : none;
            display : block; 
            text-decoration : none;            
            cursor : default;
            padding : 4px 19px 4px 33px;
        } 
        a.ElementoLista:hover 
        { 
            background-color: LightSkyBlue;
            border : 1px solid DarkBlue;
            padding : 3px 18px 3px 32px;            
        } 
    </style> 
</head>

 

protected void Page_Load(object sender, EventArgs e)
{
    //....
    oLinkButton.CssClass = "ElementoLista";
    //....
}

De modo análogo a como hacíamos con los eventos del lado cliente del control LinkButton, en lugar de situar el código de los estilos en el HTML de la página, podemos emitirlo desde el lado servidor empleando igualmente el método Page.ClientScriptRegisterClientScriptBlock. Dado que no se trata de código javascript, debemos pasar el valor false como último parámetro del método, para que omita la generación de la etiqueta <script>.

 

protected void Page_Load(object sender, EventArgs e)
{
    // emitir código cliente para crear los estilos
    // que relacionaremos con los elementos de la lista desplegable
    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    sb.Append("<style type='text/css'> \n");
    sb.Append("a.ElementoLista \n");
    sb.Append("{ \n");
    sb.Append("color : DarkBlue; \n");
    sb.Append("background-color: LightGreen; \n");
    sb.Append("border-style : none; \n");
    sb.Append("display : block; \n");
    sb.Append("text-decoration : none; \n");
    sb.Append("cursor : default; \n");
    sb.Append("padding : 4px 19px 4px 33px; \n");
    sb.Append("} \n");
    sb.Append("a.ElementoLista:hover \n");
    sb.Append("{ \n");
    sb.Append("background-color: LightSkyBlue; \n");
    sb.Append("border : 1px solid DarkBlue; \n");
    sb.Append("padding : 3px 18px 3px 32px; \n");
    sb.Append("} \n");
    sb.Append("</style> \n");

    this.ClientScript.RegisterClientScriptBlock(this.GetType(),
        "CodigoEstilos", sb.ToString(), false);

    //....
    for (int nIndice = 0; nIndice < dvDimProductSubcategory.Count; nIndice++)
    {
        //....
        oLinkButton.CssClass = "ElementoLista";
        //....
    }
    //....
}    

Y esto sería todo, el estilo vuelve a ser aplicado únicamente en los elementos de la lista.

 

El código fuente de esta entrega puede obtenerse en el siguiente enlace.

Un saludo.

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

Selección de registro en tabla relacionada

Al finalizar la segunda parte de este artículo habíamos dejado a nuestro control DropDownExtender pendiente de añadirle una funcionalidad, consistente en obtener un registro de la tabla DimProductCategory cada vez que hiciéramos una selección en la lista desplegable. Dado que el DropDownExtender lo llenábamos con los registros de la tabla DimProductSubcategory, el medio de obtener el registro de la tabla relacionada pasa por utilizar el campo ProductCategoryKey, presente en ambas tablas, y que establece dicha relación, como vemos en el siguiente esquema.

El efecto que perseguimos es el siguiente: al seleccionar un elemento de la lista, obtenemos el valor del campo ProductSubcategoryKey, el cual representa la clave primaria de la tabla DimProductSubcategory. Este dato lo habíamos añadido previamente al construir la lista de elementos del DropDownExtender, asignando a la propiedad ID de cada LinkButton una cadena compuesta por el prefijo "lnk_" más el valor del mencionado campo, que tomábamos de la fila en curso correspondiente al DataView que contenía los registros de la tabla.

 

// recorrer las filas de datos...
for (int nIndice = 0; nIndice < dvDimProductSubcategory.Count; nIndice++)
{
    // ...para cada registro
    // crear un elemento en la lista desplegable (panel de lista)
    oDataRowView = dvDimProductSubcategory[nIndice];

    // cada elemento de la lista desplegable será un control LinkButton
    oLinkButton = new LinkButton();
    oLinkButton.ID = "lnk_" + (int)oDataRowView["ProductSubcategoryKey"];
    //....
    //....
 
De esta forma, al hacer clic sobre uno de los LinkButton de la lista, en el manipulador de su evento Click, convertiremos la propiedad LinkButton.ID a un array de cadenas, recuperando la clave del registro desde la segunda posición de dicho array, tal y como vemos a continuación.
 
 
protected void LinkButton_Click(object sender, EventArgs e)
{
    //....
    // obtener a partir del identificador del elemento seleccionado
    // el código del registro de la tabla DimProductSubcategory
    string sProductSubcategoryKey = oLinkButton.ID.Split(new char[] { '_' })[1];
    //....

El siguiente paso consistirá en posicionarnos sobre el registro de la tabla DimProductSubcategory utilizando el valor recién obtenido. Para ello volveremos a obtener un DataView del  SqlDataSource que contiene dicha tabla, aplicándole un filtro basado en el valor del registro a localizar.

 

protected void LinkButton_Click(object sender, EventArgs e)
{
    //....
    // obtener a partir del identificador del elemento seleccionado
    // el código del registro de la tabla DimProductSubcategory
    string sProductSubcategoryKey = oLinkButton.ID.Split(new char[] { '_' })[1];
    //....
    // recuperar del SqlDataSource correspondiente a la tabla DimProductSubcategory
    // una vista con sus registros
    DataView dvDimProductSubcategory = (DataView)this.dsrDimProductSubcategory.Select(new DataSourceSelectArguments());

    // aplicar a la vista un filtro para obtener 
    // el registro seleccionado en la lista desplegable
    dvDimProductSubcategory.RowFilter = "ProductSubcategoryKey = " + sProductSubcategoryKey;
    //....
}

Ahora que ya estamos situados en la fila que queríamos de DimProductSubcategory, necesitamos obtener el registro relacionado de la tabla DimProductCategory mostrando sus valores en la página.

Para conseguir nuestro propósito agregaremos al diseño del WebForm un nuevo componente SqlDataSource basado en una consulta contra la tabla DimProductCategory. Puesto que solamente necesitaremos obtener un registro de dicha tabla en cada ocasión, añadiremos a este origen de datos un parámetro de filtro que nos sirva para acotar el resultado a una única fila.

Adicionalmente insertaremos en la página sendos controles Label y TextBox, cuyo fin consistirá en visualizar la información del registro de DimProductCategory seleccionado.

Para organizar adecuadamente todo el conjunto de controles que hemos incluido en la página utilizaremos una tabla, quedando la parte correspondiente al diseño de nuestro WebForm como vemos en el siguiente código fuente.

 

<form id="form2" runat="server">

<asp:ScriptManager ID="ScriptManager1" runat="server" />

<asp:SqlDataSource ID="dsrDimProductSubcategory" runat="server" 
    ConnectionString="Data Source=localhost;Initial Catalog=AdventureWorksDW;Persist Security Info=True;User ID=sa;Password=123sa456;"
    SelectCommand="SELECT * FROM DimProductSubcategory" />

<asp:SqlDataSource ID="dsrDimProductCategory" runat="server" 
    ConnectionString="Data Source=localhost;Initial Catalog=AdventureWorksDW;Persist Security Info=True;User ID=sa;Password=123sa456;"
    SelectCommand="SELECT * FROM DimProductCategory" 
    FilterExpression="ProductCategoryKey = '{0}'">
    <FilterParameters>
        <asp:Parameter Name="parProductCategoryKey" />
    </FilterParameters>
</asp:SqlDataSource>

<div>
    <asp:UpdatePanel ID="pnlUpdatePanel" runat="server">
        <ContentTemplate>
            <table border="1">
                <tr>
                    <td width="240" bgcolor="#FFFFCC">
                        <asp:TextBox ID="txtTituloLista" runat="server" Text="Seleccionar artículo" Width="200px" />
                        <br />
                        <asp:Panel ID="pnlElementosLista" runat="server" Width="200px" />
                        <cc1:DropDownExtender ID="DropDownExtender1" runat="server" DropDownControlID="pnlElementosLista"
                            TargetControlID="txtTituloLista" />
                    </td>
                    <td width="350" bgcolor="#99FFCC">
                        <asp:Label ID="Label1" runat="server" Text="ProductCategoryKey: " />
                        <asp:TextBox ID="txtProductCategoryKey" runat="server" Height="22px" Width="35px" />
                        <br />
                        <br />
                        <asp:Label ID="Label2" runat="server" Text="SpanishProductCategoryName: " />
                        <asp:TextBox ID="txtSpanishProductCategoryName" runat="server" Width="100px" />
                        <br />
                        <br />
                    </td>
                </tr>
            </table>
        </ContentTemplate>
    </asp:UpdatePanel>
</div>
</form>

Únicamente nos queda escribir en el evento Click del LinkButton, la operación encargada de pasar al parámetro de filtro del objeto SqlDataSource asociado a la tabla DimProductCategory, el valor de búsqueda para la misma, tras lo cual obtendremos un DataView con el registro localizado. Los valores de este registro los traspasaremos a los controles de la página. A continuación podemos ver al completo el código de este evento.

 

protected void LinkButton_Click(object sender, EventArgs e)
{
    // al hacer clic en un elemento de la lista
    // mostrar el texto del elemento
    // en el TextBox que sirve para desplegarla
    LinkButton oLinkButton = ((LinkButton)sender);
    this.txtTituloLista.Text = oLinkButton.Text;

    // obtener a partir del identificador del elemento seleccionado
    // el código del registro de la tabla DimProductSubcategory
    string sProductSubcategoryKey = oLinkButton.ID.Split(new char[] { '_' })[1];

    // recuperar del SqlDataSource correspondiente a la tabla DimProductSubcategory
    // una vista con sus registros
    DataView dvDimProductSubcategory = (DataView)this.dsrDimProductSubcategory.Select(new DataSourceSelectArguments());

    // aplicar a la vista un filtro para obtener 
    // el registro seleccionado en la lista desplegable
    dvDimProductSubcategory.RowFilter = "ProductSubcategoryKey = " + sProductSubcategoryKey;
    
    // obtener el registro relacionado de la tabla DimProductCategory:
    // ---------------------------------------------------------------
    // aplicar como filtro al objeto SqlDataSource de la tabla DimProductCategory
    // el valor del campo ProductCategoryKey de la vista DimProductSubcategory
    this.dsrDimProductCategory.FilterParameters["parProductCategoryKey"].DefaultValue =
        dvDimProductSubcategory[0]["ProductCategoryKey"].ToString();

    // recuperar la vista de la tabla DimProductCategory
    // conteniendo el registro filtrado y mostrarlo en los
    // controles de la página
    DataView dvDimProductCategory = (DataView)this.dsrDimProductCategory.Select(new DataSourceSelectArguments());
    this.txtProductCategoryKey.Text = dvDimProductCategory[0]["ProductCategoryKey"].ToString();
    this.txtSpanishProductCategoryName.Text = dvDimProductCategory[0]["SpanishProductCategoryName"].ToString();
}

Al volver a ejecutar la aplicación, la selección de un elemento de la lista mostrará los valores del registro de la tabla relacionada en el resto de controles de la página.

 

Tras estos últimos ajustes podemos dar por concluido el desarrollo del ejemplo, cuyo código fuente se puede descargar en este enlace. No obstante, existe un aspecto que dejaremos para una última entrega de este artículo, consistente en un modo alternativo de configurar las propiedades visuales de la lista desplegable.

Un saludo.

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

Aplicando mejoras visuales

En la primera entrega de este artículo vimos como crear, mediante el control DropDownExtender del Ajax Toolkit, una lista desplegable cuyos elementos componíamos a partir de un DataView obtenido desde un objeto SqlDataSource.

La funcionalidad conseguida era correcta, no resultando así en lo tocante al apartado visual, ya que en la carga inicial de datos, este control produce un molesto efecto de parpadeo, al mostrar por un instante el control Panel que contiene la lista de elementos. Por otro lado se encuentra el hecho de que los mencionados elementos, al ser controles LinkButton, se visualizan como es natural con la apariencia estándar de enlaces. En esta parte del artículo, nuestro objetivo será aplicar algunas mejoras estéticas a la lista para que luzca con toda su lozanía y esplendor ;-). Estas operaciones las desarrollaremos dentro del evento Load de la página.

Poniéndonos manos a la obra, en primer lugar modificaremos la configuración de color del TextBox que realiza la apertura de la lista a través de sus propiedades de estilo. Adicionalmente también tendremos que actuar sobre la propiedad DropDownExtender.HighlightBackColor si queremos que al situar el cursor sobre el TextBox se mantenga el mismo color. La forma de evitar el parpadeo inicial de la lista pasará por asignar el valor "none" al estilo "display" del Panel.

 

//....
// configurar aspecto visual de los componentes de la lista desplegable
this.pnlElementosLista.Style["display"] = "none";
this.txtTituloLista.Style[HtmlTextWriterStyle.Color] = "DarkBlue";
this.txtTituloLista.Style[HtmlTextWriterStyle.BackgroundColor] = "LightGreen";
this.ddeLista.HighlightBackColor = System.Drawing.Color.FromName("LightGreen");

// obtener las filas de la fuente de datos
DataView dvDimProductSubcategory =
    (DataView)this.dsrDimProductSubcategory.Select(new DataSourceSelectArguments());
//....


A continuación pasaremos a retocar los LinkButton que funcionan como elementos de la lista, modificando igualmente sus propiedades de estilo. Por ejemplo, mediante TextDecoration eliminaremos el subrayado del texto, y para que se dispongan en vertical dentro de la lista, asignaremos el valor "block" al estilo Display.

 

// recorrer las filas de datos...
for (int nIndice = 0; nIndice < dvDimProductSubcategory.Count; nIndice++)
{
    //....
    // configurar aspecto visual base del elemento de lista
    oLinkButton.Style[HtmlTextWriterStyle.TextDecoration] = "none";
    oLinkButton.Style[HtmlTextWriterStyle.Display] = "block";
    oLinkButton.Style[HtmlTextWriterStyle.Cursor] = "default";
    oLinkButton.Style[HtmlTextWriterStyle.BackgroundColor] = "LightGreen";
    oLinkButton.Style[HtmlTextWriterStyle.Color] = "DarkBlue";
    oLinkButton.Style[HtmlTextWriterStyle.Padding] = "4px 19px 4px 33px";
    //....
}

En su estado actual, nuestra lista de elementos se mostraría de forma similar a la siguiente imagen.

Visualmente ya hemos mejorado algo, pero todavía nos faltan un par de cosillas. La más inmediata sería suprimir el horrendo espacio vacío que queda entre cada elemento, problema este con muy fácil solución, ya que simplemente tenemos que eliminar la línea de código que inserta una etiqueta <br> después de añadir el LinkButton al Panel.

La segunda cuestión radica en el hecho de que el usuario, al desplazar el cursor por la lista abierta, no obtiene una respuesta visual del elemento sobre el que está o ha dejado de estar posicionado. Este otro entuerto lo vamos a desfacer añadiendo un par de funciones en javascript para los eventos onmouseover y onmouseout, correspondientes a la parte cliente de los LinkButton. Al modificar algunas propiedades visuales de estos elementos, conseguiremos ofrecer una adecuada interacción con el usuario. Dichas operaciones podríamos codificarlas en un bloque de script de la página de la siguiente forma.

 

<script language="javascript" type="text/javascript">
function LinkButton_onmouseover(oLnkBtn) 
{ 
    oLnkBtn.style.backgroundColor = 'LightSkyBlue';
    oLnkBtn.style.border = '1px solid DarkBlue'; 
    oLnkBtn.style.padding = '3px 18px 3px 32px'; 
} 
function LinkButton_onmouseout(oLnkBtn) 
{ 
    oLnkBtn.style.backgroundColor = 'LightGreen'; 
    oLnkBtn.style.borderStyle = 'none'; 
    oLnkBtn.style.padding = '4px 19px 4px 33px'; 
} 
</script>


Sin embargo, como ya hemos comentado anteriormente, todas las operaciones pertenecientes a esta parte del artículo las vamos a implementar en el codebehind de la página, y dado que necesitamos emitir código de script desde el servidor al cliente, recurriremos a la propiedad ClientScript de la página, que contiene un objeto ClientScriptManager, cuyo método RegisterClientScriptBlock emplearemos para pasarle el bloque de código de script que previamente habremos creado mediante un objeto StringBuilder, como vemos a continuación.

 

protected void Page_Load(object sender, EventArgs e)
{
    // emitir código cliente para los eventos
    // que se produzcan sobre los elementos de la lista desplegable
    System.Text.StringBuilder sbScript = new System.Text.StringBuilder();
    sbScript.Append("function LinkButton_onmouseover(oLnkBtn) \n");
    sbScript.Append("{ \n");
    sbScript.Append("oLnkBtn.style.backgroundColor = 'LightSkyBlue'; \n");
    sbScript.Append("oLnkBtn.style.border = '1px solid DarkBlue'; \n");
    sbScript.Append("oLnkBtn.style.padding = '3px 18px 3px 32px'; \n");
    sbScript.Append("} \n");
    sbScript.Append("function LinkButton_onmouseout(oLnkBtn) \n");
    sbScript.Append("{ \n");
    sbScript.Append("oLnkBtn.style.backgroundColor = 'LightGreen'; \n");
    sbScript.Append("oLnkBtn.style.borderStyle = 'none'; \n");
    sbScript.Append("oLnkBtn.style.padding = '4px 19px 4px 33px'; \n");
    sbScript.Append("} \n");

    this.ClientScript.RegisterClientScriptBlock(this.GetType(), "CodigoJS", sbScript.ToString(), true);
    //....


Para que dicho código de script pueda ser ejecutado cuando el control LinkButton desencadene los eventos anteriormente comentados,  los asociaremos mediante su propiedad Attributes de la siguiente forma.

 

for (int nIndice = 0; nIndice < dvDimProductSubcategory.Count; nIndice++)
{
    //....
    // configurar aspecto visual base del elemento de lista
    //....               
    //....               
    // asociar código cliente con eventos del LinkButton,
    // para advertir el estado de entrada y salida del cursor
    // en cada elemento de la lista desplegable
    oLinkButton.Attributes.Add("onmouseover", "LinkButton_onmouseover(this)");
    oLinkButton.Attributes.Add("onmouseout", "LinkButton_onmouseout(this)");
    //....               
}


Al volver a ejecutar la aplicación, el aspecto visual de nuestro control de lista habrá mejorado considerablemente como podemos comprobar en la siguiente imagen.

La única tarea pendiente que nos queda para completar el ejemplo corresponde a la búsqueda, en la tabla DimProductCategory, del registro relacionado con el valor seleccionado en la lista. Pero este es un aspecto que dejaremos para la próxima entrega del artículo. El código fuente de la entrega actual está disponible en este enlace.

Un saludo.

 

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

Creación de la lista desplegable y carga de datos

En un artículo anterior hicimos una introducción básica al control DropDownExtender del Ajax Control Toolkit, comentando sus funcionalidades principales, las cuales ilustrábamos mediante un sencillo ejemplo basado en una lista desplegable, cuyos elementos se disponían en orientación horizontal.

Sin embargo, un control de tales características suele ser alimentado, habitualmente, con valores procedentes de un origen de datos, y también es muy posible que la selección de un elemento de la lista desencadene la búsqueda y obtención de otro registro existente en una tabla relacionada.

Teniendo en cuenta las anteriores premisas, en este artículo que dividiremos en varias entregas, vamos a desarrollar un ejemplo con dicha funcionalidad. Llenaremos la lista desplegable con información de la base de datos AdventureWorksDW de SQL Server 2005, en concreto la tabla DimProductSubcategory, de forma que al seleccionar uno de sus elementos, accederemos a la tabla relacionada DimProductCategory y obtendremos el registro dependiente.

Como ya explicamos en el anterior artículo, para crear una lista desplegable mediante un DropDownExtender necesitamos, además de este último control, un par de controles más que actúen como lista de elementos y apertura de la misma. En nuestro ejemplo usaremos un control Panel para la lista de valores y un TextBox en el que alojaremos el elemento de la lista seleccionado por el usuario. También añadiremos al diseñador del WebForm un ScriptManager que nos permita habilitar las características de AJAX en nuestra página.

En el apartado de datos emplearemos un componente SqlDataSource como origen de datos. Dado que el control DropDownExtender carece de una propiedad DataSourceID para poder realizar un enlace automático de datos, puede parecer que su uso no resulta del todo apropiado en este contexto, pero enseguida veremos cómo podemos utilizarlo de una forma muy sencilla para recorrer la tabla, alimentando la lista con sus registros. El código de la página sería el siguiente.

 

<form id="form2" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:SqlDataSource ID="dsrDimProductSubcategory" runat="server"
ConnectionString="Data Source=localhost;Initial Catalog=AdventureWorksDW;Persist Security Info=True;User ID=sa;Password=123sa456;" SelectCommand="SELECT * FROM DimProductSubcategory" /> <div> <asp:TextBox ID="txtTituloLista" runat="server" Text="Seleccionar artículo" Width="200px" /> <br /> <asp:Panel ID="pnlElementosLista" runat="server" Width="200px" /> <cc1:DropDownExtender ID="DropDownExtender1" runat="server" DropDownControlID="pnlElementosLista" TargetControlID="txtTituloLista" /> </div> </form>

El modo de creación de los elementos de la lista así como su selección, lo desarrollaremos en el codebehind de la página. En primer lugar necesitamos acceder a los registros de la consulta definida en el SqlDataSource, y la forma de conseguirlo es utilizando su método Select, el cual devuelve un objeto DataView, cuyas filas recorreremos, creando un objeto LinkButton para cada elemento de la lista, que al ser pulsado ejecutará el manipulador correspondiente al evento Click, donde asignaremos el literal del elemento de la lista al TextBox que usamos para desplegarla.

Debido a lo "artesanal" del proceso de creación de la lista, para evitar que sus elementos se dispongan concatenados, después de crear cada uno de los LinkButton, agregaremos una etiqueta <br> al panel de la lista, para que el contenido de esta se muestre adecuadamente en vertical.

 

protected void Page_Load(object sender, EventArgs e)
{
    // obtener las filas de la fuente de datos
    DataView dvDimProductSubcategory =
        (DataView)this.dsrDimProductSubcategory.Select(new DataSourceSelectArguments());

    DataRowView oDataRowView;
    LinkButton oLinkButton;

    // recorrer las filas de datos...
    for (int nIndice = 0; nIndice < dvDimProductSubcategory.Count; nIndice++)
    {
        // ...para cada registro
        // crear un elemento en la lista desplegable (panel de lista)
        oDataRowView = dvDimProductSubcategory[nIndice];

        // cada elemento de la lista desplegable será un control LinkButton
        oLinkButton = new LinkButton();
        oLinkButton.ID = "lnk_" + (int)oDataRowView["ProductSubcategoryKey"];
        oLinkButton.Text = (string)oDataRowView["SpanishProductSubcategoryName"];
        oLinkButton.Click += new EventHandler(LinkButton_Click);

        // añadir el control al panel de lista de elementos
        this.pnlElementosLista.Controls.Add(oLinkButton);

        // mediante una etiqueta <br> separamos los elementos
        this.pnlElementosLista.Controls.Add(new LiteralControl("<br />"));
    }
}

protected void LinkButton_Click(object sender, EventArgs e)
{
    // al hacer clic en un elemento de la lista
    // mostrar el texto del elemento
    // en el TextBox que sirve para desplegarla
    LinkButton oLinkButton = ((LinkButton)sender);
    this.txtTituloLista.Text = oLinkButton.Text;
}

Ejecutando la aplicación en su estado actual conseguiremos una parte de la funcionalidad perseguida: la lista desplegable contiene los elementos obtenidos de la base de datos, y al seleccionar uno de ellos pasamos su valor al TextBox. Sin embargo todavía se produce en el navegador el efecto de recarga completa de la página, punto este que solucionaremos añadiendo al diseñador del WebForm un control UpdatePanel de AJAX, en cuyo interior situaremos los controles que componen la lista.

 

….
….
<asp:UpdatePanel ID="pnlUpdatePanel" runat="server">
    <ContentTemplate>
        <asp:TextBox ID="txtTituloLista" runat="server" Text="Seleccionar artículo" Width="200px" />
        <br />
        <asp:Panel ID="pnlElementosLista" runat="server" Width="200px" />
        <cc1:DropDownExtender ID="DropDownExtender1" runat="server" DropDownControlID="pnlElementosLista"
            TargetControlID="txtTituloLista" />
    </ContentTemplate>
</asp:UpdatePanel>
….
….

Con esta modificación conseguiremos evitar el desagradable efecto de recarga completa de página.

Aunque el funcionamiento de la lista ya es el correcto, estamos de acuerdo en que la parte visual deja mucho que desear, siendo este un aspecto que abordaremos en la próxima entrega del artículo. El código fuente del ejemplo realizado hasta el momento puede descargarse en el siguiente enlace.

Un saludo.

 

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

El control DropDownExtender, incluido en el Ajax Control Toolkit, nos permite crear listas desplegables con un elevado nivel de personalización, como en el caso que nos ocupa actualmente, donde supongamos que necesitamos crear una lista cuyos elementos, al ser desplegados, se dispongan alineados en horizontal, en lugar de verticalmente, como ocurre con el control DropDownList.

Por si alguien se encuentra en la tesitura de tener que desarrollar un control con un comportamiento como el que acabamos de mencionar, vamos a describir, a modo de pequeño tutorial, los pasos que debemos completar para obtener una lista desplegable de tales características.

El proyecto de ejemplo que vamos a crear se ha desarrollado utilizando Visual Studio 2008, el cual ya incluye por defecto las extensiones de AJAX en el cuadro de herramientas, mientras que el paquete de controles "Ajax Control Toolkit" será necesario descargarlo e instalarlo aparte. En la siguiente dirección encontraremos el enlace "Download the Control Toolkit", que nos permitirá descargar el conjunto de controles adicionales para AJAX.

En el caso de que nos encontremos trabajando con una versión anterior del entorno de desarrollo: Visual Studio 2005, Visual Web Developer, etc., en la siguiente dirección hallaremos descargas también para estas versiones.

Una vez que tengamos nuestro entorno de desarrollo ajustado adecuadamente nos pondremos manos a la obra. En primer lugar crearemos un nuevo proyecto de tipo ASP.NET Web Application, basado en lenguaje C#.

Como segundo paso primordial, añadiremos al diseñador del WebForm un componente ScriptManager, imprescindible si queremos "AJAXificar" ;-) nuestra aplicación.

 

A continuación pasaremos a diseñar nuestra lista desplegable personalizada, utilizando el control DropDownExtender, cuyo mecanismo de funcionamiento se sustenta en el uso de dos controles: uno que actúe como desencadenador de apertura de la lista, y otro que contenga los elementos de la misma.

Añadiremos por lo tanto un Label, para abrir la lista, y un Panel, que funcionará como lista de elementos, sobre la que el usuario se desplazará para realizar su selección. Dentro de este último insertaremos varios Label, que harán las veces de elementos de la lista.

El siguiente paso consistirá en añadir el control DropDownExtender al WebForm, indicando en sus propiedades TargetControlID y DropDownControlID, los nombres de los controles que abrirán la lista y mostrarán los elementos respectivamente. Todo ello lo vemos en el siguiente código fuente.

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
    <asp:Label ID="lblTituloLista" runat="server" Text="Elegir artículo" Width="200px"></asp:Label>
    <asp:Panel ID="pnlElementosLista" runat="server" Width="200px">
        <asp:Label ID="lblMonitor" runat="server" Text="Monitor"></asp:Label>
        <asp:Label ID="lblMemoria" runat="server" Text="Memoria USB"></asp:Label>
        <asp:Label ID="lblTeclado" runat="server" Text="Teclado"></asp:Label>
    </asp:Panel>
    <cc1:DropDownExtender ID="DropDownExtender1" runat="server" DropDownControlID="pnlElementosLista"
        TargetControlID="lblTituloLista">
    </cc1:DropDownExtender>
</div>
</form>

Tras estas operaciones, ya tenemos nuestro control de lista con una funcionalidad mínima, pero no completa, y decimos esto porque si ejecutamos la aplicación, observaremos que el Label de apertura se visualiza con un botón de despliegue, que al ser pulsado, abre el control Panel que contiene los elementos, pero estos ni cambian su estado visual al pasar el cursor sobre ellos, ni reaccionan ante un clic del usuario, como vemos en la siguiente imagen.

Además, como inconveniente añadido, cuando se carga el formulario en el navegador, observaremos que el panel de elementos se muestra por un instante, lo que provoca un desagradable efecto de parpadeo. Esto lo solucionamos insertando en el atributo Style del control Panel, los siguientes valores que inicialmente ocultan este.

Para completar esta falta de funcionalidad recurriremos al uso de estilos y eventos en el lado cliente. Necesitamos cambiar el estado visual, por ejemplo el color, cuando se produzcan los eventos onmousemove y onmouseout sobre cada elemento contenido en la lista, lo que solventaremos definiendo sendos estilos para estas situaciones, que asignaremos mediante las pertinentes funciones en javascript.

Por otro lado, la forma de detectar cuándo se realiza una selección sobre alguno de los elementos de la lista será a través de su evento onclick, para el que escribiremos una función que muestre el contenido del Label sobre el que se ha pulsado. Vemos seguidamente todo este código de la página al completo.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ListaDesplegableHorizontal._Default" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!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>Lista desplegable con elementos alineados en horizontal</title>
    <style type="text/css">
        .ConFoco
        {
            background-color: #0099FF;
            color: White;
        }
        .SinFoco
        {
            background-color: #CCFFFF;
            color: Black;
        }
    </style>
    <script language="javascript" type="text/javascript">
    function Ctl_onmousemove(oControl)
    {
        oControl.className = "ConFoco";
    }
    function Ctl_onmouseout(oControl)
    {
        oControl.className = "SinFoco";
    }
    function Ctl_onclick(oControl)
    {
        alert(oControl.innerText);
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <asp:Label ID="lblTituloLista" runat="server" Text="Elegir artículo" Width="200px"
            CssClass="SinFoco"></asp:Label>
        <asp:Panel ID="pnlElementosLista" runat="server" Width="200px" CssClass="SinFoco"
            Style="display: none; visibility: visible;">
            <asp:Label ID="lblMonitor" runat="server" Text="Monitor"></asp:Label>
            <asp:Label ID="lblMemoria" runat="server" Text="Memoria USB"></asp:Label>
            <asp:Label ID="lblTeclado" runat="server" Text="Teclado"></asp:Label>
       
</asp:Panel>
        <cc1:DropDownExtender ID="ddeListaBase" runat="server" DropDownControlID="pnlElementosLista"
            TargetControlID="lblTituloLista" HighlightBackColor="#CCFFFF">
        </cc1:DropDownExtender>
    </div>
    </form>
</body>
</html>

Para asociar los controles con el código cliente que acabamos de escribir, utilizaremos el evento Load del WebForm, donde emplearemos la propiedad Attributes que contienen los controles, que nos permite establecer qué función de las que acabamos de escribir será ejecutada al desencadenarse un determinado evento cliente sobre el control.

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        foreach (Control oControl in this.pnlElementosLista.Controls)
        {
            if (oControl.GetType() == typeof(Label))
            {
                ((Label)oControl).Attributes.Add("onmousemove", "Ctl_onmousemove(this)");
                ((Label)oControl).Attributes.Add("onmouseout", "Ctl_onmouseout(this)");
                ((Label)oControl).Attributes.Add("onclick", "Ctl_onclick(this)");
            }
        }
    }
}

Con estos ajustes que acabamos de realizar, ya hemos conseguido crear un control de lista con el comportamiento que indicábamos al principio de este post: una lista desplegable de elementos abierta en disposición horizontal, los cuales reaccionan visualmente a la interacción con el usuario, como vemos en la siguiente imagen.

El ejemplo de este artículo se puede descargar desde el siguiente enlace.

Espero que os pueda resultar de utilidad 8-).

Un saludo.

 

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

El objetivo del control ImageList consiste en permitir, de un modo sencillo, la creación y mantenimiento de una colección de imágenes dentro del diseñador de formularios Windows, de manera que las mismas puedan ser asociadas, fundamentalmente en tiempo de diseño, al resto de controles del formulario que dispongan de la capacidad de visualizar una imagen.

A continuación vemos la configuración de este control en tiempo de diseño, donde podemos establecer, entre otras propiedades, el tamaño a usar para mostrar las imágenes, y el cuadro de diálogo para el mantenimiento de la lista.

Una vez creada la lista, resulta una tarea muy simple asignar las imágenes en tiempo de diseño a un control que disponga de la propiedad ImageList. Únicamente tenemos que indicar el nombre de la lista de imágenes en dicha propiedad, mientras que para seleccionar una de ellas, utilizaremos el índice numérico que ocupa en la lista a través de la propiedad ImageIndex, o bien su nombre, mediante la propiedad ImageKey.

Sin embargo, en el caso de querer realizar la misma operación al crear una barra de herramientas con un componente ToolStrip que contenga, por ejemplo, controles ToolStripButton y ToolStripLabel, descubriremos que, curiosamente, las anteriormente mencionadas propiedades no se muestran en la ventana de propiedades del diseñador de formularios.

¿Quiere esto decir que no podemos emplear un ImageList para guardar las imágenes de una barra de herramientas?. No, por supuesto, estas propiedades siguen estando disponibles, lo que ocurre es que debemos manipularlas por código en lugar de utilizando el diseñador.

Como podemos ver en el siguiente código fuente, al cargar el formulario nos ocupamos de dicha tarea, mostrando las diversas formas de asignación de una imagen a los controles.

private void Form1_Load(object sender, EventArgs e)
{   
  // asignar lista de imágenes a barra de herramientas
   
  this.toolStrip1.ImageList = this.imageList1;         

  // asignar imágenes de lista a controles
   
  // de la barra de herramientas   
  this.toolStripButton1.ImageIndex = 0;   
  this.toolStripButton2.ImageKey = "ExploradorWeb.jpg";    

  // no ajustar el tamaño de imagen para este control
  this.toolStripButton2.ImageScaling = ToolStripItemImageScaling.None;

  // asignar una imagen a un control   
  // de la barra de herramientas sin usar ImageList   
  this.toolStripLabel1.Image = new Bitmap(@"C:\Imagenes\CarpetaCerrada.gif");
}

En primer lugar tomamos el objeto ImageList que hemos creado en tiempo de diseño y lo asignamos a la propiedad ToolStrip.ImageList. Seguidamente, pasamos a los controles de esta barra de herramientas, estableciendo las imágenes a mostrar con las propiedades ImageIndex e ImageKey respectivamente.

Por defecto, el tamaño de las imágenes visualizadas por los controles de la barra de herramientas se ajustan al tamaño de la misma, pero si no deseamos tal comportamiento, y queremos que el tamaño de la imagen sea el que hemos establecido en el ImageList, asignaremos el valor None a la propiedad ImageScaling del control perteneciente a la barra.

Finalmente utilizamos el modo más directo: instanciar un objeto derivado de la clase Image, y asignarlo a la propiedad del mismo nombre del control.

El resultado final lo observamos en la siguiente imagen, donde podemos ver los controles añadidos al ToolStrip. En el caso de la etiqueta o ToolStripLabel, se muestra de forma combinada, tanto la imagen asignada en tiempo de ejecución como el literal de la etiqueta.

Así que no se asuste nadie cuando vaya a utilizar un ImageList junto a un ToolStrip, sigue siendo posible, simplemente cambia un poco el lugar desde donde trabajar con las propiedades.

Un saludo.

 

Publicado por Luis Miguel Blanco | con no comments
Archivado en: ,