Traducción aproximada del artículo Better Silverlight 4.0 Installation and Upgrade Experience publicado en inglés por Andrew Tokeley el 17 de octubre del 2010.

 

Las apariencias son muy importantes. De hecho, la forma en que una aplicación es instalada, o actualizada, y cuánto dura en arrancar puede tener una gran influencia en el concepto que el usuario tenga de ella. Tal como viene de fábrica, Silverlight no ayuda mucho, pero tranquilos que con un poquito de esfuerzo de puede superar ese obstáculo.

La siguiente figura ilustra la serie de pruebas necesarias cuando un usuario navega a la página que contiene la aplicación de Silverlight (para una imagen más amplia le sugiero abrir la imagen en una pestaña nueva).

 

Secuencia de inicio de una aplicación de Silverlight.

 

Verificando que Silverlight está instalado

El archivo Silverlight.js se encarga de todo esto antes de que la aplicación siquiera sea cargada, pero aun así nosotros podemos agregar un poco de magia.

Una de las primeras cosas que hay que verificar es si Silverlight está instalado en el cliente y si es la versión correcta. Hay un excelente documento que explica el proceso en todo detalle. Sin embargo creo que se puede simplificar un poco y por eso he escrito este artículo.

En el archivo estándar que Visual Studio prepara, agreguen el siguiente código en la función onSilverlightError—de otro modo no será posible determinar si se requiere actualizar la versión ya instalada de Silverlight. (Puede que esto sea una pulga, aunque todavía no lo he investigado.)

 

<script type="text/javascript">
    function onSilverlightError(sender, args) {

        var appSource = "";
        if (sender != null && sender != 0) {
            appSource = sender.getHost().Source;
        }

        var errorType = args.ErrorType;
        var iErrorCode = args.ErrorCode;

        // ¡AGREGUEN ESTA PARTE!
        // Por alguna razón onUpgradeRequired nunca es llamado.
        if (iErrorCode == 8001) {
            Silverlight.onUpgradeRequired();
            return;
        }

        ...
    }
...
</script>

 

Luego hay que añadir el siguiente código en JavaScript. Estas funciones nos permiten controlar el HTML que es desplegado dependiendo de si Silverlight está instalado y cuál es su versión.

 

<script type="text/javascript"
        src="scripts/plugindetect.js"></script>
<script type="text/javascript"
        src="scripts/Silverlight.supportedUserAgent.js"></script>
<script type="text/javascript">

  Silverlight.onRestartRequired = function () {
    DisplayAltenativeContent(
      "<p>¡Casi listo! Tan sólo hay que reiniciar" +
      "el navegador para usar la aplicación.</p>");
  };

  Silverlight.onUpgradeRequired = function () {
    PluginDetect.getVersion('.');
    DisplayAltenativeContent(
      "<p>La versión de Silverlight instalada es  " +
      PluginDetect.getVersion('Silverlight') + " " +
      "pero la aplicación ocupa que sea por lo menos " +
      getRequiredVersion() + ".</p>" +
      "<p>Por favor obtenga la versión más reciente " +
      "haciendo clic abajo.</p>" +
      Silverlight.buildPromptHTML(getRequiredVersion()));
  };

  Silverlight.onInstallRequired = function () {
    DisplayAltenativeContent(
      "<p>Silverlight no parece estar instalado " +
      "(o ha sido desactivado).</p>" +
      "<p>Para instalarlo por favor haga clic abajo.</p>" +
      Silverlight.buildPromptHTML(getRequiredVersion()));
  };

  DisplayAltenativeContent = function (html) {
    document.getElementById("plugin").innerHTML =
      "<div id='content'>" + html + "</div>";
  };

  getRequiredVersion = function () {
    return document.getElementById("minRuntimeVersion")
           .getAttribute("value");
  }

  checkSupported = function () {
    // Verificar si el navegador es compatible.
    if (Silverlight.supportedUserAgent()) {
      document.getElementById("unsupported").innerHTML =
        "<p>Este navegador no está listado oficialmente " +
        "como compatible con Silverlight, pero es " +
        "posible que aun así funcione bien " +
        "(<a target='_blank' href='http://www." +
        "microsoft.com/silverlight/resources/install.aspx" +
        "#sysreq'&gtmás detalles</a>).</p>";
    }
  }

  // Esta función es llamada repetidamente mientras Silverlight
  // está siendo instalado y puede usarse para actualizar un
  // control que muestre su progreso.
  onSourceDownloadProgressChanged = function (sender, eventArgs)
  {
    //var slPlugin = sender.getHost();
    //slPlugin.content.findName("progressMessage").Text =
    //  Math.round(eventArgs.progress * 100) + "%";
  };

</script>

 

Al cargar la página podemos entonces llamar la función checkSupported() para que le avise al usuario si el navegador no es del todo compatible. Lo importante es no dejar de intentar la instalación incluso si no está en la lista oficial de navegadores compatibles de Microsoft. Es posible que todavía se pueda ejecutar.

 

<body onload="javascript: checkSupported();">

 

Finalmente, podemos alterar el elemento object para que incluya ciertos parámetros adicionales (y algunos divs extra en mi ejemplo). Presten especial atención a onUpgradeRequired, onInstalledRequired y onRestartRequired. Estos apuntan a funciones que serán llamadas por Silverlight.js durante las diferentes etapas.

 

<div id="silverlight">
  <div id="plugin">
    <object data="data:application/x-silverlight-2"
            type="application/x-silverlight-2"
            width="100%" height="100%">
      <param name="source"
             value="ClientBin/ExperienciaDeInstalacion.xap"/>
      <param name="onError"
             value="onSilverlightError" />
      <param name="background" value="black" />
      <param id="minRuntimeVersion"
             name="minRuntimeVersion"
             value="9.0.50401.0" />

      <!-- Estos parámetros son para -->
      <!-- presentar la pantalla de cargado.  -->
      <param name="splashscreensource"
             value="ClientBin/splash_spinner.xaml" />
      <param name="onSourceDownloadProgressChanged"
             value="onSourceDownloadProgressChanged" />

      <!-- Estos son para la instrucciones -->
      <!-- de instalación/actualización. -->
      <param name="onUpgradeRequired"
             value="onUpgradeRequired" />
      <param name="onInstallRequired"
             value="onInstallRequired" />
      <param name="onRestartRequired"
             value="onRestartRequired" />
      <param name="autoUpgrade"
             value="false" />
    </object>
    <iframe id="_sl_historyFrame"
            style="visibility:hidden;height:0px;width:0px;border:0px">
    </iframe>
  </div>
  <div id="unsupported">
  </div>
</div>

 

Para probar, se puede cambiar el minRuntimeVersion a un número mayor del que está actualmente instalado causando el siguiente resultado.

 

onUpgradeRequired informa al usuario que necesita una versión más reciente de Silverlight.

 

Noten que he incluido PluginDetect en mi página, para poder identificar la versión instalada de Silverlight. Hay veces en que puede ser más fácil poder informar al usuario cuál versión es la que tienen y cuál es la que ocupan. Por supuesto, podemos decidir cuántos detalles mostrar mediante modificar Silverlight.onUpgradeRequired. El enlace para instalar la versión deseada es creado mediante llamar la función Silverlight.buildPromptHTML(versión) (definida en Silverlight.js).

El siguiente experimento es desactivar el plugin de Silverlight en el navegador. De esta manera se puede simular el caso de un usuario sin Silverlight en su máquina. De acuerdo con el código en Silverlight.onInstallRequired, debería verse algo como esto en la pantalla:

 

onInstallRequired notifica al usuarion que Silverlight no está instalado y le indica dónde conseguirlo.

 

Otra prueba es usar un navegador que no esté en la lista de los compatibles. Volviendo la versión a la que teníamos originalmente especificada, abrimos a página en el navegador de prueba y esta vez la aplicación es cargada pero con una nota de advertencia.

 

checkSupported advierte al usuario si el navegador no es compatible dejándolo decidir si desea ejecutar la aplicación.

 

También es fácil comprobar si la aplicación ha sido instalada en la computadora. Todo lo que hay que hacer es verificar la condición App.Current.InstallState == InstallState.Installed y si es cierta entonces desplegar algo como lo siguiente.

 

La aplicación en sí puede detectar si ya ha sido instalada fuera del navegador.

 

Actualizando una aplicación OOB

Si la aplicación tiene como propósito que sea eventualmente instalada en el computador y usada en fuera del navegador (OOB por sus siglas en inglés), entonces es importante asegurarse de que revise si hay actualizaciones disponibles en el servidor. Recuerden que una vez que el usuario instala la aplicación, dejará de ser actualizada automáticamente, aun habiendo nuevas versiones del XAP en el servidor.

En mi ejemplo he creado una página con el propósito específico de revisar si hay actualizaciones. La página es cargada siempre al inicio de la aplicación y le avisa al usuario si hay una actualización disponible, si no entonces pasa el mando a la página principal.

De esta manera podemos informar al usuario que la aplicación está buscando actualizaciones y presentar una animación mientras tanto.

 

La aplicación revisando si hay versiones más recientes en el servidor.

 

El único código interesante de esta página es:

 

App.Current.CheckAndDownloadUpdateCompleted +=
    Current_CheckAndDownloadUpdateCompleted;
App.Current.CheckAndDownloadUpdateAsync();

 

Es no más asunto de conectar un controlador que sea ejecutado cuando complete CheckAndDownloadUpdatesAsync() y que informe al usuario si hay una nueva versión disponible. Algo como esto:

 

En caso de haber sido actualizada, la aplicación informa al usuario al respecto.

 

Pantalla de cargado personalizada

Finalmente, una forma efectiva de darle un aspecto profesional es evitar que esta animación aparezca:

 

Esferas originales (y ya aburridas) de Silverlight cargando. La idea es usar algo original. 

 

El procedimiento es sencillo y ha sido bien explicado por Laurent Duveau y en MSDN por lo que no voy a repetirlo acá. Sin embargo, hay algunas cosas que a veces no quedan claras:

  • No estoy seguro por qué, pero la pantalla de cargado no siempre aparece. En todo caso, he notado lo siguiente:
    • Chrome parece ser el mejor portado.
    • Limpien el cache del navegador para asegurarse de que de hecho están descargando algo.
    • He obtenido resultados más consistentes haciendo clic derecho en la página HTML (o aspx) y seleccionando Ver en el explorador… en vez de usar F5 para depurar.
  • Si ven la animación original con las esferas azules el problema es generalmente un error ortográfico en alguno de los parámetros en el elemento object. Si ven una página en blanco si pantalla de cargado entonces probablemente la aplicación no ha sido descargada. Tal vez por estar en el cache; vean el paso anterior.
  • El XAML de la pantalla de cargado es un fragmento. No necesita estar dentro de un UserControl o Page. Por ejemplo, basta con declarar un elemento Canvas o Grid y crear el contenido (ejemplos más adelante).
  • Puesto que el XAML reside en la página de web, no es posible verla en el área de diseño de Blend. Lo que se puede hacer es diseñarla dentro de un proyecto de Silverlight y ya cuando está lista copiar el fragmento a la página de web.
  • No es posible usar código subyacente por lo que si tienen una animación, debe ser iniciada de una manera como esta:

     

    <Grid>
    
        <Grid.Triggers>
            <EventTrigger RoutedEvent="Grid.Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        ...
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Grid.Triggers>
    
        <!-- Agregar controles animados por el guión -->
    
    </Grid>

 

Les muestro ahora un par de ejemplos de pantallas de cargado.

La forma más sencilla es rotar una imagen:

 

<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Name="parentCanvas"
  Background="Black" >

  <Grid.Triggers>
    <EventTrigger RoutedEvent="Grid.Loaded">
      <BeginStoryboard>
        <Storyboard RepeatBehavior="Forever">
          <DoubleAnimation
                 Storyboard.TargetName="spinnerRotation"
                 Storyboard.TargetProperty="Angle"
                 From="0" To="360" Duration="00:00:02" >
          </DoubleAnimation>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Grid.Triggers>

  <StackPanel VerticalAlignment="Center"
              HorizontalAlignment="Center">

    <Image Source="/images/spinner.png"
           Stretch="None" Margin="15" >
      <Image.RenderTransform>
        <RotateTransform x:Name="spinnerRotation"
                         Angle="0" CenterX="25"
                         CenterY="25">
        </RotateTransform>
      </Image.RenderTransform>
    </Image>
  </StackPanel>

</Grid>

 

Otra es usar animaciones diferidas:

 

<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Name="parentCanvas"
  Background="Black">

  <Grid.Triggers>
    <EventTrigger RoutedEvent="Grid.Loaded">
      <BeginStoryboard>
        <Storyboard RepeatBehavior="Forever" AutoReverse="True">
          <DoubleAnimation Storyboard.TargetName="c1"
                           Storyboard.TargetProperty="Opacity"
                           BeginTime="0:0:0" From="0" To="1"
                           By="0.1" Duration="0:0:0.2"
                           AutoReverse="True" >
          </DoubleAnimation>
          <DoubleAnimation Storyboard.TargetName="c2"
                           Storyboard.TargetProperty="Opacity"
                           BeginTime="0:0:0.1" From="0" To="1"
                           By="0.1" Duration="0:0:0.2"
                           AutoReverse="True" >
          </DoubleAnimation>
          <DoubleAnimation Storyboard.TargetName="c3"
                           Storyboard.TargetProperty="Opacity"
                           BeginTime="0:0:0.2" From="0" To="1"
                           By="0.1" Duration="0:0:0.2"
                           AutoReverse="True">
          </DoubleAnimation>
          <DoubleAnimation Storyboard.TargetName="c4"
                           Storyboard.TargetProperty="Opacity"
                           BeginTime="0:0:0.3" From="0" To="1"
                           By="0.1" Duration="0:0:0.2"
                           AutoReverse="True">
          </DoubleAnimation>
          <DoubleAnimation Storyboard.TargetName="c5"
                           Storyboard.TargetProperty="Opacity"
                           BeginTime="0:0:0.4" From="0" To="1"
                           By="0.1" Duration="0:0:0.2"
                           AutoReverse="True">
          </DoubleAnimation>
        </Storyboard>
      </BeginStoryboard>
    </EventTrigger>
  </Grid.Triggers>

  <TextBlock x:Name="progressMessage" Text="0%"
             HorizontalAlignment="Center"
             VerticalAlignment="Center"
             FontWeight="Bold" FontSize="12"
             Foreground="#FFB3BABA">
  </TextBlock>

  <StackPanel Orientation="Horizontal" Margin="0,20,0,0"
             HorizontalAlignment="Center"
             VerticalAlignment="Center" >
    <Ellipse x:Name="c1" Fill="White" Opacity="0"
             HorizontalAlignment="Left"
             Height="4" Stroke="Black"
             VerticalAlignment="Bottom" Width="10"/>
    <Ellipse x:Name="c2" Fill="White" Opacity="0"
             HorizontalAlignment="Left"
             Height="4" Stroke="Black"
             VerticalAlignment="Bottom" Width="10"/>
    <Ellipse x:Name="c3" Fill="White" Opacity="0"
             HorizontalAlignment="Left"
             Height="4" Stroke="Black"
             VerticalAlignment="Bottom" Width="10"/>
    <Ellipse x:Name="c4" Fill="White" Opacity="0"
             HorizontalAlignment="Left"
             Height="4" Stroke="Black"
             VerticalAlignment="Bottom" Width="10"/>
    <Ellipse x:Name="c5" Fill="White" Opacity="0"
             HorizontalAlignment="Left"
             Height="4" Stroke="Black"
             VerticalAlignment="Bottom" Width="10"/>
  </StackPanel>
</Grid>

 

Pueden descargar el código de ejemplo acá. Mi recomendación es crear una aplicación de gran tamaño (decenas de MiB) que requiera una descarga lo suficientemente extensa como para ver la animación.

 

Andrew Tokeley

 

Etiquetas asignadas:
 

Responder



Licencia de uso

El contenido de las traducciones está sujeto a los términos de protección de derechos de uso de los autores originales quienes han autorizado su publicación en este blog. Asegúrese de entender los terminos de la licencia de cada autor antes de usar tal contenido.

Mis propios artículos son publicados bajo los términos de la Licencia Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos de Creative Commons:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos License.

License

The contents of all translated articles is subject to the copyright and licensing terms of the original authors and has been published here with their express permission. Verify the original author's licensing terms before using the contents of these articles.

My own articles are licensed under the Creative Commons Attribution-Share Alike 3.0 United States License:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.