Hace algún tiempo, tonteando y sin intención de hacer nada en concreto me puse a crear un Control Web para Visual Studio. Ahora, bastante tiempo después, vamos a ver como podemos crear un Web Control de forma sencilla (aunque no lo parezca), y ahorrarnos así mucho trabajo en nuestras aplicaciones asp.net.
Resultado
Esta galería de imágenes permite que el programador la personalice en colores, estilos CSS y maquetación, los textos que se muestran, si las imagenes se abren en la misma página o en una nueva, y por último la paginación, ya sea mostrar todas las imágenes en una página, o paginar eligiendo el número de imagenes por fila y página. Por otro lado, la maquetación que arroja es 100% CSS + Html. No puedo mostraros la aplicación en funcionamiento ya que el hosting es un Linux/Apache...
Creamos un nuevo Web Control Library, (en Visual C# para el caso), y vamos con el código:
En primer lugar, los espacios de nombres que usaremos son:
using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; using System.Drawing; using System.IO;
Ahora especificamos el espacio de nombres (NameSpace) que tendrá nuestro WebControl para asp.net y la Clase del mismo:
namespace ScourDesign { /// <summary> /// Para el correcto funcionamiento de esta galerÃa, las miniaturas y
las imágenes grandes deben tener el mismo nombre, estándo en carpetas distintas
dentro del directorio de nuestra aplicación web Asp.Net /// </summary> [DefaultProperty("RutaImagenesGrandes"), ToolboxData("<{0}:Galeria RutaImagenesGrandes=\"\"
RutaMiniaturas=\"\" Titulo=\"\" TamTitulo=\"Uno\" BackColor=\"#FFFFFF\" Firma=\"\"
numImagenesPorFila=\"4\" AnchoGaleria=\"760\" NumImagenesPorPagina=\"-1\" Paginacion=\"0\"
DocumentoAspx=\"\" GenerarEstilos=\"true\" BackColorFirma=\"transparent\" Target=\"_blank\"
runat=server></{0}:Galeria>")] public class Galeria : System.Web.UI.WebControls.WebControl {
Como se ve, el espacio de nombres es ScourDesign y la clase Galeria, de modo que la forma de instanciar posteriormente la librería será ScourDesign.Galeria. Por otro lado, el ToolboxData especifica como se escribirá el código de la instancia de la clase en el html de Visual Studio, que será así:
<cc1:Galeria RutaImagenesGrandes="" RutaMiniaturas="" Titulo="" TamTitulo="Uno"
BackColor="#FFFFFF" Firma="" numImagenesPorFila="4" AnchoGaleria="760" NumImagenesPorPagina="-1"
Paginacion="0" DocumentoAspx="" GenerarEstilos="true" BackColorFirma="transparent" Target="_blank"
runat=server></cc1:Galeria>
Hemos especificado que el control es de tipo WebControl (System.Web.UI.WebControls.WebControl), de este modo, de forma automática tendrá todas las propiedades, eventos y métodos de dicho espacio de nombres (otra cosa será que nosotros los usemos o no, pero igual los tendrá)
A continuación pasamos a la declaración de propiedades y variables públicas y privadas.
private string rutaImagenesGrandes; private string rutaMiniaturas; public enum tTitulo:int { Uno = 1, Dos = 2, Tres = 3, Cuatro = 4, Cinco = 5, Seis = 6, } private tTitulo tamTitulo; private string titulo; private string firma; private int numImagenesPorFila; private int anchoGaleria; private int numImagenesPorPagina; private int paginacion; private string documentoAspx; private bool generarEstilos; private Color backColorFirma; private string target; [Browsable(false)] public override string AccessKey { get { return ""; } set {} } [Browsable(false)] public override Color BorderColor { get { return Color.White; } set {} } [Browsable(false)] public override BorderStyle BorderStyle { get { return BorderStyle.Solid; } set {} } [Browsable(false)] public override Unit BorderWidth { get { return Unit.Pixel(0); } set {} } [Browsable(false)] public override string CssClass { get { return ""; } set {} } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Ruta relativa hasta la
carpeta que contiene las imágenes.")] public string RutaImagenesGrandes { get { return rutaImagenesGrandes; } set { rutaImagenesGrandes = value; } } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Ruta relativa hasta la
carpeta que contiene las miniaturas.")] public string RutaMiniaturas { get { return rutaMiniaturas; } set { rutaMiniaturas = value; } } [Bindable(true), Category("Appearance"), DefaultValue("Uno"), Description("Tamaño del
encabezado, de 1 (Mayor) a 6 (Menor).")] public tTitulo TamTitulo { get { return tamTitulo; } set { tamTitulo = value; } } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("TÃtulo que
aparecerá sobre la galerÃa. Dejar vacio si no se desea que aparezca.")] public string Titulo { get { return titulo; } set { titulo = value; } } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Firma que
aparecerá al pié de la galerÃa. Dejar vacio si no se desea que aparezca.")] public string Firma { get { return firma; } set { firma = value; } } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Firma que
aparecerá al pié de la galerÃa. Dejar vacio si no se desea que aparezca.")] public int NumImagenesPorFila { get { return numImagenesPorFila; } set { numImagenesPorFila = value; } } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Ancho en Pixels de la
galerÃa.")] public int AnchoGaleria { get { return anchoGaleria; } set { anchoGaleria = value; } } [Bindable(true), Category("Appearance"), DefaultValue(-1), Description("Numero de
imágenes por página. Especifique '-1' si desea listar todo el directorio en una sola página.")] public int NumImagenesPorPagina { get { return numImagenesPorPagina; } set { numImagenesPorPagina = value; } } [Bindable(true), Category("Appearance"), DefaultValue(0), Description("Número de página
actual. Especifique 0 si no desea que aparezcan los enlaces 'anterior/siguiente'")] public int Paginacion { get { return paginacion; } set { paginacion = value; } } [Bindable(true), Category("Appearance"), DefaultValue(""), Description("Página .aspx
en la que se muestra el documento (Necesario para la paginación). Ej: Default.aspx")] public string DocumentoAspx { get { return documentoAspx; } set { documentoAspx = value; } } [Bindable(true), Category("Appearance"), DefaultValue(true), Description("Si se especifica
'True', el control generará automáticamente los estilos CSS para maquetar y diseñar la GalerÃa")] public bool GenerarEstilos { get { return generarEstilos; } set { generarEstilos = value; } } [Bindable(true), Category("Appearance"), DefaultValue("transparent"), Description("Color
de fondo de la firma de la galerÃa")] public Color BackColorFirma { get { return backColorFirma; } set { backColorFirma = value; } } [Bindable(true), Category("Appearance"), DefaultValue("_blank"), Description("Ventana en
la que se abrirá la imagen.")] public string Target { get { return target; } set { target = value; } }
Algunos tipos de datos son los enumeradores; por decirlo de forma sencilla es una forma de limitar el número de valores y enmascararlos de forma más comprensible para el programador. Así, el enumerador:
public enum tTitulo:int { Uno = 1, Dos = 2, Tres = 3, Cuatro = 4, Cinco = 5, Seis = 6, }
Lo usaremos para poner los encabezados (<h1> a <h6>) al título de la galería según seleccione el programador, de modo que este no podrá seleccionar otro valor a parte de los dados, y con la comodidad de que los valores tienen una máscara de texto, que le da un significado más comprensible (aunque en este caso es igual de comprensible...).
Establecidas ya todas las propiedades públicas y pribadas de nuestro control de asp.net, vamos a renderizarlo, o lo que es lo mismo, contruirlo:
/// <summary> /// Procesar este control en el parámetro de salida especificado. /// </summary> /// <param name="output"> Programa de escritura HTML para escribir </param> //La función Render se utiliza siempre para renderizar o construir los controles protected override void Render(HtmlTextWriter output) { try { //Operaciones para la contrucción del control string ttit = TamTitulo.ToString(); int tTit = 1; switch (ttit) { case "Uno": tTit = 1; break; case "Dos": tTit = 2; break; case "Tres": tTit = 3; break; case "Cuatro": tTit = 4; break; case "Cinco": tTit = 5; break; case "Seis": tTit = 6; break; } string colorFondo = ""; /* */ if (BackColor.IsEmpty) { colorFondo = "#FFFFFF"; } else { if (BackColor.IsNamedColor) { colorFondo = BackColor.Name.ToString(); } else { colorFondo = "#" + BackColor.Name.Remove(0, 2); } } string colorFondoFirma = ""; if (BackColorFirma.IsEmpty) { colorFondoFirma = "transparent"; } else { if (BackColor.IsNamedColor) { colorFondoFirma = BackColorFirma.Name.ToString(); } else { colorFondoFirma = "#" + BackColorFirma.Name.Remove(0, 2); } } string[] sArchivos; FileInfo archivoInfo; string ruta = Page.Server.MapPath(""); if (RutaMiniaturas.Substring(0, 1) == "\\" || RutaMiniaturas.
Substring(0, 1) == "/index.html") { RutaMiniaturas = RutaMiniaturas.Remove(0, 1); } if (RutaMiniaturas.Substring(RutaMiniaturas.Length - 1, 1)
== "\\" || RutaMiniaturas.Substring(RutaMiniaturas.Length - 1, 1)
== "/index.html") { RutaMiniaturas = RutaMiniaturas.Remove(RutaMiniaturas.
Length - 1, 1); } if (RutaImagenesGrandes.Substring(0, 1) == "\\" ||
RutaImagenesGrandes.Substring(0, 1) == "/index.html") { RutaImagenesGrandes = RutaImagenesGrandes.Remove(0, 1); } if (RutaImagenesGrandes.Substring(RutaImagenesGrandes.Length
- 1, 1) == "\\" || RutaImagenesGrandes.Substring(
RutaImagenesGrandes.Length - 1, 1) == "/index.html") { RutaImagenesGrandes = RutaImagenesGrandes.Remove(
RutaImagenesGrandes.Length - 1, 1); } ruta = ruta + "\\" + RutaMiniaturas; sArchivos = Directory.GetFiles(ruta); int Total = sArchivos.Length; int i; int linea = 0; //output escribirá el código html que arrojará el control output.WriteLine(); //Créditos y licencia (si modifica el código, añada sus créditos,
pero no borre los actuales) output.WriteLineNoTabs("/articulos/tutoriales/net/lt___/index.html"); output.WriteLineNoTabs("//index.html"); output.WriteLineNoTabs(" GalerÃa Scour Design "); output.WriteLineNoTabs(" Autor: Carlos Carmona. "); output.WriteLineNoTabs(" Web: www.scourdesign.com "); output.WriteLineNoTabs(" Licencia GPL: General Public License "); output.WriteLineNoTabs(" Puede usar libremente este control Web "); output.WriteLineNoTabs(" O incluso modificar el código fuente "); output.WriteLineNoTabs(" Citando al autor y enlazando el WebSite. "); output.WriteLineNoTabs("//index.html"); output.WriteLineNoTabs("////////////////////////////////////// -->"); try { //El código html se genera con etiquetas con el atributo id y class
añadido, //Si el programador lo desea, puede utilizar la generación de estilos
automáticos //aquà especificados, si no, podrá utilizar su propio CSS utilizando
los //id y class de las etiquetas html que arroja el control if (GenerarEstilos == true) { output.WriteLineNoTabs("<script type=\"text/javascript\">"); output.WriteLineNoTabs("var auxiliar = navigator.appName;"); output.WriteLineNoTabs("var EstiloIE = '';"); output.WriteLineNoTabs("var EstiloBuenosNavegadores = '';"); output.WriteLineNoTabs("if (auxiliar.indexOf('Internet') >
-1) {"); output.WriteLineNoTabs("EstiloIE = '<style type=\\\"text/
css\\\">';"); output.WriteLineNoTabs("EstiloIE += '#ScourDesignGaleria
a:link { background-color: #FFFFFF; }';"); output.WriteLineNoTabs("EstiloIE += '#ScourDesignGaleria
a:visited { background-color: #D5DADE; }';"); output.WriteLineNoTabs("EstiloIE += '#ScourDesignGaleria
a:hover { background-color: #D8D2C0; }';"); output.WriteLineNoTabs("EstiloIE += '</style>';"); output.WriteLineNoTabs("document.write (EstiloIE);"); output.WriteLineNoTabs("} else if (auxiliar.indexOf('
Netscape') > -1) {"); output.WriteLineNoTabs("EstiloBuenosNavegadores = '<style
type=\\\"text/css\\\">';"); output.WriteLineNoTabs("EstiloBuenosNavegadores += '
#ScourDesignGaleria img:hover { background-color: #D8D2C0; }';"); output.WriteLineNoTabs("EstiloBuenosNavegadores += '
</style>';"); output.WriteLineNoTabs("document.write (
EstiloBuenosNavegadores);"); output.WriteLineNoTabs("}"); output.WriteLineNoTabs("</script>"); output.WriteLineNoTabs("<style type=\"text/css\">"); if (Firma == null) { output.WriteLineNoTabs("#Paginacion {
margin-bottom: 15px !Important; }"); } output.WriteLineNoTabs("#Paginacion a:link, #Paginacion
a:visited { text-decoration: none; background-color: transparent; color:
#67100E; font-weight: bold; }"); output.WriteLineNoTabs("#Paginacion a:hover {
background-color: transparent; color: #FF0000; }"); output.WriteLineNoTabs("#ScourDesignGaleria {
margin:0px; padding:0px; text-align:center; }"); output.WriteLineNoTabs("#Contenedor { position:
relative; margin: 0px auto; padding: 0px; background-color: "
+ colorFondo + "; width: " + AnchoGaleria + "px; border:1px solid #616161;
}"); output.WriteLineNoTabs("h1, h2, h3, h4, h5, h6 {
margin-top: 10px; }"); output.WriteLineNoTabs(".Fila { display:block;
text-align:center; }"); output.WriteLineNoTabs(".DivImagen { margin: 2px;
display:inline; }"); output.WriteLineNoTabs(".Enlace { width:100%; }"); output.WriteLineNoTabs(".Imagen { margin:5px; padding:
10px; border:1px solid #616161; }"); output.WriteLineNoTabs("#Firma { border: 1px solid #A2A2A2;
margin: 10px; padding: 10px; font-weight: bold; background-color: " +
colorFondoFirma + "; }"); output.WriteLineNoTabs("</style>"); } } catch {;} output.WriteLineNoTabs("<div id=\"ScourDesignGaleria\">"); output.WriteLineNoTabs("<div id=\"Contenedor\">"); try { if (Paginacion > 0) {;} } catch { Paginacion = 0; } try { if (NumImagenesPorPagina > 0) {;} } catch { NumImagenesPorPagina = -1; } try { //TÃtulo de la galerÃa if (Titulo != "" & Titulo.Length > 0) { output.WriteLineNoTabs("<h" + tTit + ">" + Titulo + "</h"
+ tTit + ">"); } } catch {;} int desde = 0; int hasta = Total - 1; try { for (i = 0; i < Total; i++) { if (i % NumImagenesPorPagina == 0) { if ((i / NumImagenesPorPagina) + 1 == Paginacion) { desde = i; hasta = i + NumImagenesPorPagina; } } } } catch {;} int Num = NumImagenesPorPagina; int TotalMostradas = 0; for (i = 0; i < Total; i++) { if (((i >= desde) & (i <= hasta)) || Paginacion < 1) { if (Num > 0 || Num == -1) { //los formatos de imagen permitidos son jpg, jpeg,
gif, png y bmp if (archivoInfo.Extension == "/articulos/tutoriales/net/.jpg" || archivoInfo.
Extension == "/articulos/tutoriales/net/.jpeg" || archivoInfo.Extension == "/articulos/tutoriales/net/.gif" || archivoInfo.
Extension == "/articulos/tutoriales/net/.png" || archivoInfo.Extension == ".bmp") { if (linea == 0) { output.WriteLineNoTabs("<div
class=\"Fila\">"); } output.Write("<div class=\"DivImagen\"><a
href=\""); output.Write(RutaImagenesGrandes + "/index.html" +
archivoInfo.Name); output.Write("\" class=\"Enlace\""); if (Target != null) { output.Write(" target=\"" + Target +
"\""); } output.Write("><img src=\""); output.Write(RutaMiniaturas + "/index.html" + archivoInfo.
Name); output.Write("\" class=\"Imagen\"></a></div>"); output.WriteLine(); linea = linea + 1; TotalMostradas = TotalMostradas + 1; if (linea == NumImagenesPorFila) { output.WriteLineNoTabs("</div>"); linea = 0; } } if (Num != -1) { Num = Num - 1; } } } } try { if (Paginacion > 0 & (!(TotalMostradas < NumImagenesPorPagina
& Paginacion == 1))) { if (Paginacion == 1) { output.WriteLineNoTabs("<div id=\"Paginacion\"> <a
href=\"" + DocumentoAspx + "?pag=" + (Paginacion + 1).ToString() +
"\">Siguiente</a></div>"); } else { string text = (Total / NumImagenesPorPagina).ToString(); string PagTotales = ""; for (i = 0; i < text.Length; i++) { if (text.Substring(i, 1) != "," & text.Substring(
i, 1) != ".") { PagTotales = PagTotales + text.Substring(i, 1); } else { break; } } if (Paginacion > Int32.Parse(PagTotales)) { output.WriteLineNoTabs("<div id=\"Paginacion\">
<a href=\"" + DocumentoAspx + "?pag=" + (Paginacion - 1).ToString() +
"\">Anterior</a></div>"); } else { output.WriteLineNoTabs("<div id=\"Paginacion\"> <
a href=\"" + DocumentoAspx + "?pag=" + (Paginacion - 1).ToString() + "\">
Anterior</a> - <a href=\"" + DocumentoAspx + "?pag=" + (Paginacion + 1).
ToString() + "\">Siguiente</a></div>"); } } } if (Firma != "" & Firma.Length > 0) { output.WriteLineNoTabs("<div id=\"Firma\">" + Firma + "
</div>"); } } catch {;} output.WriteLineNoTabs("</div>"); output.WriteLineNoTabs("</div>"); } catch { output.Write("<p>GalerÃa Scour Design. <br>La maquetación se
generará <br>en tiempo de ejecución.</p>"); } }
La clase HtmlTextWriter, instanciada aquí en la variable output, sirve para escribir o arrojar el código html final de nuestro control web. El método HtmlTextWriter.WriteLineNoTabs escribe un salto de linea y retorno de carro al final del HTML, de modo que si alguien mira el código fuente de la web verá el código más limpio que si estubiese todo en una línea.
Con esto, compilamos el Control Web y vamos a crear nuestra galería.
Abrimos Visual Studio y creamos un nuevo proyecto web. Para añadir a Visual Studio el nuevo control, hacemos clic derecho del ratón sobre la columna izquierda y le damos a Add/Remove Items...
Del cuadro que se abrirá, seleccionamos el control o le damos a "examinar" si no aparece en la lista.
Una vez añadido el control web, solo tendremos que seleccionarlo y arrastrarlo a la página como haríamos con cualquier otro WebControl. Si seleccionamos el control ahora ya instanciado en nuestro Web Form, podremos ver que tiene todas las propiedades públicas que hemos especificado en el control Web, más todas las del espacio de nombres WebControl. trastearlas y probad distintas convinaciones.
Para concluir, si deseamos que la galería tenga paginación, en el evento Page_Load de nuestro web form añadiremos el siguiente código:
protected ScourDesign.Galeria Galeria2; private void Page_Load(object sender, System.EventArgs e) { if (!(Page.IsPostBack)) { if (Request.QueryString["pag"] == null) { Response.Redirect("/articulos/tutoriales/net/WebForm1_pag_1.html"); } else { try { Galeria2.Paginacion = Int32.Parse(Request.QueryString["pag"].
ToString()); } catch { Response.Redirect("/articulos/tutoriales/net/WebForm1_pag_1.html"); } } } }
<cc1:galeria id="Galeria2" runat="server" RutaImagenesGrandes="imagenes/max"
RutaMiniaturas="imagenes/min" Titulo="GalerÃa de Imágenes Scour Design"
TamTitulo="Uno" BackColor="#F2F2EE" numImagenesPorFila="4" AnchoGaleria="650"
NumImagenesPorPagina="8" DocumentoAspx="/articulos/tutoriales/net/WebForm1.html" Firma="Scour Design © -
Control de Servidor Web (Visual C#.NET)" BackColorFirma="218, 214, 197"
tabIndex="1" GenerarEstilos="true" Target="_blank"></cc1:galeria>
<cc1:galeria id="Galeria2" runat="server" RutaImagenesGrandes="imagenes/max"
RutaMiniaturas="imagenes/min" Titulo="GalerÃa de Imágenes Scour Design"
TamTitulo="Uno" BackColor="#F2F2EE" numImagenesPorFila="4" AnchoGaleria="650"
NumImagenesPorPagina="8" DocumentoAspx="/articulos/tutoriales/net/WebForm1.html" Firma="Scour Design © -
Control de Servidor Web (Visual C#.NET)" BackColorFirma="218, 214, 197"
tabIndex="1" GenerarEstilos="true" Target="_blank"></cc1:galeria>
<cc1:galeria id="Galeria2" runat="server" RutaImagenesGrandes="imagenes/max"
RutaMiniaturas="imagenes/min" Titulo="GalerÃa de Imágenes Scour Design"
TamTitulo="Uno" BackColor="#F2F2EE" numImagenesPorFila="6" AnchoGaleria="900"
NumImagenesPorPagina="-1" DocumentoAspx="/articulos/tutoriales/net/WebForm1.html" Firma="Scour Design © -
Control de Servidor Web (Visual C#.NET)" BackColorFirma="218, 214, 197"
tabIndex="1" GenerarEstilos="true" Target="_blank" Paginacion="0"></cc1:galeria>
Éste es un Control Web Simple. La diferencia entre Controles Web Simples y Controles Web Compuestos reside en que los segundos son Controles creados a partir de otros controles. Al principio especificamos que nuestra Control de Galería para Imágenes heredase de la Clase WebControl, sin embargo, podría suceder que en nuestra galería quisieramos poner, por ejemplo, un TextBox y un botón para que la gente añadiese algún comentario; para ello, deberíamos haber añadido la declaración de que nuestro Control Web va a contener otros controles a su vez, esto es, la claúsula INamingContainer.
public class Galeria : System.Web.UI.WebControls.WebControl, INamingContainer
Ahora ya podríamos añadir otros Controles Web.
Por último, añadir que la clase HtmlTextWriter, tiene muchos métodos para escribir HTML, tales como métodos para inicio y fin de etiquetas (WriteBeginTag y WriteEndTag), métodos para añadir atributos (WriteAttribute y WriteStyleAttribute), etc... Sin embargo vereis que yo no he usado nada de esto, sencillamente he escrito el código html y css directamente en los métodos Write y WriteLineNoTabs, para escribirlos tal cual. ¿Por qué? Por que el código html que arroja el .NET Framework 1.1 es malísimo, muchísimo más si pretendemos maquetar el resultado final con CSS. Si quereis utilizar un HTML de calidad no os recomiendo dejar en manos del .NET Framework esta tarea. Suficientemente mal lo hemos pasado muchos a la hora de maquetar con CSS el código arrojado por el Visual Studio y conseguir que se vea igual en IE y FireFox. Escribid y controlar vuestro código, aunque sea para que lo use otro programador... mejor dicho, sobre todo si es para que lo use otro programador.
Al control se le pueden añadir muchas más propiedades que serían muy interesantes, sobre todo con respecto a los estilos. Lo dejo en vuestras manos.