Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Posts Tagged ‘control’

Suspender el dibujado de un control o formulario

Posted by Gerardo Contijoch en septiembre 14, 2009

Hay ocasiones en que nuestros Forms terminan recargados de controles, los cuales cambian de estado o contenido todos a la vez. Si la cantidad de controles es mucha y el contenido de los mismo es modificado varias veces en un breve intervalo de tiempo o simplemente el redibujado de los mismos es muy lento (es decir, el usuario nota como se va llenando cargando el contenido del Form progresivamente), podemos optar por suspender el redibujado del form en su totalidad hasta que los controles estén listos para ser dibujados.

Muy relacionado con mi último post, les dejo el link al código de una clase que se encarga de suspender y reanudar el redibujado de un control (o form en su totalidad si lo deseamos). La técnica se basa en el envío de mensajes al control o ventana en cuestión.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Anuncios

Posted in .Net 2.0, C# | Etiquetado: , | 4 Comments »

Encontrar el valor de un Window Message

Posted by Gerardo Contijoch en septiembre 12, 2009

De vez en cuando, si trabajamos con WinForms, nos puede surgir la necesidad de manejar un evento a muy bajo nivel y para hacerlo tenemos que interceptar los famosos Window Messages, o mensajes de ventana. Estos mensajes son el mecanismo que utiliza Windows para comunicarse con una aplicación. Cuando apretamos una tecla y nuestra aplicación responde, parece que es nuestra aplicación la que ‘atrapó’ el teclazo, pero en realidad es el Sistema Operativo (SO) el que lo atrapó (debido a que el SO es quien controla el puerto donde esta conectado el teclado) y este le envía un mensaje a nuestra aplicación para que responda al mismo, normalmente imprimiendo el caracter representado por la tecla presionada. Aunque no lo parezca, el SO es quien tiene control de todo lo que sucede y cuando nuestra aplicación responde a distintos inputs, es en realidad el SO el que le ordena que haga una cosa u otra.

Si tenemos un Form y el SO determina que es necesario que sea redibujado (porque cambio su contenido por ejemplo), entonces se envía un mensaje conocido como WM_PAINT (en .NET veremos que esto dispara el evento Paint), en cambio si presionamos la tecla ‘Q’ en el teclado, entonces el mensaje recibido por nuestra aplicación es WM_KEYDOWN (también tenemos el evento KeyDown en .NET, el cual es lanzado cuando se recibe un mensaje de este tipo). Cabe aclarar que el nombre de los mensajes es solo una ayuda para nosotros los humanos, ya que los mensajes en realidad no son más que un valor entero, y estos nombres son los nombres de las constantes asociadas a los mismos.

Para interceptar estos mensajes lo que se suele hacer comúnmente es sobrescribir el método WndProc definido en la clase Control de la siguiente manera:

   1: ...
   2: private const int WM_PAINT = 15;
   3:
   4: protected override void WndProc(ref Message m) {
   5:
   6:     if (m.Msg == WM_PAINT) {
   7:         // Atrapamos el mensaje...
   8:     } else{
   9:         // Ignoramos el mensaje y dejamos que siga su curso
  10:         base.WndProc(ref m);
  11:     }
  12:
  13: }
  14: ...

El método WndProc es el que se encarga de procesar todos los mensajes que recibe un Form y es por eso que ese es el punto indicado para atraparlos.

Ahora el problema esta en conocer el valor de los mensajes, ya que muchas veces uno sabe que mensaje procesar, pero no sabe cual es su valor. Estos valores están definidos en la clase System.Windows.Forms.NativeMethods y System.Design.NativeMethods, pero lamentablemente estas clases son internas y no tenemos acceso a ellas, por lo que vamos a tener que usar .NET Reflector para ver su contenido.

La info de este post fue sacada de aquí.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in .Net 2.0, C# | Etiquetado: , , | Leave a Comment »

Un CheckBoxList que funciona en ASP.NET MVC

Posted by Gerardo Contijoch en julio 4, 2009

Si buscamos en internet ‘CheckBoxList ASP.NET MVC’ encontraremos infinidad de páginas con quejas de la desaparición del método de extensión CheckBoxList() de la clase HtmlHelper en la Preview 5 de ASP.NET MVC (antes de la versión oficial). Este es un problema de aparente fácil resolución ya que el método CheckBox() sigue estando presente, por lo que una lista de CheckBoxes podría crearse con un código similar al siguiente:

   1: <% for (int i = 0; i < 4; i++) {%>
   2:   <%= Html.CheckBox("checks", (object)new { value = "val" + i.ToString() })%><%= "Check " + i.ToString() %><br/>
   3: <%}%>

Sin embargo, rápidamente nos vamos a encontrar con dos problemas. El primero de ellos se hace presente dentro del código de la acción que se ejecuta al postear el form.

Dada la siguiente vista:

   1: <% using (Html.BeginForm("Index1", "Home", FormMethod.Post)) { %>
   2:     <% for (int i = 0; i < 4; i++) {%>
   3:         <%= Html.CheckBox("checks1", (object)new { value = "val" + i.ToString() })%><%= "Check " + i.ToString() %><br/>
   4:     <%}%>
   5:     <input type="submit" value="Post!" />
   6: <%}%>

veamos como recibimos los valores de los CheckBoxes si tildamos sólo los dos primeros CheckBoxes (los asociados a los valores ‘val0’ y ‘val1’):

aspnetmvc-checkboxes-valoresChecks1

Como se puede apreciar, lo que recibimos es un array de valores un tanto desconcertante. Esto se debe a la manera en que postea el valor de los CheckBoxes.

Posteo de Checkboxes

Un CheckBox no es más que un input de tipo checkbox en una página, el cual puede tener o no un valor asociado. A diferencia de como funciona un input de tipo text (un TextBox), el valor del checkbox sólo se postea si el mismo esta tildado. Esto significa que si en un form HTML no tildamos un CheckBox, su valor no será enviado al servidor, lo cual puede traer muchos problemas en ASP.NET MVC si nuestra acción (la asociada al form que posteamos) tiene que recibir como parámetro el valor del CheckBox, ya que la llamada fallará cuando este no este tildado y el DefaultModelBinder no pueda asignarle un valor al parámetro asociado al mismo (debido a que no se posteó).

Es por este problema que el método de extensión CheckBox renderiza dos inputs en vez de uno solo. El primer input es el propio CheckBox (un imput de tipo ‘checkbox’), y el segundo es un input de tipo hidden llamado igual, pero cuyo valor esta hardcodeado a ‘false’. Es decir, dado el siguiente código:

   1: <%= Html.CheckBox("checkbox") %>"Check"

se renderiza lo siguiente en la página:

   1: <input id="checkbox" type="checkbox" value="true" name="checkbox"/>
   2: <input type="hidden" value="false" name="checkbox"/>
   3: Check
   4: <br/>

Así, si posteamos el form sin tildar el CheckBox, se postea el valor del input oculto, es decir, para la variable del post ‘checkbox’ se asigna el valor ‘false’ (que en el servidor es transformado en el booleano False). En cambio, si tildamos el CheckBox, se postea el valor ‘true, false’ (uno por cada input). Este valor es procesado por el DefaultModelBinder y lo interpreta como el booleano True.

Posteo de más de un CheckBox

Hay que reconocer que es un método bastante ingenioso de resolver el problema, pero lamentablemente está pensado para funcionar sólo con un único CheckBox. Volviendo al ejemplo que presenté al comienzo de este post, se puede ver ahora que el array que recibimos en nuestra acción esta conformado por los valores asociados a cada uno de los CheckBoxes renderizados, esto es, se posteo ‘val0, false’ para el primer CheckBox, ‘val1, false’ para el segundo, ‘false’ para el tercero, y ‘false’ para el cuarto (recordemos que los dos últimos CheckBoxes no fueron tildados). Esta forma de recibir los parámetro puede ser bastante problemática. Por un lado, no podemos procesar los valores con un foreach ya que no todos los valores nos interesan, sólo aquellos que son distintos a ‘false’ nos resultan útiles. Uno podría discriminar estos valores para recuperar sólo el set que nos interesa, pero esto es posible únicamente si no posteamos valores booleanos ya que si efectivamente queremos un valor ‘false’ cuando no tildamos un CheckBox, no vamos a poder ubicarlo fácilmente dentro del array. Es verdad que uno puede imaginarse una lógica un tanto compleja en donde si se encuentra un valor ‘true’, entonces ignora el siguiente valor (que debería ser ‘false’), pero esto tiene una utilidad limitada, ya que muy posiblemente nos topemos con el segundo de los problemas que mencioné anteriormente.

Este segundo problema es similar al del posteo de los valores, pero se presenta a la hora de mostrarlos a los valores. Cuando posteamos un form en ASP.NET MVC los parámetros de las acciones se cargan en el ModelState, lo cual nos permite, al momento de renderizar la vista inicializar los valores de los controles con los valores posteados para que los mismos no aparezcan vacíos (recordemos que la web es stateless y luego de un post las páginas se vuelven a cargar desde cero). El modo de hacerlo es matcheando los nombres de los controles (atributo name) con las entradas del ModelState. Así, si tenemos un TextBox llamado ‘txtNombre’, ASP.NET MVC va a crear una entrada dentro del ModelState con el nombre ‘txtNombre’ asociado al valor posteado en el TextBox. Al momento de renderizar la página se consulta el ModelState y se cargan los valores posteados con anterioridad. Particularmente en nuestro caso, lo que se carga en el ModelState es un arrays de valores, el cual esta asociado a todos los CheckBoxes en el form. Y eso es un problema ya el método CheckBox() no esta preparado para procesar un array de valores, sino un único valor que determina si un CheckBox esta o no tildado (ese código puede verse claramente cerca de la línea 153 de la clase InputExtensions en el código fuente de ASP.NET MVC). Como consecuencia de esto, si tenemos más de un CheckBox con el mismo nombre en el form, los mismos no van a recuperar su estado anterior.

Un CheckBoxList que funciona

Dado ese problema, decidí crear un nuevo CheckBoxList personalizado (hay un par de implementaciones dando vueltas, pero no encontré ninguna que resolviera el segundo problema).

El código es bastante sencillo y esta ‘inspirado’ en el código del CheckBox original de ASP.NET MVC.

   1: public static partial class HtmlHelperExtensions {
   2:
   3:     public static string CheckBoxList(this HtmlHelper htmlHelper, string name, IEnumerable<string> values, object htmlAttributes) {
   4:         return CheckBoxList(htmlHelper, name, values, values, htmlAttributes);
   5:     }
   6:
   7:     public static string CheckBoxList(this HtmlHelper htmlHelper, string name, IEnumerable<string> values, IEnumerable<string> labels, object htmlAttributes) {
   8:         // No creamos ningun CheckBox si no hay valores
   9:         if (values == null) {
  10:             return "";
  11:         }
  12:
  13:         if (labels == null) {
  14:             labels = new List<string>();
  15:         }
  16:
  17:         RouteValueDictionary attributes = htmlAttributes == null ? new RouteValueDictionary() : new RouteValueDictionary(htmlAttributes);
  18:         attributes.Remove("checked");
  19:
  20:         StringBuilder sb = new StringBuilder();
  21:
  22:         string[] modelValues = new string[] { };
  23:
  24:         ModelState modelState;
  25:         if (htmlHelper.ViewData.ModelState.TryGetValue(name, out modelState)) {
  26:             modelValues = ((string[])modelState.Value.RawValue);
  27:         }
  28:
  29:         // Por cada valor pasado generamos un CheckBox
  30:
  31:         IEnumerator<string> labelEnumerator = labels.GetEnumerator();
  32:         foreach (string s in values) {
  33:             // Si el array contiene el valor correspondiente a este checkbox, entonces fue chequeado
  34:             bool isChecked = modelValues.Contains(s);
  35:             sb.Append(CrearCheckBox(name, s, isChecked, attributes));
  36:
  37:             labelEnumerator.MoveNext();
  38:             if (labelEnumerator.Current != null) {
  39:                 sb.AppendLine(labelEnumerator.Current);
  40:             }
  41:         }
  42:
  43:         // Creamos el div contenedor
  44:         TagBuilder divTag = new TagBuilder("div");
  45:         divTag.InnerHtml = sb.ToString();
  46:
  47:         // No nos olvidemos de indicar si hay un error en alguno de los checks
  48:         if (modelState != null && modelState.Errors.Count > 0) {
  49:             divTag.AddCssClass(HtmlHelper.ValidationInputCssClassName);
  50:         }
  51:
  52:         return divTag.ToString(TagRenderMode.Normal);
  53:     }
  54:
  55:     private static string CrearCheckBox(string name, string value, bool isChecked, IDictionary<string, object> htmlAttributes) {
  56:         TagBuilder tagBuilder = new TagBuilder("input");
  57:         tagBuilder.MergeAttributes(htmlAttributes);
  58:         tagBuilder.MergeAttribute("type", "checkbox");
  59:         tagBuilder.MergeAttribute("name", name, true);
  60:
  61:         tagBuilder.GenerateId(name);
  62:
  63:         if (isChecked) {
  64:             tagBuilder.MergeAttribute("checked", "checked");
  65:         }
  66:
  67:         if (value != null) {
  68:             tagBuilder.MergeAttribute("value", value, true);
  69:         }
  70:
  71:         return tagBuilder.ToString(TagRenderMode.SelfClosing);
  72:     }
  73: }

Su uso es muy sencillo:

   1: <%
   2:    List<string> values = new List<string>() { "val0", "val1", "val2", "val3", "val4" };
   3:    List<string> labels = new List<string>() { "Check 0", "Check 1", "Check 2", "Check 3", "Check 4" };
   4: %>
   5: <%= Html.CheckBoxList("checks3", values, labels, null) %>

Los valores en la acción son recibidos así:

aspnetmvc-checkboxes-valoresChecks2

El código podría mejorarse un poco más agregando la posibilidad de aplicarles estilos al div contenedor de los CheckBoxes, usar labels reales en vez de solo texto, agregar sobrecargas a los métodos, etc., pero preferí dejarlo así porque era más sencillo. Es preciso aclarar que este CheckBoxList, al igual que la primer alternativa que presenté, no va a funcionar bien con valores booleanos ya que la idea es que los valores asociados a cada uno de los CheckBoxes sean únicos.

Si encuentran algún bug o tienen algo que aportar, no duden en comentarlo.

¡Nos vemos en el próximo post!

Publicado originalmente en https://gerardocontijoch.wordpress.com.

Posted in ASP.NET MVC, Desarrollo Web | Etiquetado: , , , | 3 Comments »