Navegando desde una página maestra mediante el control TreeView de Silverlight

El planteamiento del problema

Durante el desarrollo de una aplicación ASP.NET, a la hora de diseñar la interfaz de usuario, podemos encontrarnos ante un escenario en el que se requiera que la navegación a ciertas páginas se centralice en un único punto de la aplicación.

En situaciones como esta, hacer uso de una página maestra resulta una excelente solución, ya que nos evita tener que duplicar el código en todas aquellas páginas que necesiten disponer de la mencionada funcionalidad de navegación. Adicionalmente, tendremos que incluir en dicha página maestra un control o controles que realicen la operación de navegación.

El control que en esta ocasión utilizaremos para el ejemplo a desarrollar será el TreeView de Silverlight 2, incluido en el Silverlight 2 Toolkit de marzo de 2009, cuyos pasos de descarga e instalación explicamos seguidamente.

 

Descarga e instalación del Silverlight 2 Toolkit

En caso de no disponer del Silverlight 2 Toolkit, podemos descargarlo en el siguiente enlace. Una vez completada la descarga, ejecutaremos su instalador, que a través de unos sencillos pasos, como vemos en la siguiente imagen, instalará el conjunto de controles que extienden las funcionalidades de Silverlight.

 

Finalizado el trabajo del instalador, en Visual Studio 2008 aparecerán, dentro de la caja de herramientas, los nuevos controles que forman parte del Toolkit (TreeView incluido, naturalmente).

   

Creando el proyecto Web y la página maestra

Como hemos indicado al comienzo del artículo, vamos a desarrollar una aplicación Web que estará compuesta por varios WebForm, los cuales harán uso de una página maestra, que albergará un control TreeView de Silverlight. El TreeView contendrá un conjunto de opciones (elementos TreeViewItem) en los que al hacer clic se producirá la redirección a la página correspondiente.

Comenzaremos pues, el desarrollo de nuestro ejemplo, iniciando Visual Studio 2008 y creando un nuevo proyecto Web con el nombre NavMasterPageTreeView, al que añadiremos una MasterPage con el nombre Maestra.Master. Posteriormente añadiremos el proyecto Silverlight a la solución, pero por ahora nos centraremos en los formularios Web.

 

 

Esta página maestra, como vemos en su código de presentación, estará compuesta por una tabla con dos celdas; una de ellas corresponderá a la zona que reservaremos para el contenido Silverlight, y la otra para los formularios Web a los que naveguemos dentro de la aplicación. 

 

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Maestra.master.cs" Inherits="NavMasterPageTreeView.Maestra" %>

<%@ Register Assembly="System.Web.Silverlight" Namespace="System.Web.UI.SilverlightControls"
    TagPrefix="asp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <div>
                <table border="7" width="100%">
                    <tr>
                        <td width="20%" align="center">
                            <%Espacio para contenido Silverlight%>
                        </td>
                        <td width="80%">
                            <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
                                 <%Espacio que ocuparán los WebForms%>
                            </asp:ContentPlaceHolder>
                        </td>
                    </tr>
                </table>
            </div>
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>

 

Los formularios de contenido

A continuación agregaremos al proyecto los diferentes formularios Web que utilizaremos para efectuar las operaciones de navegación. Dado que su contenido no representa un aspecto relevante en el desarrollo del ejemplo, estarán compuestos simplemente por un control Label  a modo de título de la página y un TextBox, al que en el evento Load, asignaremos como valor la hora en que se produjo la carga de la página; esto nos servirá para demostrar, más adelante, un curioso efecto que se produce al cargar el WebForm desde el TreeView.

En total serán cuatro los formularios que crearemos, los cuales vemos remarcados en la siguiente imagen del Explorador de soluciones de Visual Studio. 

 

Seguidamente podemos ver tanto el código de presentación como el code behind de uno de estos WebForm, obviamos el resto ya que comparten las mismas características.

 

<%@ Page Title="" Language="C#" MasterPageFile="~/Maestra.Master" AutoEventWireup="true"
    CodeBehind="Facturas.aspx.cs" Inherits="NavMasterPageTreeView.Facturas" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <table border="2" style="margin: 10px">
        <tr>
            <td>
                <asp:Label ID="Label1" runat="server" Text="FACTURAS"></asp:Label>
            </td>
            <td>
                <asp:TextBox ID="txtHora" runat="server"></asp:TextBox>
            </td>
        </tr>
    </table>
</asp:Content>

 

public partial class Facturas : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        this.txtHora.Text = DateTime.Now.ToString("HH:mm:ss");
    }
}

 

Creación del contenido Silverlight

El siguiente punto a desarrollar consiste en añadir el control TreeView a la aplicación y dotarle de la funcionalidad necesaria. Para ello, agregaremos a la solución con la que estamos trabajando un nuevo proyecto de tipo Silverlight con el nombre Opciones.

 

Cuando el asistente muestre el cuadro de diálogo para configurar la creación de este proyecto, desmarcaremos la casilla para la creación automática de la página de pruebas para el contenido Silverlight.

 

El motivo de no emplear una página de prueba reside en que nuestra página de testeo para el contenido Silverlight será la propia master page, tal y como indicábamos en un apartado anterior.

Situándonos en el editor de código XAML, diseñaremos una página que contenga un control TreeView, con varios elementos TreeViewItem dispuestos en dos niveles, que nos permitirán organizar por unos hipotéticos tipos de funcionalidad, las páginas de nuestra aplicación. No obstante, nos interesa visualizar en todo momento el árbol completo de opciones, por lo que en los TreeViewItem de nivel superior, asignamos el valor True a su propiedad IsExpanded. Todo ello lo vemos en el siguiente bloque de código.

 

<UserControl xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"  x:Class="Opciones.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="140" Height="170">
    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel>
            <controls:TreeView x:Name="tvOpciones" Width="130" Height="160" Margin="5">
                <controls:TreeViewItem Header="Gestión" IsExpanded="True">
                    <controls:TreeViewItem x:Name="tviFacturas" Header="Facturas"></controls:TreeViewItem>
                    <controls:TreeViewItem x:Name="tviListados" Header="Listados"></controls:TreeViewItem>
                </controls:TreeViewItem>
                <controls:TreeViewItem Header="Contabilidad" IsExpanded="True">
                    <controls:TreeViewItem x:Name="tviApuntes" Header="Apuntes"></controls:TreeViewItem>
                    <controls:TreeViewItem x:Name="tviBalances" Header="Balances"></controls:TreeViewItem>
                </controls:TreeViewItem>
            </controls:TreeView>
        </StackPanel>
    </Grid>
</UserControl>

 

El resultado visual quedará de la forma mostrada en la siguiente imagen.

 

 

Al hacer clic en cada uno de los TreeViewItem de nivel inferior, el resultado que pretendemos conseguir es la carga en el navegador del WebForm correspondiente; por lo tanto, como siguiente paso, debemos dotar a esta interfaz que acabamos de diseñar de la funcionalidad necesaria.

Abriendo el editor de code behind de la página XAML, escribiremos un manipulador para el evento Selected de los objetos TreeViewItem. Puesto que la operación a realizar es la misma en todos los casos, y únicamente cambiará la página a la que debemos navegar, solamente será necesario un único manipulador de evento, ya que tomaremos el valor de la propiedad Header del TreeViewItem que recibe el evento en su parámetro sender; concatenaremos a dicho valor la cadena «.aspx», y realizaremos la navegación mediante el método Navigate del objeto HtmlPage.Window. Dentro del constructor de la página Silverlight, efectuaremos la asociación entre el evento de los objetos TreeViewItem y el manipulador. Todo ello lo vemos en el siguiente bloque de código.

 

using Microsoft.Windows.Controls;
using System.Windows.Browser;
//....
public partial class Page : UserControl
{
    public Page()
    {
        //....
        this.tviFacturas.Selected += new RoutedEventHandler(tvi_Selected);
        this.tviListados.Selected += new RoutedEventHandler(tvi_Selected);
        this.tviApuntes.Selected += new RoutedEventHandler(tvi_Selected);
        this.tviBalances.Selected += new RoutedEventHandler(tvi_Selected);
    }
    //....
    void tvi_Selected(object sender, RoutedEventArgs e)
    {
        TreeViewItem tviOpcion = sender as TreeViewItem;
        string sNombreItem = tviOpcion.Header.ToString();
        HtmlPage.Window.Navigate(new Uri(sNombreItem + ".aspx", UriKind.Relative));
    }
}

 

Para completar la operación, en la master page del proyecto Web agregaremos un control Silverlight con el nombre slOpciones, cuya propiedad Source apunte al archivo xap del proyecto Silverlight que hemos desarrollado.

 

<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <div>
            <table border="7" width="100%">
                <tr>
                    <td width="20%" align="center">
                        <asp:Silverlight ID="slOpciones" runat="server" 
                            Width="140px" Height="170px" 
                            Source="~/ClientBin/Opciones.xap" />
                    </td>
                    <td width="80%">
                        <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
                        </asp:ContentPlaceHolder>
                    </td>
                </tr>
            </table>
        </div>
    </ContentTemplate>
</asp:UpdatePanel>
</form>

 

A continuación observamos el resultado en tiempo de ejecución.

  

El problema con el método HtmlPage.Window.Navigate

Sin embargo, como no podía ser de otro modo, y para hacer más interesante el ejemplo, ahora nos encontramos con un «pequeño» inconveniente, motivado por la mecánica de navegación que hemos implementado mediante el control TreeView.

El problema consiste en que después de haber cargado por primera vez uno de los formularios de la aplicación, haciendo clic en el elemento TreeViewItem correspondiente; en las sucesivas ocasiones en que volvamos a hacer clic en ese mismo TreeViewItem, el WebForm seleccionado no se cargará nuevamente, sino que se utilizará la versión cacheada de dicha página. Este comportamiento podemos comprobarlo muy fácilmente poniendo un punto de ruptura en el evento Load del WebForm, aunque en nuestro ejemplo no sería necesario porque observando el TextBox que contiene cada página, donde se muestra la hora de carga de la misma, nos percataremos de que en las siguientes ejecuciones a partir de la primera, la hora no se actualiza.

 

 

Una forma de solucionar esta contrariedad, de modo que se fuerce la carga de la página, consiste en utilizar el método HtmlPage.Window.Eval, al que pasaremos como parámetro una cadena conteniendo una sentencia en JavaScript que efectúe este cometido, tal y como vemos en el siguiente bloque de código.

 

void tvi_Selected(object sender, RoutedEventArgs e)
{
    TreeViewItem tviOpcion = sender as TreeViewItem;
    string sNombreItem = tviOpcion.Header.ToString();
    HtmlPage.Window.Eval("window.location = '" + sNombreItem + ".aspx ';");
}

 

En el siguiente post perteneciente al blog de Kevin Hazzard, se comenta también este comportamiento.

 

Resaltando visualmente el TreeViewItem seleccionado

Otro aspecto que también puede resultar problemático radica en el hecho de que al hacer clic en uno de los elementos del TreeView, no queda constancia visual del elemento seleccionado, ya que la navegación a una nueva página produce una recarga completa, contenido Silverlight incluido.

Lo deseable en este caso sería que el TreeViewItem pulsado fuera informado de la página que ha sido cargada en el navegador (página actual en ejecución), para que de alguna manera pudiera quedar remarcado visualmente

La técnica que vamos a seguir en nuestro ejemplo para conseguir esta funcionalidad pasa por hacer uso, desde el code behind de la master page, de la propiedad InitParameters perteneciente al control Silverlight alojado en esta página, lo que nos permitirá enviar, desde el lado servidor, información al contenido Silverlight.

Para asignar valores a InitParameters deberemos utilizar una cadena compuesta por pares de identificador=valor separados por coma, en un formato como el que vemos a continuación.

ControlSilverlight.InitParameters = «Identificador01=Valor01,Identificador02=Valor02…»; 

Esta operación la realizaremos en el evento Load de la página maestra, como vemos en el siguiente bloque de código. Para obtener la página que acaba de ser cargada recurriremos a la propiedad PhysicalPath del objeto Request.

 

public partial class Maestra : System.Web.UI.MasterPage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string sPagina = Path.GetFileName(this.Request.PhysicalPath);
        this.slOpciones.InitParameters = "PaginaActual=" + sPagina.Replace(".aspx", string.Empty);
    }
}

 

La información asignada a InitParameters, será recogida en el proyecto Silverlight por el objeto Application que se halla en el código del archivo App.xaml.cs (en realidad se trata de una clase con el nombre App, que deriva de Application), más concretamente en el evento Startup de este objeto, que se desencadena al comienzo de la ejecución del contenido Silverlight.

El evento Startup recibe un parámetro de tipo StartupEventArgs, cuya propiedad InitParameters, de tipo IDictionary, utilizaremos para recuperar el valor de parámetro PaginaActual, que anteriormente establecimos en la página maestra. Dado que este valor necesitaremos utilizarlo desde el código de marcado de la página Silverlight (archivo Page.xaml), crearemos una propiedad en la clase App con el nombre PaginaActual, para poder manipularlo más fácilmente, como veremos brevemente.

 

public partial class App : Application
{
    string msPaginaActual;

    public string PaginaActual
    {
        get { return msPaginaActual; }
    }
    //....
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        this.RootVisual = new Page();

        msPaginaActual = e.InitParams["PaginaActual"];
    }
    //....
}

 

Finalmente, en el code behind de la página Silverlight, escribiremos un manipulador para el evento Loaded, dentro de cuyo código, a partir de la propiedad estática App.Current, accederemos a la instancia del objeto App, y por ende a nuestra propiedad PaginaActual, que en función de su valor, nos servirá para resaltar el elemento TreeViewItem pertinente, utilizando su propiedad Background. 

 

public partial class Page : UserControl
{
    public Page()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(Page_Loaded);
        //....
    }

    void Page_Loaded(object sender, RoutedEventArgs e)
    {
        SolidColorBrush scbItemSeleccionado = new SolidColorBrush(Colors.Cyan);
        App oAplicacion = App.Current as App;

        switch (oAplicacion.PaginaActual)
        {
            case "Facturas":
                this.tviFacturas.Background = scbItemSeleccionado;
                break;

            case "Listados":
                this.tviListados.Background = scbItemSeleccionado;
                break;

            case "Apuntes":
                this.tviApuntes.Background = scbItemSeleccionado;
                break;

            case "Balances":
                this.tviBalances.Background = scbItemSeleccionado;
                break;
        }
    }
}

 

La siguiente imagen muestra el resultado de esta característica que acabamos de añadir. 

 

 

Conclusiones

El empleo de una interfaz basada en una master page, que contenga elementos Silverlight tales como el control TreeView que hemos utilizado en el ejemplo, puede resultar de gran utilidad en el momento de desarrollar un mecanismo de navegación hacia las páginas Web que componen nuestra aplicación. Los ejemplos de código están disponibles en los siguientes enlaces: C# y VB. Espero que os pueda resultar de ayuda.

Un saludo.

8 Comentarios

  1. anonymous

    Hola

    Tu articulo es muy interesante, pero tus archivos en formatro ZIP estan corruptos

    Saludos

    aruizvc@gmail.com

  2. lmblanco

    Hola Alfonso

    Muchas gracias por tu interés en el artículo. Acabo de revisar los enlaces a los ficheros zip, y no sé que ha podido ocurrir. Los he vuelto a subir al servidor. Espero que ahora te funcione la descarga correctamente.

    Un saludo,
    Luismi

  3. anonymous

    Hola que tal Luis Miguel Blanco.
    Apenas estoy aprendiendo todo lo nuevo de microsoft (Silverlight, expression studio, vs2008, win server 2008, sql 2008). Tus artículos son exelentes

    Quisiera saber si tu me puedes ayudar, tengo que pasar un sistema de contabilidad (para internet «silverlight» o en que???)y me surgen nuchas dudas. Desde que software tengo que instalar perimero y cual al final, lo mas importante como hacerlo.

    VER http://cid-af00c83b0be541ca.skydrive.live.com/self.aspx/.Public/SisContable.ppsx

    Una pequeña historia lo realice en 1.- RPG(IBM), 2.- COBOL,3.- DBASE IV,4.- ACCESS y 5.- VB 6.

    Gracias de antemano, mi e-mail : sei_g@msn.com

  4. anonymous

    Hola que tal tus articulos son exelentes.

    Me gustaria tener contacto contigo o en donde puedo hacerte una dudas de como hacer aplicaciones con Silverlight.

    http://cid-af00c83b0be541ca.skydrive.live.com/self.aspx/.Public/SisContable.ppsx

    Garcias y muchos saludos, mi E-mail : sei_g@msn.com

  5. anonymous

    tengo mi pagina master pero al dar clic en un boton «ciclo de vida» abre otra pagina donde carga una imagen, como puedo hacer q al presionar un boton «ver proceso» que fue heredado de pagina master, la master se vuelva a cargar, la pagina principal y a la vez abra el e «el evento del boton» que seria mostrar una ventana con procesos???

  6. anonymous

    Buenos días estoy buscando un articulo que no solamente me muestre como cargar datos con RIA SERVICE sinó como hace la modificacion de las tablas (insertar, modificar o eliminar), y la verdad que todos los articulos, videos etc son iguales, mostrando datos por este medio, podras comentarme como hacer para poder insertar datos con RIA dado que al poner la opcion de editar con el domain service y crearse los metodos de actualizacion cuando hago la instancia no se ven y no puedo realizar estas acciones.
    gracias

  7. lmblanco

    Hola Luis

    En los siguientes posts de este mismo blog se explica cómo obtener y editar datos mediante RIA Services utilizando un control DataForm:

    http://geeks.ms/blogs/lmblanco/archive/2010/10/13/dataform-edittemplate-y-newitemtemplate-edici-243-n-de-datos-en-silverlight-mediante-plantillas-1.aspx

    http://geeks.ms/blogs/lmblanco/archive/2010/10/14/dataform-edittemplate-y-newitemtemplate-edici-243-n-de-datos-en-silverlight-mediante-plantillas-y-2.aspx

    En lo que respecta a la grabación de los datos modificados, puedes realizarla obteniendo la instancia del DomainDataSource empleado para realizar la carga de datos en el DataForm. Algo parecido al siguiente código de un botón situado en la página, y que se encarga de hacer esta operación.

    private void btnActualizarFuenteDatos_Click(object sender, RoutedEventArgs e)
    {
    this.ddsInvoices.SubmitChanges();
    }

    De esto último tienes el código de ejemplo disponible en un artículo que publiqué en dotNetMania.

    http://www.dotnetmania.com/

    Busca en la web de la revista el número 74 usando el control que te permite desplazarte por los números publicados, y en el artículo «Edición de entidades con el control DataForm de Silverlight» haz clic en el icono para descargar el código fuente, donde podrás ver el ejemplo completo que se comenta en el artículo.

    Espero que te ayude.

    Un saludo,
    Luismi

Deja un comentario

Tema creado por Anders Norén