Mostrando entradas con la etiqueta UserControl. Mostrar todas las entradas
Mostrando entradas con la etiqueta UserControl. Mostrar todas las entradas

OnClick Event not fired first in custom control

Tal y como indica el título de este post, voy a explicar porque a veces no se ejecuta el evento 'OnClick' a la primera, dentro de nuestros Custom controls, y sí a la segunda.

El problema supuesto se puede reproducir insertando en una página aspx, varios controles personalizados, que a su vez tienen subcontroles hijos personalizados con botones que deben ejecutar un postback a la página padre.

Contemos con el supuesto inicial:
· Una página padre, con un UpdatePanel de Ajax.
· Un control principal, que se inserta 5 veces en la página padre.
· Unos controles hijos que se insertan dinámicamente en el control padre (segun la interaccion del usuario), hasta un total de 3.
· Estos controles hijos tienen un botón que permite eliminarlos de la lista del control padre.

El proceso es el siguiente:
Cuando se carga la página principal, el usuario puede añadir los controles padre según le convenga. Además, según se van cargando los controles padres, el usuario puede ir insertando en ellos, los controles hijos. Si en algún momento se encuentra en la necesidad de borrar un subcontrol hijo de la lista de uno de los padres, debe pulsar su botón para eliminarlo... y es en este punto donde falla el sistema si se han añadido más de un control hijo.

Después de mucho buscar en "San Google" (más adelante pondré algunas referencias) en la mayoría de los supuestos, se hablaba de un conflicto con los postback, y la carga de los controles de forma asíncrona. O bien, trataban el tema desde la generación de los controles en el Page_Load.

Teniendo en cuenta las anteriores soluciones y confirmando que todo estaba en su sitio, encontré mi propia solución y tuve que darme golpes contra la pared porque se trataba de un tema tan simple como abrumador.

SOLUCION:

El propio compilador de ASP.NET, genera un ID automático y único para cada componente insertado en la página, sin importar cuando o donde se genere. Ahora bien, si se insertan dinámicamente los controles, es posible que por el orden de inserción, dichos controles no contengan el mismo ID entre un postback y otro.

Esto supone, que si insertamos un primer control e inmediatamente después, realizamos un postback sobre él, el sistema es capaz de reconocer de dónde viene, y por tanto ejecutarlo. Pero si por el contrario, insertamos un primer elemento y posteriormente un segundo, es posible que los ID autogenerados entren en conflicto y al intentar realizar un postback desde el primero, éste no encuentre el ID inicial y no ejecute el evento.

NOTA CURIOSA: En este punto la página ha vuelto a reenviarse y cada subcontrol se ha vuelto a generar con un nuevo ID. Por tanto si se vuelve a ejecutar el postback del botón, esta vez SÍ encontrará el control y lo realizará correctamente.

COMO EVITAR ENTONCES este problema...
Tan sencillo como estúpido. Asigna directamente un ID único a tu subcontrol cuando lo generes. De esta forma mantendrá siempre un identificador unico por muchos controles que insertes en la página.

Entiendo que algunos me podréis decir que es "Good Practice" asignar directamente el ID y sí, así es. Pero el problema es que muchas veces necesitamos trabajar tan rápido, y estamos tan acostumbrados a las bondades de Visual Studio, que nos olvidamos de estos pequeños detalles (tan importantes).

Así que no lo olvides, toma como buena práctica identificar cada control que generes con un ID propio, y evitarás este molesto ¿problemilla?.

Espero que os ayude.
¡Un saludo a todos!

Referencias:
StackOverflow Questions: 2765815
PostBack forced on UpdatePanel
Programmatically adding ASP.net User Control to Partial Page Update / UpdatePanel
[...]

Leer más >>

Customize GridViewColumn: Personalizando columnas de gridview por código con controles

Muchas veces he tenido la necesidad de incluir información concreta en una columna de un GridView, y me ha costado mucho encontrar referencias que explicaran en profundidad cómo realizar la “customización” de una columna (sobre todo en castellano, porque no encontraba documentación al respecto).

En este post, vamos a explicar paso a paso cómo se puede personalizar completamente una columna de un GridView por código. En concreto, lo que realizaremos una personalización de una columna con un CheckBox  (que permita su edición), y una cabecera que seleccione todas las filas del grid.

Para ello debemos crear  una plantilla para la columna y una vez realizada, referenciarla desde la página del CodeBehind, indicándole así al grid cómo debe mostrar la información.

Dado que sería muy largo, lo he dividido en dos partes:
Parte I: Generación de la plantilla
    PASO 1: Generación de la plantilla ITemplate
    PASO 2: Control de eventos
Parte II: Construcción del GridView


PREVIO:
En este ejemplo, vamos a construir una columna de  CheckBox que controla un listado de test realizados por usuarios. El nombre de cada columna hará referencia al campo equivalente en la DDBB (así, Test1 sería la columna de los datos para el Test1). Además vamos a incluir en la cabecera una imagen que al pulsar, seleccionará todos los checks del gridview.


Parte I: Generación de la plantilla

PASO 1: Generación de la clase ITemplate
Para poder referenciar nuestra columna desde el GridView, necesitamos proporcionarle al sistema una forma de generar nuestra columna de CheckBox. Para ello hay que crear una clase que implemente el interfaz ITemplate. En nuestro ejemplo, denominaremos a la clase GridViewCheckboxTemplate.

public class GridViewCheckboxTemplate : ITemplate
{
    //Event handler - boton Header para “Seleccionar Todos”
    public event EventHandler ImageClick;

    //atributo para almacenar el ListItemType.
    ListItemType _templateType;

    //Atributo con el nombre identificador de la columna.
    private string _columnName;

    //Constructor donde definimos el tipo y nombre.
    public GridViewCheckboxTemplate(ListItemType type, string colname)
    {
        _templateType = type;
        _columnName = colname;
    }
 }

El ListItemType es el que nos indicará que parte del GridViewColumn estamos editando. Los valores que puede tomar son Header, Item, EditItem y Footer.

NOTA: En este caso, estamos generando una columna a partir de un constructor con 2 parametros. Podríamos crear otros constructores con más parámetros, en función de las necesidades.

Una vez implementados los atributos y el constructor, debemos indicar al sistema cómo ha de generar la vista de la columna en el GridView. Para ello, implementaremos el método ITemplate.InstantiateIn:

void ITemplate.InstantiateIn(System.Web.UI.Control container)
{
    switch (_templateType)
    {
        case ListItemType.Header:
            //TODO – elementos de la cabecera.
            break;
        case ListItemType.Item:
            //TODO – CheckBox en formato PRESENTACIÓN.
            break;
        case ListItemType.EditItem:
            //TODO –  CheckBox en formato EDICIÓN.
            break;
        case ListItemType.Footer:
            //TODO – elementos del footer.
            break;
    }
}

En la definición del método anterior podemos observar que, en función del tipo de ítem que estemos solicitando, el sistema generará unos controles u otros para el render de la columna completa. Por tanto, ya podemos incluir los controles necesarios para nuestra personalización:

HEADER:

Vamos a incluir un control ImageButton, de forma que aparezca un selector multiple que actualice todas las filas, y un Label con el nombre de la fila: 

case ListItemType.Header:
    ImageButton imgButton = new ImageButton();
    imgButton.ID = "imgHeader_" + _columnName;
    imgButton.ImageUrl = "~/images/ico/check_all.png";
    imgButton.ImageAlign = ImageAlign.AbsMiddle;
    imgButton.ToolTip = "Activar TODOS ";
    imgButton.Click += new ImageClickEventHandler(imgHeader_ClickEventHandler);
    imgButton.Attributes.Add("onclick", "return confirm('¿Seguro que desea activar todas las filas?');");
    container.Controls.Add(imgButton);

    //Creamos el label que indica en qué columna estamos.
    Label lbl = new Label();
    lbl.Text = " "+_columnName;
    container.Controls.Add(lbl);
    break;

Como se puede observar, para cada control establecemos todas sus propiedades, entre las que cabe destacar:
· La Url relativa a la raíz del proyecto la imagen que vamos a mostrar:
    imgButton.ImageUrl = "~/images/ico/check_all.png";  ->
· El evento que se disparará cuando se pulse sobre la imagen. Más adelante veremos cómo programar esta función:
        imgButton.Click += new ImageClickEventHandler(imgHeader_ClickEventHandler);
· Una confirmación de activación para todos los elementos:
        imgButton.Attributes.Add("onclick", "return confirm('¿Seguro que desea activar todas las filas?');");

ITEM y EDITITEM:
Como sólo vamos a mostrar un checkBox, simplemente debemos añadir dicho control a la columna:

case ListItemType.Item:
    //Creamos el checkBox.
    CheckBox chk = new CheckBox();
    chk.EnableViewState = true;
    chk.Enabled = false; //en vista, NO PERMITIMOS la modificación del valor.
    chk.ID = "chkItem_" + _columnName;
    chk.DataBinding += new EventHandler(CB1_DataBinding); //enlace a datos.
    container.Controls.Add(chk);
    break;

case ListItemType.EditItem:
    // Creamos el checkbox para edición
    CheckBox chkEdit = new CheckBox();
    chkEdit.EnableViewState = true;
    chkEdit.Enabled = true; //en edición, PERMITIMOS la modificación del valor
    chkEdit.ID = "chkEditItem_" + _columnName;
    chkEdit.DataBinding += new EventHandler(CB1_DataBinding);   //enlace a datos.
    container.Controls.Add(chkEdit);
    break;

A destacar el DataBinding que se encargará de mostrar la información al generar el GridView (ver paso 2 ‘Control de Eventos’), y la opción Enabled que es la que permite la modificación del estado del checkbox.

FOOTER:
En este ejemplo, no añadimos ningún elemento al footer, pero se podría personalizar con cualquier control que necesitaramos. Como ejemplos; un botón para añadir un nuevo ítem, ó un Label con información del total de registros, o ubicar aquí el selector multiple, etc.


NOTA: Recordar que si incluimos aquí cualquier control, en vista de diseño deberemos marcar el atributo ShowFooter a true.

PASO 2: Control de eventos
Una vez generado el método para construir la columna debemos controlar los eventos de datos y, en este ejemplo, del botón header para ‘Seleccionar todos’.

Control del click sobre la imagen
    void imgHeader_ClickEventHandler(object sender, ImageClickEventArgs e)
    {
        if (ImageClick != null) { ImageClick(sender, EventArgs.Empty); }
    }
Aquí simplemente verificamos que existe un EventHandler asociado a la imagen y en caso afirmativo, llamamos al delegado. Éste será el encargado de modificar los datos y actualizar el grid.

Control de los datos a mostrar
    void CB1_DataBinding(object sender, EventArgs e)
    {
        CheckBox chk = (CheckBox)sender; //Obtenemos el control
        GridViewRow container = (GridViewRow)chk.NamingContainer; //contenedor con datos
        object dataValue = DataBinder.Eval(container.DataItem, _columnName); //valor obtenido
        chk.Checked = (dataValue.ToString() == "1"); //actualización del check
    }

Aquí obtendremos el valor a mostrar en el CheckBox. Dado que en este ejemplo, en la DDBB tenemos una columna ‘Bit’, deberemos marcar la selección en función de si el valor es ‘0’ ó ‘1’.



Llegados a este punto, ya tenemos configurada nuestra plantilla completa para la columna del gridView. Recordad que en este ejemplo estamos trabajando con un CheckBox, pero se podría personalizar para cualquier tipo de control e información que os hiciese falta.

En el próximo post indicaré cómo hacer uso de esta clase, para generar nuestro grid personalizado.


Referencias:
MSDN Microsoft

Un saludo a todos!
[...]

Leer más >>

Agregar una propiedad en un control de usuario con C# y Atributos

En estos días he tenido la necesidad de hacer un control de usuario personalizado, de forma que se pudiera reutilizar en dos formatos distintos. Concretamente se trataba de un control que mostraba las etiquetas de un blog y que podía presentarse de dos formas distintas:
· en formato lista (tag list)
· en formato nube (o cloud tags)

Dado que los controles se cargan en la página mediante el uso de <%@ Register %>, quería que tuviese un parámetro opcional con valores personalizados (y uno por defecto) y que estuviese accesible, mostrando todos los valores posibles, desde la vista de diseño.

Evidentemente esto se puede realizar de muchas formas, pero quería utilizar los atributos existentes en .NET, por lo que el siguiente desarrollo está encaminado hacia ese uso.

GENERACIÓN DEL CÓDIGO

Lo primero que debemos crear es una enumeración con los valores que necesitamos controlar con posterioridad. Dado que en este caso, lo que queremos controlar es el tipo de "display" que vamos a ejecutar al cargar el control, vamos a generar una enumeración auxiliar llamada DisplayOption. Así, obtendríamos el siguiente fragmento de código:

public enum
DisplayOption
{
    ToList,
    ToCloud
}


Una vez generada nuestra enumeración, debemos crear una propiedad pública en nuestro control. De esta forma, el sistema identificará dicha propiedad con el valor que le asignemos y desde ese momento, podrá trabajar con él.

public
DisplayOption Display { get; set; }


Aunque el sistema podría reconocer directamente la propiedad pública, es bueno y recomendable hacer uso de los atributos, desde donde podremos indicarle si es accesible, a qué categoría pertenece, el valor por defecto y una breve descripción. Según esto, el código resultante sería:

[
Browsable(true),
Category("Appearance"),
DefaultValue(DisplayOption.ToList),
Description("Indica el tipo de desplegado usado para la visualización.")]
public DisplayOption Display { getset; }


NOTA: Se debe utilizar la librería "using System.ComponentModel;" para que el sistema reconozca los atributos utilizados.


Ya sólo nos queda modificar el código fuente del control para formatear la información según el valor que tenga la propiedad. Esto se podría hacer en la carga del control de forma:

protected void Page_Load(object sender, EventArgs e)
{
    if (Display == DisplayOption.ToList)
        GeneraListaDeEtiquetas();
    else
        GeneraNubeDeEtiquetas();
}


Una vez realizado este punto, ya podríamos acceder a dicha propiedad desde el diseñador, y además tendríamos listados en ella todos los elementos. Aunque no incluyesemos dicha propiedad en el diseñador, ya tendría un valor asignado por el atributo.


Así, tendríamos en la página contenedora el siguiente fragmento de código disponible:


<%@ Register TagPrefix="ctrl" TagName="NavTags" Src="~/controls/NavTagControl.ascx" %>

<ctrl:NavTags runat="server" ID="NavTagControl" Display="ToCloud"  />

Aquí podéis ver una captura del efecto final.





Enlaces de referencia:
User Controls Custom Properties
Walkthrough: Developing and Using a Custom Server Control
[...]

Leer más >>