Controles Personalizados en ASP.NET – Parte 2

En la entrada anterior, vimos como extender el control TextBox (del .NET Framework) para restringir la entrada a sólo ciertos caracteres definidos en una expresión regular. Pues bien, en esta ocasión vamos a crear un control que ayudará a medir la popularidad de algún artículo o comentario.

Para la apariencia del control, tomé como base "CSS Star Rating", el cuál incluye una hoja de estilos y una imagen. Como ya había comentado en la entrada anterior, para hacer uso de estos elementos es necesario registrarlos y compilarlos, a continuación se muestra la forma de hacerlo:

csharp:
// AssemblyInfo.cs
[assembly: System.Web.UI.WebResource("Buayacorp.Web.Resources.rating.css", "text/css", PerformSubstitution = true)]
[assembly: System.Web.UI.WebResource("Buayacorp.Web.Resources.star.gif", "image/gif")]

Como habrán podido observar, puse el atributo PerformSubstitution=true para poner expresiones del tipo background: url('<%= WebResource("Buayacorp.Web.Resources.star.gif") %>') top left repeat-x; dentro de rating.css (es necesario hacer esto ya que si sólo se pone background: url(star.gif) top left repeat-x;, no funcionará)

A continuación, una parte del código del control:

csharp:
// BCRating.cs
public class BCRating : WebControl, IPostBackEventHandler
{
        #region Propiedades
        // Sirve para indicar si se usa una hoja de estilos personalizada
        public bool UseCustomCss {/*...*/}
        // Sirve para obtener el valor que ha sido seleccionado por el usuario
        public int SelectedRating {/*...*/}
        // Sirve para establecer el promedio de valoración
        public double Value {/*...*/}
        #endregion

        #region "Renderizado" del control
        protected override void Render(HtmlTextWriter writer)
        {
                // Asegurar que el control está dentro de un formulario con atributo runat=server
                Page.VerifyRenderingInServerForm(this);
               
                if (!DesignMode)
                {
                        RenderCss(writer);
                        CreateLinks(writer);
                }
                else// Escribir el ID del control si el control está en modo de diseño
                        writer.Write(ID);
        }

        private void RenderCss(HtmlTextWriter writer)
        {
                // Sólo agregar un tag <link /> a la página, aún cuando haya varios controles
                if (HttpContext.Current.Items["rating_ctrl"] == null && !UseCustomCss)
                {
                        // Obtenemos la URL del recurso embedido
                        string resource = Page.ClientScript.GetWebResourceUrl(GetType(), "Buayacorp.Web.Resources.rating.css");

                        // Crear el tag <link /> y agregarlo a la colección de controles
                        HtmlLink lnk = new HtmlLink();
                        lnk.Href = resource;
                        lnk.Attributes["rel"] = "stylesheet";
                        lnk.Attributes["type"] = "text/css";

                        Controls.Add(lnk);
                        lnk.RenderControl(writer);

                        HttpContext.Current.Items["rating_ctrl"] = true;
                }
        }

        private void RenderLinks(HtmlTextWriter writer)
        {
                // Agregar el tag: <ul class="star-rating">
                writer.AddAttribute(HtmlTextWriterAttribute.Class, "star-rating");
                writer.RenderBeginTag(HtmlTextWriterTag.Ul);

                // Calcular el ancho del control, en base al promedio de valoración
                // 25 -> equivale al ancho de la imagen que forma parte del control (Mejorar esta parte!)
                int width = (int)Math.Floor(25 * Value);

                // Agregar un tag <li class="current-rating">Actualmente X/5<li>
                writer.AddAttribute(HtmlTextWriterAttribute.Class, "current-rating");
                writer.AddAttribute(HtmlTextWriterAttribute.Style, string.Format("width:{0}px", width));
                writer.RenderBeginTag(HtmlTextWriterTag.Li);
                writer.Write(string.Format("Actualmente {0}/5", Value));
                writer.RenderEndTag();

                // La valoración es de 1-5
                links = new HtmlAnchor[5];
                for (int i = 0; i < links.Length; i++)
                {
                        // Crear tags de la forma <li><a href="..." class="starX" title="X de 5">X</a></li>
                        links[i] = new HtmlAnchor();
                        links[i].EnableViewState = false;
                        links[i].InnerText = (i + 1).ToString();
                        links[i].HRef = Page.ClientScript.GetPostBackClientHyperlink(this, (i + 1).ToString());
                        links[i].Attributes["class"] = "star" + (i + 1).ToString();
                        links[i].Title = (i + 1) + " de " + links.Length;
                        Controls.Add(links[i]);

                        writer.RenderBeginTag(HtmlTextWriterTag.Li);
                        links[i].RenderControl(writer);
                        writer.RenderEndTag();
                }
                writer.RenderEndTag();
        }
        #endregion

        private HtmlAnchor[] links;

        public event EventHandler SelectedRatingChanged;
       
        #region IPostBackEventHandler Members

        void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
        {
                if (!string.IsNullOrEmpty(eventArgument))
                {
                        SelectedRating = int.Parse(eventArgument);

                        if (SelectedRatingChanged != null)
                                SelectedRatingChanged(this, EventArgs.Empty);
                }
        }

        #endregion
}

En el código mostrado arriba, sólo quiero comentar la implementación de la interfaz IPostBackEventHandler, ésto posibilitará que nuestro control maneje los postbacks y genere los eventos necesarios.

El control mostrado tiene algunas deficiencias y poca flexibilidad para personalizarlo, se deja como tarea para el lector solucionar estas cosas. :-)

Información relacionada