Surface en raona…

Hola! En raona tenemos desde hace algún tiempecillo la Surface… Lo que os quiero mostrar no es nada más que un pequeño vídeo de una aplicación que hemos desarrollado para ella: el Surface Shooter.

¿Y que es? Pues como bien se encarga de explicar mi compañero Juan Carlos Viñas permite navegar por los documentos de un portal de sharepoint usando la Surface.

Os dejo el enlace al youtube donde está el video:

Video Raona Surface Shooter (youtube)

En raona pensamos que la Surface ofrece grandes oportunidades, tanto a clientes como a proveedores, aunque es cierto que es necesario que nos olvidemos de todos los conceptos que tenemos preconcebidos en cuanto a como debe ser presentada una interfaz gráfica (os recomiendo que os leais este enorme post del no menos grande Miguel: Microsoft Surface: Developing NUI Touch Experiences).

Un saludo a todos!!! 🙂

Pexcando errores en nuestro código…

Buenas… ¿Conoceis Pex? Es una herramienta que genera tests unitarios a partir de nuestro código. Su característica principal es que analiza el código e intenta generar tests unitarios que cubran todas las posibilidades de nuestro código.

Vamos a ver un pequeño ejemplo de como usarlo y como se integra con Microsoft Code Contracts. Antes que nada os recomiendo echar un vistazo al genial Post de Jorge Serrano Precondiciones y Microsoft Code Contracts v1.0.

Vamos a hacer un método muy simple (y conocido por aquellos que desarrollen en VB.NET):

 public static class StringExtensions
{
     public static string Right(this string value, int chars)
     {
         return value;
     }
}

El método Right debe devolver los últimos chars carácteres de la cadena value.

Simplemente con este código nos descargamos Pex y lo instalamos. Una vez hecho vereis que nos aparece una nueva opción el menú contextual del “Solution Explorer” llamada “Run Pex Explorations”. Con esta opción lo que hacemos es que Pex analice todo nuestro código buscando generar Tests unitarios para nuestros métodos públicos:

image

Si ejecutamos esta opción Pex analiza el código y nos genera tests unitarios para el método Right:

image

 

En este caso nos ha generado un solo test. Es importante saber como Pex genera los tests: no lo hace al azar, sinó que intenta cubrir el máximo de nuestro código. En nuestro caso como no nuestro código apenas hace nada, con un solo test, Pex obtiene una cobertura del 100% y se queda ahí. Evidentemente Pex nada sabe de cual debe ser el resultado de nuestro método, por lo que no puede generar tests unitarios funcionales.

Ahora antes de empezar a analizar código vamos a ver el uso de Code Contracts. Una vez instalada, añadís la referencia a “Microsoft Contracts Library” (Microsoft.Contracts.dll). En el framework 4.0 parece ser que se incluirá el código dentro de mscorlib, por lo que no será necesario usar referencia alguna.

Ya estamos listos para definir los contratos en nuestra clase. Para ello debemos especificar las precondiciones de nuestro método. Una precondición es algo que debe cumplirse sí o sí para asegurar que la llamada  anuestro método es “válida”.

Una precondición de nuestro método es que NO vamos a aceptar que la cadena de entrada sea nula. El método Contract.Requires sirve para especificar las precondiciones:

public static string Right(this string value, int chars)
{
    Contract.Requires(value !=null);
    return value;
}

Si lanzamos Pex ahora vemos que… no ocurre nada distinto. Esto es porque por defecto los contratos no se evalúan. Si queremos que se evalúen debemos irnos a la pestaña “Code Contracts” de las propiedades del proyecto y habilitar “Perform runtime contract checking”. Una vez hecho esto, volvemos a ejecutar Pex y obtenemos lo siguiente:

image

Vemos que ahora Pex nos ha generado dos tests: uno que pase el contrato (le manda una cadena con un ‘’ y otro que no (le manda null).

Otra pecondición que vamos a usar es que el segundo parámetro debe ser mayor que cero:

public static string Right(this string value, int chars)
{
    Contract.Requires(value !=null);
    Contract.Requires(chars > 0);
    return value;
}

 

Una vez establecidas las precondiciones nos interesa establecer las postcondiciones, es decir todo aquello que aseguramos que se cumple al finalizar el método. Para ello usamos Contract.Ensures. P.ej. para añadir una postcondición que asegure que nunca devolveremos null:

public static string Right(this string value, int chars)
{
    Contract.Requires(value !=null);
    Contract.Requires(chars > 0);
    Contract.Ensures(Contract.Result<string>() != null);
    return value;
}

Una cosa realmente importante: Contract.Ensures se evalúa SIEMPRE al final del método… aunque como en este caso lo hayamos colocada antes del return, se evaluará después del return. El secreto está en que Code Contracts viene con un MSIL rewriter que modifica el código MSIL desplazando las llamadas a Contract.Requires al principio del método y de Contract.Ensures al final. El método Contract.Result<T> sirve para acceder al valor retornado por el método.

Contract.Requires no deja obsoleto a Debug.Assert. Ambos son necesarios: Con Contract.Requires comprobaremos las precondiciones lógicas de nuestro método, mientras que Debug.Assert lo seguiremos usando para comprobaciones de depuración (comprobaciones internas).

Tenemos el contrato de nuestro método especificado, tenemos a Pex para que nos genere tests unitarios… podemos empezar a meter código!

 public static class StringExtensions
{
     public static string Right(this string value, int chars)
     {
         Contract.Requires(value !=null);
         Contract.Requires(chars > 0);
         Contract.Ensures(Contract.Result<string>() != null);
         if (chars >= value.Length) return value;
         else
         {
             int inicial = value.Length - chars;
             if (inicial < 0) inicial = 0;
             return value.Substring(inicial, chars);
         }
     }
}

Una vez tenemos el código ya listo (o eso creemos jejejeeee…) relanzamos Pex para que nos cree un nuevo conjunto de tests unitarios para nuestro método. Como ahora tenemos un código un poco más complejo, Pex nos creará varios tests unitarios:

image

Pex ha creado 4 tests unitarios con el objetivo de cubrir al máximo nuestro código. A partir de este punto si queremos podemos generar un proyecto de test con estos 4 tests unitarios. Los podemos seleccionar desde la ventana de Pex Exploration Results y le dais a “Save”:

image

Con ello Pex nos genera el proyecto de tests unitarios:

image

El proyecto tiene dos archivos especiales: El fichero StringExtensionsTest.cs y el fichero StringExtensionsTest.Right.g.cs.

El segundo (.g.cs) se regenera cada vez que ejecutamos Pex, por lo que NO debemos poner código en él. El primero por su parte se mantiene y contiene lo que Pex llama PUTs, o Parametrized Unit Tests. Un PUT es un test unitario pero que acepta parámetros. Pex los usa para poder “agrupar” clausulas Assert: En lugar de colocar un Assert para cada test unitario, coloca uno de solo en el PUT y los tests unitarios que genera Pex, son llamadas al PUT con los parámetros correspondientes.

P.ej. el código del PUT generado es:

[PexMethod]
public string Right(string value, int chars)
{
    // TODO: add assertions to method StringExtensionsTest.Right(String, Int32)
    string result = StringExtensions.Right(value, chars);
    return result;
}

Como se puede ver no hace nada salvo llamar al método real que estamos testeando pasándole los parámetros. Pero aquí nosotros podríamos poner Asserts adicionales, que se comprobarían para todos los unit tests de Pex. El código de un unit test de Pex es similar a:

[TestMethod]
[PexGeneratedBy(typeof(StringExtensionsTest))]
public void Right04()
{
    string s;
    s = this.Right("", 1);
    Assert.AreEqual<string>("", s);
}

La llamada a this.Right es la llamada al PUT que ha generado antes.

Os dejo un par de videos con más información sobre Code Contracts y Pex para que les echeis un vistazo, dado que es un tema que vale realmente la pena!

Saludos!

Curiosidad (o no) en WinForms y focus…

Os cuento una curiosidad de la que me acabo de dar cuenta ahora mismo… Un funcionamiento, como mínimo curioso en Winforms… siempre entendiendo como curioso que “yo no lo sabía y mi no encaja en mi (poco) sentido común”.

El titular sensacionalista sería: Control desactivado recibe el focus.

La realidad es que si en el evento “Leave” de un control, desactivamos el siguiente control que debe recibir el foco, este control NO recibe el foco (como es de esperar) pero su evento “Enter” se ejecuta…

… y no sólo eso! La siguiente vez que pulsemos TAB (o que con el mouse pongamos el foco en cualquier otro control incluyendo el propio control que tiene el foco), el evento “Leave” del control deshabilitado se ejecutará!

A partir de aquí, el focus ya no “entrará” ninguna otra vez en el control deshabilitado (no lanzará más eventos Enter/Leave).

Si “quereis” jugar con esta curiosidad, aquí os dejo un pequeño programilla, con el que comprobar in-situ esta curiosidad…

Link al programa para testear la curiosidad. Compilado con VS2008 y .NET 3.5 SP1, bajo Windows XP.

Y una capturilla para que lo veais (al tener la checkbox activa, se deshabilita el textbox que está a la derecha del TextBox con fondo naranja-suave (que he llamado en un alarde de pereza textBox3)):

image

Evidentemente, si alguien tiene más información al respecto y sabe el porqué de esta causa… pues le estaré muy agradecido!!! 😉

Saludos!

Recaptcha, ASP.NET MVC, SimpleModal y un poco de Ajax…

Hola família!

En los dos últimos posts (http://geeks.ms/blogs/etomas/archive/2009/04/14/mostrar-un-formulario-modal-con-asp-net-mvc-y-ajax.aspx y http://geeks.ms/blogs/etomas/archive/2009/04/15/mostrar-un-formulario-modal-con-asp-net-mvc-y-ajax-ii.aspx) comenté como he usado SimpleModal, junto con ASP.NET MVC para mostrar un formulario modal y comunicarlo via AJAX con nuestro controlador.

En mi caso, este formulario era el formulario de registro… y para evitar los spammers (tal y como diría cierto ministro, yo no digo que haya que prohibir el spam, pero yo lo prohibiría :p) decidí usar un captcha.

Paso 1: El captcha

Nevagando por esas webs de dios, llegué a Recaptcha un sitio donde ofrecen de forma totalmente gratuita un servicio de captchas bastante interesante… Empecé a ver como integrar Recaptcha en ASP.NET MVC… la API no es muy complicada y hay gente que ha hecho utilidades para casi todos lenguajes de servidor (como se puede ver en la página de recursos de Recaptcha). Aunque no aparece en esta página de recursos, googleando un poco más vi que Andrew Wilinksi ya había hecho una API para recaptcha usando ASP.NET MVC Beta 1: RecaptchaMvc. En su blog anuncia que es para la Beta 1 de ASP.NET MVC, así que me descargue el código fuente desde http://recaptchamvc.codeplex.com/SourceControl/ListDownloadableCommits.aspx (el changeset 27773) y lo compilé contra la versión final de ASP.NET MVC.

El resultado es que NO compila, debido al uso de la interfaz IValueProvider y la clase DefaultValueProvider, que existían en la Beta y que fueron eliminadas. La solución es bastante simple, en el fichero controllerextensions.cs, cambiar las referencias a IValueProvider por IDictionary<string, ValueProviderResult>. Al final del post adjunto el fichero modificado para que compile contra la versión final. Este es el único fichero que debe ser modificado.

RecaptchaMvc incorpora métodos de extensión para HtmlHelper, de forma que poner un Captcha es tan sencillo como:

<%= Html.Recaptcha(this.Model) %>

Entiendendo que estamos en una vista tipada cuyo tipo de modelo es IRecaptchaModelState, una interfaz proporcionada por RecaptchaMvc, que también proporciona la clase RecaptchaModelState que implementa la interfaz. Dicha clase espera nuestra clave pública de Recaptcha.

Paso 2: El formulario modal…

Y este post se habría terminado ya, si en mi caso yo no estuviese mostrando el captcha en un formulario modal via SimpleModa. A ver, a grandes rasgos el problema es que la llamada a Html.Recaptcha, genera un tag <script>. Si mostramos un formulario usando Ajax, lo que hacermos básicamente es rellenar un <div> via javascript con cierto contenido html. En este caso, los tags <script> son ignorados, por lo que el captcha no se ve…

La solución? Una vez esté cargado el formulario modal, llamar via Ajax a recaptcha y mostrar entonces el captcha. Por suerte esto es posible porque la gente de recaptcha ofrecen una API Ajax, porque si no… buf! En la página de la API cliente de recaptcha se describe como funciona la API de recaptcha si queremos usar Ajax. Hay también un ejemplo y la verdad es que es bastante sencillo… De hecho son dos pasos muy sencillos:

  1. Incluir el fichero de script recaptcha_ajax.js
  2. Llamar a la función Recaptcha.Create. Esta función espera la clave pública, el ID del objeto DOM a rellenar con el captcha y un objeto con varias propiedades adicionales para personalizar el captcha.

Por lo tanto, lo que he hecho ha sido:

En la vista principal (la que muestra el popup cuando se pulsa un enlace), que en mi caso se llama Home/Index.aspx, he añadido el tag <script>:

<script type="text/javascript" src="http://api.recaptcha.net/js/recaptcha_ajax.js"></script>

Y luego he definido una función showRecaptcha, que llama Recaptcha.Create:

<script type="text/javascript">
    function showRecaptcha() {
Recaptcha.create("clave_publica_de_recaptcha",
"div_recaptcha", {
theme: "red",
tabindex: 0 }); } </script>

Finalmente, llamo a esta función en cuanto se ha mostrado el formulario modal. Para ello uso el callback onOpen de SimpleModal. Si mirais mi post anterior, vereis que ya usaba este callback para añadir un manejador de eventos javascript para ir comprobando (via AJAX) si el login del usuario estaba libre o no, sin necesidad de hacer submit de todo el popup. En mi caso tenía una función popup_open, y ha sido en ella que he añadido el código para mostrar el captcha:

function popup_open(dialog) {
    dialog.overlay.fadeIn('slow', function() {
        dialog.container.fadeIn('slow', function() {
            dialog.data.fadeIn('slow', function() {
                showRecaptcha();
// resto de código… }); }); }); }

Paso 3: Comprobar el captcha

Esto, usando RecaptchaMvc es muy fácil. En mi caso, cuando el usuario pulsa el botón “Enviar” de mi popup, se hace un POST a una URL gestionada por la acción Signup del controlador Account, cuyo código es:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Signup(FormCollection values)
{                       
    string privateKey = this.GetRecaptchaPrivateKey();
    var response = this.TryValidateRecaptcha
        (privateKey, values.ToValueProvider());
    if (response.IsValid) {
        // Recaptcha ha sido Ok
    }
    else { 
        // Recapcha ha ido mal
    }
}

Muy simple: Obtenemos la clave privada de Recaptcha (el método GetRecaptchaPrivateKey) es un método extensor mío, que obtiene la clave privada que guardo en el web.config. Luego simplemente llamamos a TryValidateRecaptcha (método extensor que incorpora RecapthcaMvc) y con esto ya sabemos si el captcha es correcto o no!

Y aquí teneis una captura de pantalla con todo funcionando:

image

(Sí, sí… el estilo es muy… ASP.NET MVC :p :p :p)

Saludos!

Enlace del fichero controllerextensions.cs de RecaptchaMvc preparado para ASP.NET MVC versión final.

Mostrar un formulario modal con ASP.NET MVC y Ajax (ii)

En el post anterior (Mostrar un formulario modal con ASP.NET MVC y Ajax) comenté como había usado SimpleModal en una aplicación ASP.NET MVC para mostrar un formulario modal al usuario.

En este post voy a comentar como podemos comunicar nuestro formulario modal con nuestros controladores, para así poder validar (parcialmente o totalmente) el formulario desde servidor, sin necesidad de hacer un submit, usando Ajax.

En mi caso p.ej. a medida que el usuario va entrando un posible nickname, se le indica si dicho nickname ya está ocupado o no (aunqué en la versión final probablemente sea un boton “comprobar nick”, ya que una llamada Ajax cada vez que se entre un carácter en un textbox quizá es demasiado… pero eso no afecta al sentido del post). Para realizar las llamadas Ajax cada vez que el usuario pulse una tecla en el campo “nick” del formulario modal vamos a usar jQuery.

Lo primero que debemos cambiar respecto al post anterior es la forma en como abrimos el formulario modal: el método modal de SimpleModal, admite varios parámetros, uno de los cuales es una función de callback que SimpleModal llamará. A través de esta función vamos a registrar una función gestora del evento KeyPress usando jQuery. Recordáis la función show_popup() que teníamos en la vista Index.aspx? Su código ahora queda así:

function show_popup() {
    $("#popup").modal( { onOpen: popup_open});
}

Hemos añadido el parámetro onOpen con el nombre de una función javascript que SimpleModal nos llamará cuando deba abrir el formulario modal.

Una particularidad de SimpleModal es que si usamos la función de callback onOpen, debemos encargarnos nosotros de abrir el formulario modal. Así el código de la función popup_open queda así:

function popup_open(dialog) {
    dialog.overlay.show(1);                
    dialog.container.show(1);
    dialog.data.show(1);
}

Debemos llamar al método show (un método estándar definido en jQuery) con los tres elementos que componen el formulario modal de SimpleModal: Overlay (que lo que hace es inhabilitar el resto de la pantalla), Container (que muestra el borde del formulario) y Data (que muestra el contenido del formulario).

Otra forma “más jQuery” de realizar lo mismo es encadenar las llamadas: Generalmente todos los métodos jQuery aceptan un parámetro callback con código a realizar cuando se termine el método. Así también podríamos escribir la función popup_open como:

function popup_open(dialog) {
    dialog.overlay.show(1,function() {
        dialog.container.show(1,function() {
            dialog.data.show(1);
        });
    });
}

El siguiente paso es añadir el código para suscribirnos al evento onKeyPress del textbox cuyo id era “nick”. jQuery unifica todos los eventos de los distintos browsers en un conjunto de eventos propio, lo que permite más fácilmente desarrollar aplicaciones cross-browser. El método keypress de un objeto jQuery permite suscribir un callback al evento de pulsación de una tecla. Un objeto jQuery es un objeto javascript que se obtiene generalmente usando la función selector (comúnmente llamada $) de jQuery. Así la llamada:

$("#popup")

Me devuelve el objeto jQuery asociado al elemento DOM cuyo ID sea “popup”. Esto no es equivalente a document.getElementById(“popup”) que me devuelve el objeto DOM directamente… es mucho mejor, ya que sobre el objeto jQuery puedo usar todas las propiedades de jQuery (como el método show() que hemos visto antes o el método modal() que define SimpleModal)!

Así pues, para suscribirnos al keypress del textbox cuyo ID es “nick” usando jQuery, el código de popup_open queda:

function popup_open(dialog) {
    dialog.overlay.show(1, function() {
        dialog.container.show(1, function() {
            dialog.data.show(1, function() {
                $("#nick").keypress(function(e) { });
            });
        });
    });
}

Ahora sólo nos rellenar la función anónima que pasamos como parámetro a la llamada a keypress con el código que realice una petición Ajax a un controlador para que compruebe si el nick que se ha entrado está libre o no. Para ello vamos a usar la función getJSON de jQuery, que lo que hace es realizar una petición Ajax a la URL especificada, esperar la respuesta en formato JSON, deserializar la respuesta en un objeto Javascript y ejecutar el método de callback que nosotros le indiquemos.

Así, pues usando getJSON el código de popup_open queda así:

function popup_open(dialog) {
    dialog.overlay.show(1, function() {
        dialog.container.show(1, function() {
            dialog.data.show(1, function() {
                $("#nick").keypress(function(e) {
                    if (e.which != 13 && e.which != 8 
&& e.which != 0) { var str = this.value +
String.fromCharCode(e.which); var url = "/Account/Check"; $.getJSON(url, { nick: escape(str) },
function(data) { if (data.existeix) { $("#invalid_nick").show(); } else { $("#invalid_nick").hide(); } }); } }); }); }); }); }

Dentro de la función gestora del evento keypress:

  1. Miramos que la tecla pulsada NO sea enter, backspace o tabulador
  2. Llamamos a getJSON con la URL /Account/Check (parámetro 1), con el valor del textbox codificado como parámetro (parámetro 2) y la función de callback que queremos ejecutar cuando recibamos la respuesta del servidor (parámetro 3 que es un método anónimo).
    1. Dentro del método anónimo, miramos si el valor del campo “existeix” del objeto recibido como parámetro es true para mostrar u ocultar un objeto cuyo ID es “invalid_nick”.

¿Que nos queda por hacer? Pues por un lado modificar la vista parcial que es el formulario modal (en mi caso era SignupPopup.ascx), para añadir un <DIV> con un id “invalid_nick” con un mensaje que ponga “NICK INCORRECTO” (o algo así). P.ej:

<div id="invalid_nick" style="display:none">
    NICK IS INVALID
</div>

Inicialmente lo tenemos oculto (evidentemente usaríamos CSS y alguna imágen para hacerlo más “bonito”), puesto que lo mostramos via jQuery.

Por último lo que nos queda es hacer la función correspondiente en el controlador. En mi caso el controlador es AccountController y la acción es “Check” (como se puede deducir de la URL /Account/Check):

public ActionResult Check(string nick)
{
    return new JsonResult() { 
        Data = new {existeix = nick.Length % 2 == 0 }
    };
}

Esta acción en lugar de devolver una vista, devuelve un objeto serializado en JSON, usando JsonResult. Básicamente cuando queráis devolver un objeto codificado en JSON usando ASP.NET MVC:

  1. Creais un JsonResult.
  2. A la propiedad Data la asignais el objeto a serializar.

Esta función devuelve un objeto con una propiedad “existeix” que vale true si el nick tiene un número par de carácteres.

¡Ya lo tenemos todo listo! Ahora si vais tecleando carácteres en el formulario, se ve como se muestra o se oculta la etiqueta “NICK IS INVALID”.

¿Fácil, verdad?

Saludos!

PD: Recordais lo que os dije, que cuando usabamos el callback onOpen de SimpleModal debíamos “abrir” manualmente el overlay, el container y la data y que eso nos daba capacidades interesantes? Este “interesantes” viene por la API de animación de jQuery. P.ej. si podríamos cambiar los show() por llamadas a fadeIn para que la aparición del formulario sea más espectacular:

function popup_open(dialog) {
    dialog.overlay.fadeIn('slow', function() {
        dialog.container.fadeIn('slow', function() {
            dialog.data.fadeIn('slow', function() {
                // A partir de aquí todo igual...
           });
        });
    });
}            

¡Y observad como se despliega suavemente el formulario! 😉

Mostrar un formulario modal con ASP.NET MVC y Ajax

¿Os gusta ASP.NET MVC? A mi personalmente me encanta… aunque está un poco verde, en el sentido que comparándolo con webforms hay varias cosas que debes hacerte tu mismo, el modelo de programación es simple y elegante… Gran parte del mérito lo tiene (además del uso del patrón MVC evidentemente), jQuery genial librería de Javascript donde las haya.

Hay mucha gente desarrollando en jQuery (al margen de que usen o no ASP.NET MVC) y dado lo bien que se entienden ASP.NET MVC y jQuery es muy fácil realizar tareas que antes eran un poco… complejas.

Yo me he encontrado con la necesidad de mostrar un pop-up (modal) en una aplicación ASP.NET MVC. Un par de búsquedas por google me han llevado a SimpleModal, un genial plugin para jQuery que precisamente hace esto: mostrar formularios modales. En su página web hay varios ejemplos (en su caso él usa PHP).

Os cuento como he integrado SimpleModal en mi aplicación ASP.NET MVC por si a alguien le interesa… Esta ha sido mi manera de hacerlo, no pretendo sentar cátedra porque hay muuuuuuucha gente que sabe más que yo (especialmente de jQuery).

En concreto la necesidad era mostrar un link, que al pulsarse desplegase un pop-up modal para que la gente pudiera darse de alta en la página.

La página que muestra el enlace (en mi caso Index.aspx) tiene el siguiente código ASP.NET:

<%= Ajax.PopupLink ("Join the game", "Signup","Account", "popup") %>
and start playing!
<div id="popup" />

El método PopupLink es un método extensor de AjaxHelper:

public static string PopupLink(this AjaxHelper helper, string linkText, 
string actionName, string controllerName, string popupId) { AjaxOptions options = new AjaxOptions() { UpdateTargetId = popupId, OnComplete = "show_popup", HttpMethod = "GET" }; string link = helper.ActionLink(linkText, actionName,
controllerName, options); return link; }

Ok… no es un método muy configurable, pero a mi me va bien 🙂 Lo que hace es mostrar un enlace con el texto especificado y le establece unas opciones por defecto: Que la llamada sea via Ajax usando GET, que se llame a una función javascript “show_popup” al terminar y que se actualice el elemento DOM especificado (en este caso el último parámetro llamado ‘popup’). Los parámetros “actionName” y “controllerName” del método sirven para especificar que acción de que controlador debe devolver la vista parcial que contiene el popup. En mi caso la acción “Signup” del controllador “AccountController” que está definida tal y como sigue:

public ActionResult Signup()
{
    if (Request.IsAjaxRequest())
    {
        return PartialView("SignupPopup");
    }
    else return RedirectToAction("Index", "Home");               
}

Como podeis ver me limito a devolver la vista parcial SignupPopup que es la que contiene el código HTML del popup. Cuando el usuario haga click en el enlace “Join the game” se llamará via Ajax a la acción Signup que devolverá la vista parcial “SignupPopup”, el código de la cual se incrustará dentro del div “popup”.

El código de la vista parcial en mi caso es muy simple:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<div>
    <h1>Join the Game!</h1>
    <% using (Html.BeginForm("Signup", "Account", FormMethod.Post)) { %>
        <label for="nick">*Nick Name:</label>
        <input type="text" id="nick" name="nick" tabindex="1001" />
        <br />
        <label for="email">*Email:</label>
        <input type="text" id="email" name="email" tabindex="1002" />
        <br />
        <label>A email for validate your account will be sent at 
email address you specified.</label> <br /> <button type="submit" tabindex="1006">Send</button> <button type="reset" tabindex="1007">Cancel</button> <br/> <% } %> </div>

Basicamente tenemos un formulario con dos campos: nick y email. Cuando hagamos un submit del formulario (via POST) se llamará a la acción Signup del controlador AccountController, acción que está definida como sigue:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Signup(string nick, string email)
{
    // Codigo para dar de alta el nuevo usuario...
    // Mostramos la vista de Bienvenida
    return View();
}

No mucha cosa… El controlador da de alta el usuario y finalmente muestra una vista de bienvenida.

Finalmente en la página Index.aspx, debemos tener el método javascript show_popup, que será el encargado de mostrar el popup usando SimpleModal:

<script type="text/javascript">
    function show_popup() {
        $("#popup").modal();
    }
</script>    

El código es muy simple: accedemos al elemento div con id=”popup” que hemos rellenado con el contenido de la vista parcial, y usamos el método modal() que define SimpleModal para mostrar este div como un formulario modal…

… y listos!

Simple y sencillo… en otro post mostraré como comunicar nuestro formulario via Ajax con nuestros controladores (p.ej. para poder validar datos en servidor sin necesidad de hacer submit del formulario).

ASP.NET MVC, Controles Chart y Ajax…

Supongo que la gran mayoría de vosotros, conoceréis los controles de gráficos de ASP.NET. José M. Aguilar hizo un excelente post sobre ellos aquí (http://geeks.ms/blogs/jmaguilar/archive/2008/12/14/microsoft-chart-control-para-asp-net-3-5-sp1.aspx).

Utilizarlos es realmente simple… basta con que os los descargueis de la web de Microsoft y después de instalarlos agregueis las siguiente líneas en el web.config:

<add path="ChartImg.axd" verb="GET,HEAD" 
type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler,
System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
" validate="false"/>

En la sección <httpHandlers> y la siguiente:

<add tagPrefix="asp" 
namespace="System.Web.UI.DataVisualization.Charting"
assembly="System.Web.DataVisualization, Version=3.5.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35
"/>

En la sección <controls>.

Despues ya podeis arrastrar un Chart control desde la toolbox a vuestra página ASP.NET y empezar a trabajar con él.

Si, como yo, os encanta ASP.NET MVC sabed que podeis usar este control sin ningún problema (http://code-inside.de/blog-in/2008/11/27/howto-use-the-new-aspnet-chart-controls-with-aspnet-mvc/).

El único temilla a tener en cuenta es si se quiere actualizar sólo el gráfico mediante Ajax (usando ASP.NET MVC).

Suponed una vista parcial (Chart.ascx) con el siguiente código que muestra un gráfico con contenido random:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<asp:Chart ID="chart" runat="server" Palette="Fire" >
    <Series>
        <asp:Series Name="D1" ChartType="StackedColumn" />
        <asp:Series Name="D2" ChartType="StackedColumn" />
    </Series>
    <ChartAreas>
        <asp:ChartArea Name="ChartArea1">
        </asp:ChartArea>
    </ChartAreas>
</asp:Chart>
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        Random r = new Random();
        this.chart.Series["D1"].Points.Add(r.Next(100));
        this.chart.Series["D2"].Points.Add(r.Next(100));        
    }
</script>

Y otra vista (Victories.aspx) que contiene el siguiente código (entre otro):

    <%=Ajax.ActionLink("Actualizar", "Victories", 
new RouteValueDictionary(new { Days = 7, Interval = 1 }),
new AjaxOptions() { UpdateTargetId = "chart" }) %> <div id="chart" />

El enlace “Actualizar” envia una petición Ajax al controlador actual para que invoque la acción “Victories” y con el resultado actualice el div “chart”.

La acción “Victories” está implementada en el controlador tal como sigue:

public ActionResult Victories(int? days, int? interval)
{
    return PartialView("Chart");
}

De este modo a cada click del enlace se genera un nuevo gráfico aleatorio y se actualiza via Ajax la página…

… en teoria, porque en la práctica no se ve nada. Analizando con firebug lo que ha sucedido se puede observar que se lanza una excepción:

[HttpException (0x80004005): Error executing child request for ChartImg.axd.]

La solución? Caer en la cuenta de que las peticiones Ajax usan POST por defecto, así que o bien cambiamos la línea que añadimos en el web.config para que soporte POST:

<add path="ChartImg.axd" verb="GET,HEAD, POST" 
type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler,
System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35
" validate="false"/>

o bien le indicamos  a la petición Ajax que sea usando “GET”:

<%=Ajax.ActionLink("Last Week", "Victories", 
new RouteValueDictionary(new { Days = 7, Interval = 1 }),
new AjaxOptions() { HttpMethod="GET", UpdateTargetId = "chart" }) %>

Saludos!

Interfaces “Dockables” con AvalonDock

Hace algún tiempo escribí como integrar AvalonDock con PRISM. En el post daba por asumidos algunos conceptos de AvalonDock, pero algunos comentarios recibidos me han pedido si puedo profundizar un poco, así que voy a ello. Vamos a ver como crear paso a paso una aplicación AvalonDock y luego, en otro post ya veremos como podemos PRISMearla… 🙂

AvalonDock es una librería para la creación de interfaces con ventanas flotantes (al estilo del propio Visual Studio). Según su creador soporta también winforms, aunque yo siempre la he usado con WPF, así que nada puedo deciros de su integración con winforms.

Hay un tutorial de AvalonDock en el blog de su creador (http://www.youdev.net/post/2008/09/25/AvalonDock-Tutorial.aspx) que aunque muy básico explica los conceptos clave… echadle una ojeada si os apetece 🙂

Supongo que teneis instalada AvalonDock… si usais el instalador, os creará una toolbar en Visual Studio con los controles de AvalonDock, no és imprescindible pero ayuda 🙂

El primer paso es crear una aplicación WPF, añadir una referencia a AvalonDock.dll y en la ventana principal, debemos añadir el componente padre de AvalonDock, el DockingManager:

<Window x:Class=»DockReader.Window1″ xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation» xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml» Title=»Window1″ Height=»300″ Width=»300″ xmlns:ad=»clr-namespace:AvalonDock;assembly=AvalonDock»> <Grid> <ad:DockingManager Name=»dockManager»/> </Grid> </Window>


En este caso DockManager ocupa todo el espacio disponible en la Grid del control. Si nos interesa tener algún control en la ventana principal que no participe del sistema de ventanas flotantes (p.ej. una statusbar o una ribbon siempre visibles y fijas), podemos colocarla en alguna otra fila de la grid:

<Window x:Class=»DockReader.Window1″ xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation» xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml» Title=»Window1″ Height=»300″ Width=»300″ xmlns:ad=»clr-namespace:AvalonDock;assembly=AvalonDock»> <Grid> <Grid.RowDefinitions> <RowDefinition Height=»50″/> <RowDefinition/> </Grid.RowDefinitions> <!– ToolBar fija –> <ToolBar Grid.Row=»0″> <Button> <Image Source=»/DockReader;component/open.png» Height=»32″ Width=»32″></Image> </Button> </ToolBar> <!– Docking Manager –> <ad:DockingManager Grid.Row=»1″ Name=»dockManager»/> </Grid> </Window>


El DockingManager por sí solo no hace nada… tenemos que rellenarlo y para ello podemos usar los dos contenidos que tiene AvalonDock:

  • DockableContent: Un DockableContent se puede “dockar” en cualquier parte del DockingManager y también puede aparecer en forma de ventana flotante.
  • DocumentContent: Los DocumentContent aparecen “fijos” en una zona, y pueden “dockarse” en cualquier parte de esta zona (y generalmente no aparecen en forma de ventanas flotantes).

P.ej. en Visual Studio las distintas ventanas con documentos serian DocumentContents, y el restro de ventanas flotantes (p.ej. la toolbox) serian DockableContents.

Para que AvalonDock funcione correctamente debemos incrustar los DockableContent y los DocumentContent en sus propios paneles que son DocumentPane (para contener DocumentContents) y DockablePane para contener DockableContents.

P.ej. si colocamos un DocumentPane con dos DocumentContent dentro del DockingManager, obtenemos la siguiente interfaz:

image image

Como podeis observar sólo con un DocumentPane ya tenemos una interfaz totalmente dockable.

Si queremos combinar varios DocumentPane (cada uno con sus ContentPane) y/o varios DockablePane (cada uno con sus DockableContents) debemos usar un panel dentro del DockingManager. P.ej, la siguiente interfaz és el mismo ContentPane de antes y un DockableContent, todo ello dentro de un StackPanel:

image

El código XAML sería tal y como sigue:

<!– Docking Manager –> <ad:DockingManager Grid.Row=»1″ Name=»dockManager»> <StackPanel Orientation=»Horizontal»> <ad:DocumentPane Width=»200″> <ad:DocumentContent> <Label>Documento 1</Label> </ad:DocumentContent> <ad:DockableContent> <Label>Documento 2</Label> </ad:DockableContent> </ad:DocumentPane> <ad:DockablePane Width=»80″> <ad:DockableContent> <Button>Botón Dockable</Button> </ad:DockableContent> </ad:DockablePane> </StackPanel> </ad:DockingManager>


 

 

Esta ventana tiene el problema de que el tamaño del DocumentPane y del DockablePane está fijo… AvalonDock nos ofrece un panel (ResizingPanel) que integra un splitter… simplemente cambiando el StackPanel por un ResizingPanel nuestra interfaz ya es totalmente funcional!

Vamos a hacer una aplicación completa: un lector de ficheros XPS. En la parte izquierda tendremos un DocumentPane con los distintos ficheros abiertos, y en la parte derecha una lista con los nombres de los ficheros abiertos.

El XAML de la ventana principal quedaria:

<Window x:Class=»DockReader.Window1″ xmlns=»http://schemas.microsoft.com/winfx/2006/xaml/presentation» xmlns:x=»http://schemas.microsoft.com/winfx/2006/xaml» Title=»Window1″ Height=»300″ Width=»300″ xmlns:ad=»clr-namespace:AvalonDock;assembly=AvalonDock»> <Grid> <Grid.RowDefinitions> <RowDefinition Height=»50″/> <RowDefinition/> </Grid.RowDefinitions> <!– ToolBar fija –> <ToolBar Grid.Row=»0″> <Button x:Name=»cmdAbrir» Click=»cmdAbrir_Click»> <Image Source=»/DockReader;component/open.png» Height=»32″ Width=»32″></Image> </Button> </ToolBar> <!– Docking Manager –> <ad:DockingManager Grid.Row=»1″ Name=»dockManager»> <ad:ResizingPanel Orientation=»Horizontal»> <ad:DocumentPane x:Name=»docsPane»> </ad:DocumentPane> <ad:DockablePane> <ad:DockableContent> <DockPanel> <Label DockPanel.Dock=»Top»>
Ficheros Abiertos:</Label> <ListBox x:Name=»openFiles»/> </DockPanel> </ad:DockableContent> </ad:DockablePane> </ad:ResizingPanel> </ad:DockingManager> </Grid> </Window>


Vemos la toolbar fija (fuera del DockingManager). Luego tenemos un DocumentPane vacío (inicialmente no tenemos cargado ningún documento) y también vemos un DockablePane que tiene un DockableContent con una label y la listbox…

En la función gestora del evento Click del botón “cmdAbrir”, mostramos un OpenFileDialog para seleccionar un fichero xps:

private void cmdAbrir_Click(object sender, RoutedEventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.AddExtension = true; ofd.DefaultExt = «.xps»; ofd.Multiselect = false; ofd.CheckFileExists = true; ofd.Filter = «Documentos XPS | *.xps»; if (ofd.ShowDialog() == true) { string s = ofd.FileName; CrearNuevoVisor(s); } }


Finalmente la función CrearNuevoVisor es la que creará un DocumentContent con el contenido del fichero XPS:

private void CrearNuevoVisor(string fname) { // Creamos el DocumentContent DocumentContent dc = new DocumentContent(); dc.Closed += new EventHandler(Document_Closed); dc.Title = System.IO.Path.GetFileNameWithoutExtension(fname); this.files.Add(dc.Title); // Carga el XPS y crea un DocumentViewer XpsDocument doc = new XpsDocument(fname, FileAccess.Read, CompressionOption.NotCompressed); DocumentViewer dv = new DocumentViewer(); // Muestra el XPS en el DocumentViewer dv.Document = doc.GetFixedDocumentSequence(); // Incrusta el DocumentViewer en el DocumentContent dc.Content = dv; // Incrusta el DocumentContent en el ContentPane this.docsPane.Items.Add(dc); }


El código es realmente simple, no? Si os preguntais que es this.files, os diré que es una ObservableCollection<string> que está enlazada a la listbox:

private ObservableCollection<string> files; public Window1() { InitializeComponent(); this.files = new ObservableCollection<string>(); this.openFiles.ItemsSource = this.files; }


Finalmente, en el método Document_Closed lo único que hacemos es eliminar de this.files la cadena que hemos añadido antes (no pongo el código, ya que es trivial).

Con esto ya tenemos un lector de XPS completamente funcional y con una interfaz totalmente “dockable”…

image image

En un próximo post veremos como convertir este DockReader en una aplicación compuesta usando PRISM!

Saludos!

La sencillez de una interfaz compleja

Hace algún tiempecillo escribí un artículo para el e-zine de raona, que enviamos a distintos clientes. En el artículo esbozaba los patrones básicos para diseñar interfaces de usuario compuestas. Posteriormente me surgió la idea de que una ampliación de dicho artículo, donde se mostrasen ejemplos en PRISM y WPF de estos conceptos podría ser interesante. Afortunadamente en DotNetMania pensaron lo mismo y es por ello que en la revista de este marzo hay un artículo con este mismo título.

Lo que ahora sigue és el artículo original, el que escribí para el e-zine. Aunque el de DotNetMania describe las mismas ideas, ambos artículos tienen poco a ver (tanto en contenido, como en enfoque como en extensión). Como creo que el artículo del e-zine tambiém tiene su interés, me he tomado la libertad de postearlo aquí 🙂

PD: Por si os interesa, los distintos e-zines que vamos sacando en raona, los podeis consultar en la sección de ezines de raona.

La sencillez de una interfaz compleja (versión e-zine).

Cada vez las aplicaciones son más y más complejas, y los usuarios son más y más exigentes. Los desarrolladores debemos afrontar este doble reto siendo capaces de proporcionar mejores y más complejas aplicaciones, en cada vez menos tiempo. Evidentemente este es un proceso global, que afecta a todo el desarrollo de una solución de software, pero me gustaría centrarme en los retos concretos que esto implica cuando hablamos de interfaces de usuario. No en vano, la interfaz de usuario es el punto de conexión entre el usuario y la aplicación. Es un elemento crítico y de su implementación depende en buena medida el éxito final de toda la aplicación.

Interfaces complejas

Las aplicaciones actuales demandan interfaces de usuario complejas a nivel técnico pero que a la vez sean sencillas de utilizar y de mantener. Que se adapten a cualquier tipo de usuario, que tanto usuarios expertos como inexpertos se sientan a gusto utilizándola. En resumen que la experiencia del usuario al utilizar la interfaz sea plena y que nuestra aplicación sea usable por cualquier usuario.

El término interfaz compleja, no quiere referirse al aspecto visual o usable si no al hecho de que cada vez es más complejo y difícil realizar interfaces que permitan conjugar las crecientes posibilidades de la aplicación con la sencillez de uso y la usabilidad. Este es el reto que debemos solucionar.

Nuevas tecnologías, nuevas posibilidades

Con la aparición de WPF, Microsoft ha dotado a los desarrolladores de un montón de nuevas posibilidades para la construcción de interfaces. El hecho de disponer de un lenguaje declarativo (XAML) para definir las interfaces permite la separación entre dos roles básicos hoy en día: los diseñadores y los desarrolladores. Pero separar desarrolladores y diseñadores es sólo un primer paso (un primer gran paso) pero no soluciona la problemática de fondo: el modo en cómo están diseñadas y desarrolladas las interfaces de usuario.

El mecanismo habitual de desarrollar interfaces de usuario en .NET, viene influenciado de los tiempos de Visual Basic. Es un patrón que consiste en una clase contenedora (llamada comúnmente formulario) que contiene a los controles y toda la lógica asociada a dichos controles. Por norma general el formulario se suscribe a los eventos de los controles a los que contiene y realiza la lógica apropiada en las funciones gestoras de dichos eventos. La ventaja de este modelo de programación es que es sencillo, intuitivo y fácil de aprender. Las desventajas son que al tener unidas la lógica con la presentación se vuelve más complejo el mantenimiento tendiendo a colapsar en grandes proyectos y se dificulta la separación del desarrollo entre varios equipos.

Una de las claves para desarrollar interfaces de usuario complejas y que resulten mantenibles es desacoplar la presentación de la lógica de dicha presentación. WPF presenta algunas novedades que ayudan a conseguir dicho desacople:

  1. Commands: Permite desacoplar los eventos de los controles de la gestión de dichos eventos y unificar en un mismo punto la gestión de distintos eventos de distintos controles pero que realizan la misma acción (p.ej. un botón de una toolbar y una entrada de menú).
  2. Routed Events: Permiten tratar en un solo contenedor cualquier evento generado por cualquier de los controles “hijos” (estén en el nivel de profundidad que estén).

Estas novedades aunque interesantes, por si solas no son suficientes para desacoplar totalmente la presentación y su lógica. Para ello hace falta un cambio de paradigma, un nuevo modelo.

Composite Application Library

Composite Application Library (CAL) también conocida por su nombre en código PRISM, es un framework lanzado por la gente de “Patterns & Practices” para ayudar en la construcción de aplicaciones complejas utilizando WPF.

CAL se basa en varios patrones para ayudar a conseguir un desacople total entre la presentación y la lógica de presentación. A continuación comentaremos tres de los patrones más relevantes de CAL.

Inversion of Control (IoC)

Este patron desacopla una clase en concreto de otra clase de las clases que utiliza, es decir de sus referencias:

Dependencias entre clsases

La clase ClassA utiliza dos servicios (ServiceA y ServiceB) y por lo tanto tiene referencias directas a ellos. Esto implica, entre otras cosas, que si queremos modificar las referencias debemos modificar el código fuente de ClassA. Para desacoplar la clase de sus referencias la solución es añadir un componente externo que se encargue de encontrar y gestionar las referencias de la clase ClassA. En función de cómo diseñemos este elemento externo hablaremos de un patrón ServiceLocator o de Dependency Injection.

Service locator &  Dependency Injection

Con ServiceLocator la clase ClassA sólo contiene una referencia al Locator siendo esta clase la que obtiene y devuelve la referencia adecuada a la clase. El Locator NO instancia los servicios, simplemente los localiza (los servicios han estado instanciados previamente por alguien): mientras un servicio esté registrado, el Locator lo encontrará. CAL utiliza Unity para ofrecer un Service Locator, lo que nos permite instanciar y registrar servicios que luego serán utilizados en cualquier parte de la aplicación.

Con Dependency Injection, existe un Builder que crea el objeto del servicio y lo “incrusta” en alguna propiedad específica de la clase ClassA (o bien en algún parámetro del constructor). CAL utiliza Unity para proporcionar Dependency Injection.

Separated Presentation

Si IoC nos permite desacoplar nuestras clases de las referencias que necesitan, el conjunto de patrones conocidos como “separated presentation”, nos permiten separar la presentación de la parte de la lógica que gestiona la presentación.

Existen varios patrones que implementan “separated presentation” siendo los más concidos MVP (Model – View – Presenter) o MVVM (Model – View- ViewModel). Debido a las capacidades de WPF se tiende a utilizar más MVVM (mientras que MVP es más frecuente en aplicaciones Windows forms p.ej), debido a que MVVM se basa en uno de los aspectos más potentes de WPF: su data binding.

El patrón MVVM se basa en tres elementos:

  • Vista (View): Contiene los elementos visuales (controles). En el caso de WPF suele estar creada por un diseñador e implementada vía XAML
  • Modelo (Model): Contiene los datos que muestra la vista. En este punto es cuando se utiliza básicamente el data binding, para enlazar los valores de los controles de la vista a valores de distintos elementos del modelo.
  • Modelo de Vista (ViewModel): En interfaces sencillas, la vista se puede enlazar directamente con el modelo, pero en interfaces complejas no suele ser posible. En muchos casos el Modelo puede tener multitud de información que no se puede mapear a valores de los controles o bien nuestra vista debe realizar y mantener valores “temporales” que no forman parte estricta del modelo. Es en estos casos donde entra en escena el “ViewModel”, actuando de puente proporcionando transformadores para poder enlazar los controles de la vista y commands para que la vista pueda interactuar con el modelo.

De esta manera no existe más una clase “formulario” que contiene los controles y toda la lógica asociada a ellos.

Event Aggregator

Un Event Aggregator es la implementación de un modelo de eventos publish and subscribe, donde cuando alquien quiere recibir un evento se suscribe a este tipo de evento en particular. Cuando alguien quiere enviar un evento lo publica y el event aggregator se ocupa de que todos los que se hayan suscrito a este tipo de evento lo reciban. De esta manera se desacoplan totalmente los publicadores de eventos de sus suscriptores.

Conclusiones

Para desarrollar interfaces complejas que a la vez sean amigables para el usuario y mantenibles para el desarrollador es necesario involucrar a los diseñadores por un lado y desacoplar la presentación de la lógica que la gestiona por otro. Gracias a XAML, WPF permite que los diseñadores se integren totalmente en el ciclo de desarrollo, y el uso de determinados patrones permite desacoplar los distintos elementos que conforman la inferfaz de usuario. CAL es un framework y una guía de estilo que basándose en dichos patrones y en las funcionalidades inherentes a WPF nos permite desarrollar aplicaciones complejas que sean mantenibles y amigables a la vez.

Referencias

PRISM y Winforms: Mostrar vistas en nuevos formularios

En un post anterior (PRISM y Winforms), comentaba como usar PRISM para realizar aplicaciones Winforms.

Un comentario de Jose en esta entrada, me ha motivado a escribir el siguiente post, para mostrar como podríamos mostrar vistas en regiones que estén incrustadas no en un UserControl (típicamente un Panel) de la ventana principal, sino incrustadas en un nuevo formulario.

Para poder usar regiones en Winforms era necesario definirnos un RegionAdapter para la clase “Control” que era básicamente el objetivo del post anterior. En el método Adapt() teníamos el código que “incrustaba” la vista dentro del control. Dicho método recibía (de PRISM) la región y el target, o control donde colocar dicha región.

Antes que nada recordad tres conceptos que a veces se confunden:

  1. Region: En PRISM una región es un conjunto de vistas (algunas activas, otras no activas) que se muestran en algún sitio determinado.
  2. Target: El target de una región es el lugar donde se muestran. P.ej. un TableLayout podría ser el target de una región y cada vista de la región podría mostrarse en distintas celdas.
  3. Vista: Una vista muestra una determinada información. P.ej. si tenemos una aplicación que nos muestra la cotización de varias acciones, podríamos tener varias instancias de una vista, donde cada instancia nos mostraría la cotización de una acción. En winforms generalmente es un UserControl.

En PRISM las vistas se colocan en regiones y las regiones se incrustan en los targets… recordad que una región puede tener varias vistas.

En el RegionAdapter que vimos en el post anterior, siempre recibíamos la región y el target. El target ya estaba creado porque era un control ya existente en la ventana principal.

Esto no nos sirve si queremos mostrar una vista en un formulario nuevo ya que ahora debemos crear el formulario cada vez que queramos mostrar la región… ¿como podemos hacerlo?

Además del método Adapt() que usamos en el post anterior, los RegionAdapter pueden redefinir otro método llamado AttachBehaviors. En este método podemos añadir la lógica que queramos para personalizar el comportamiento de la región… en este caso podremos aprovechar para crear el target.

Un vistazo al código…

El código que sigue a continuación es una adaptación de la clase WindowRegionAdapter de Composite WPF Contrib, que he adaptado para que funcione con Windows Forms.

El método AttachBehaviors lo redefinimos de la siguiente manera:

protected override void AttachBehaviors(IRegion region, 
    Form regionTarget)
{
    base.AttachBehaviors(region, regionTarget);
    FormRegionBehavior behavior = 
        new FormRegionBehavior
            (regionTarget, region, FormBorderStyle.FixedSingle);
    behavior.Attach();
}

Los parámetros que recibe son los mismos que Adapt: la región y el target (en este caso un Form) ya que derivamos de RegionAdapterBase<Form>. En este método creamos un objeto FormRegionBehavior que será el que tendrá todo el código para gestionar regiones que estén dentro de un formulario. En concreto dicha clase será la responsable de:

  1. Crear un formulario por cada nueva vista añadida a la región y incrustar dicha vista en el formulario
  2. Cerrar el formulario que contiene una vista si esta se elimina de la región.
  3. Eliminar una vista de la región si se cierra el formulario que la contiene.
  4. Activar o desactivar la vista cuando su formulario es activado o desactivado.

A continuación pongo el código más relevante de dicha clase. Al final del post adjunto una aplicación de demo.

1 y 2. Crear un formulario por cada nueva vista y eliminar el formulario de una vista eliminada

El método Attach() de la clase FormRegionBehavior de suscribe a los dos eventos CollectionChanged de las colecciones Views y ActiveViews de la región:

internal void Attach()
{
    IRegion region = _regionWeakReference.Target as IRegion;
    if (region != null)
    {
        region.Views.CollectionChanged +=
            new NotifyCollectionChangedEventHandler
                (Views_CollectionChanged);
        region.ActiveViews.CollectionChanged += 
            new NotifyCollectionChangedEventHandler
                (ActiveViews_CollectionChanged);
    }
}

En el método Views_CollectionChanged es donde sabemos si se ha añadido una vista a la región o se ha eliminado, y así podemos crear o destruir el formulario asociado:

private void Views_CollectionChanged(object sender,
NotifyCollectionChangedEventArgs e) { Form owner = _ownerWeakReference.Target as Form; if (owner == null) { Detach(); return; } if (e.Action == NotifyCollectionChangedAction.Add) { foreach (object view in e.NewItems) { // Creamos un formulario por cada vista y lo mostramos... } } else if (e.Action == NotifyCollectionChangedAction.Remove) { foreach (object view in e.OldItems) { // Buscamos el formulario que contiene cada vista // para cerrarlo y "disposarlo" 🙂 } } }

3. Eliminar la vista de la región si se cierra el formulario

Para ello, cuando creamos un formulario, nos suscribimos al evento Closed para poder eliminar la vista asociada a la región:

private void Form_Closed(object sender, EventArgs e)
{
    Form frm = sender as Form;
    IRegion region = _regionWeakReference.Target 
        as IRegion;
    if (frm != null && frm.Controls.Count > 0 
        && region != null)
        if (region.Views.Contains(frm.Controls[0]))
            region.Remove(frm.Controls[0]);
}

4. Activar o desactivar la vista cuando su formulario es activado o desactivado

Para ello, cuando creamos un formulario nos suscribimos a los eventos Activated y Deactivate, para desactivar o activar la vista correspondiente:

private void Form_Activated(object sender, EventArgs e)
{
    IRegion region = _regionWeakReference.Target 
        as IRegion;
    Form frm = sender as Form;
    if (frm != null && frm.Controls.Count > 0 &&
        !region.ActiveViews.Contains(frm.Controls[0]))
        region.Activate(frm.Controls[0]);
}

5. Algunas consideraciones finales

Para PRISM toda región tiene un solo contenedor (o target) que es aquel que se usa cuando se llama a AttachNewRegion. Aunque este RegionAdapter va creando distintos formularios, para PRISM la región debe estar vinculada a un único target… que debe existir cuando llamemos a  AttachNewRegion y cuyo tipo debe ser Form puesto que nuestro RegionAdapter trabaja con targets de tipo Form.

Esto nos deja en una situación curiosa, puesto que necesitamos tener un Form creado para poder crear la región. Aunque luego este formulario no contendrà ninguna de las vistas de la región. Consideraremos a este formulario el formulario padre, y cuando el formulario padre muera el RegionAdapter dejarà de tratar a la región (digamos que la región habrá muerto).

Otra cosa que también debemos solucionar es dado una vista, encontrar que formulario la contiene. El RegionAdapter no se guarda una lista de los formularios creados, en su lugar solo tiene una WeakReference al formulario padre (el usado para llamar a AttachNewRegion y que nunca contendrá vistas de esta región) y usa el formulario padre para encontrar a los formularios que contiene la vista. Esto es posible porque al crear un formulario que contiene una vista se indica que su padre es el formulario padre:

Form owner = _ownerWeakReference.Target as Form;
// Creamos un formulario por cada vista y lo mostramos...

frm.Owner = owner;

De este modo se puede usar la propiedad OwnedForms del formulario padre para iterar sobre todos los formularios creados y ver cual contiene la vista en cuestión.

Finalmente, el hecho de tener que pasar un formulario ya existente a AttachNewRegion, no es excesivamente problemático: lo más fácil es usar el formulario principal del programa!

Adjunto el programa de demostración. El RegionAdapter DialogRegionAdapter es el que muestra vistas en nuevos formularios, mientras que el RegionAdapter ControlRegionAdapter las muestra en paneles (de hecho en cualquier control).

Descargar el código de demostración.

Espero que os sea útil! 🙂