Perception is everything and how your application installs, loads and upgrades can make a big difference to how users feel about it. Out of the box, Silverlight doesn’t make things particularly easy, but don’t worry, with a little work, you can cover the bases.

There are a number of checks you need to make when a user first browses to a website that contains your Silverlight application. The figure below illustrates this.

SilverlightInstallDecisionTree

Checking Silverlight Installed

This is all done before your application is even loaded and is controlled through the scripts in Silverlight.js and some extra magic you’ll have to write.

One of the first things you need to do before your application can run in a browser is to ensure Silverlight is actually installed on the client and that the correct version is available. There’s a good paper here that explains all the details – it’s good but I thought it could be simplified slightly – hence this post.

In the standard html file that Visual Studio creates for you, add the code below to the onSilverlightError function – otherwise you won’t be able to detect whether an upgrade to the installed version of Silverlight is required (this seems like a bug – I haven’t tracked it down yet).

<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;
 
        // ADD THIS BLOCK!
        // For some reason the onUpgradeRequired event is never fired
        if (iErrorCode == 8001) {
            Silverlight.onUpgradeRequired();
            return;
        }
 
        ...
    }
...
</script>

Next add the following additional JavaScript code. These functions will allow you to control the HTML that is displayed depending on whether Silverlight is installed or the version that is available.

<script type="text/javascript" src="script/plugindetect.js"></script>
<script type="text/javascript" src="script/Silverlight.supportedUserAgent.js"></script>
<script type="text/javascript">
 
    Silverlight.onRestartRequired = function () {
        DisplayAltenativeContent(
            "<p>Almost there! You just need to restart your browser and to use this application.</p>");
    };
 
    Silverlight.onUpgradeRequired = function () {
        PluginDetect.getVersion('.');
        DisplayAltenativeContent(
            "<p>Your have version " + PluginDetect.getVersion('Silverlight') + " of Silverlight installed " +
            "but this application needs at least version " + getRequiredVersion() + ".</p>" +
            "<p>Click below to get the latest version.</p>" +
            Silverlight.buildPromptHTML(getRequiredVersion()));
    };
 
    Silverlight.onInstallRequired = function () {
 
        DisplayAltenativeContent(
            "<p>Silverlight doesn't appear to be installed (or may be disabled).</p>" +
            "<p>If you want to install Siverlight click below</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 () {
 
        // Check
        if (Silverlight.supportedUserAgent()) {
            document.getElementById("unsupported").innerHTML = "<p>Your browser does not officially support Silverlight, but it may still work (<a target='_blank' href='http://www.microsoft.com/silverlight/resources/install.aspx#sysreq'>more information</a>)</p>";
        }
    }
 
    // This function is called as the Silverlight plugin is being installed
    // and can be used to update controls in the pre-loader.
    onSourceDownloadProgressChanged = function (sender, eventArgs) {
        //var slPlugin = sender.getHost();
        //slPlugin.content.findName("progressMessage").Text = Math.round(eventArgs.progress * 100) + "%"; 
    };
 
</script>

Then call the checkSupport() function when the page loads. This warns the user if their browser is not officially supported. It’s important to not give up loading the plug-in since even browsers that aren’t on Microsoft’s official list may still be able to run it.

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

And finally update the object block to include a few additional parameters (and for my example a few extra divs). In particular, note onUpgradeRequired, onInstalledRequired and onRestartRequired. These determine which functions will be called under the different scenarios and will be initiated by the code in Silverlight.js.

<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/SilverlightInstallExperience.xap" />
            <param name="onError" value="onSilverlightError" />
            <param name="background" value="black" />
            <param id="minRuntimeVersion" name="minRuntimeVersion" value="9.0.50401.0" />
            <!-- add these parameters to get a splash screen to display -->
            <param name="splashscreensource" value="ClientBin/splash_spinner.xaml" />
            <param name="onSourceDownloadProgressChanged" value="onSourceDownloadProgressChanged" />
            <!-- add these parameters to display to Silvelight installation/upgrade instructions -->
            <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>

To test the different behaviours, first change the minRuntimeVersion to something greater what you have installed, say, 9.0.50401.0. This will result in the following.

image

Note that in my implementation I’ve included PluginDetect to identify the version of Silverlight installed – it may be useful, in some scenarios, to let a user easily see what version they have installed and what is required. But obviously it’s up to you what information you decide to display – simply change the Silverlight.onUpgradeRequired definition. The link to install the required version is created from a call to Silverlight.buildPromptHTML(version) (this function is defined in Silverlight.js).

Next, try disabling the Silverlight plug-in on your favourite browser. Things now behave as they would if someone doesn’t have Silverlight installed at all. You should see something like this (based on what’s defined in Silverlight.onInstallRequired ),

 image

The next thing to check is using a browser that isn’t officially supported. Enable Silverlight again, reset the minRequiredVersion and on an unsupported browser you’ll get the application loading but with a warning underneath,

image

The other thing that’s easy to do is identify when a users accesses the in-browser application after they’ve already unstalled it on their machine. All you need to do is check whether App.Current.InstallState == InstallState.Installed and display something like this instead of the image above.

image

Updating Your (OOB) Application

If your Silverlight application, when displayed in the browser, is intended to launch an out of browser (OOB) application then you’ll want to make sure you include code to check for updates on the server. Remember, once a user installs the application on their computer, by default, it will never get updated, even when you put a new XAP up on the server.

In my example, I’ve created a page specifically tasked with checking for updates. This page is loaded every time the application starts and advises the user if a new update has been installed, otherwise it simply redirects to the main application page.

In this way I can create a nice animation to let the user know that the application is checking for updates.

image

The only code you really need to know from this page is,

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

Simply, wire up a handler that will get called once CheckAndDownloadUpdateAsync() has completed. You can then tell whether an new version is available. If so, display something like,

image

Custom Loading Screen

And finally, preventing this animation,

alt

appearing as your application is downloaded to the user’s machine is pretty straightforward and really effective in giving a sense of professionalism. I won’t re-hash the details – go here or here. A coupe of things that aren’t always made clear from most of the examples for this include,

  • Not sure exactly why the loading screen doesn’t always display, but here are some things that help me,
    • Chrome seamed to behave best
    • Clear your browser cache to make sure you are actually downloading something
    • I’ve had the most success by right-clicking my host HTML file and selecting to view in browser – rather than in debug
  • If you see the default blue balls instead of your animation this normally means you have misspelt one of the object params. If you see a blank page with no loader then it’s probably not loading and is cached somewhere (see first step)
  • The XAML you define is a fragment – it doesn’t need to be wrapped in a Page or UserControl. For example, just create a Canvas or Grid container and start there (see below for examples)
  • Since your XAML is defined in the host web application, Blend won’t allow you to view it in its designer – to do this you have to take a copy and place it in your Silverlight project – get it right, and copy it back.  
  • If you want to include some animation (and since you don’t have any code-behind) start your Storyboards with something like,
      <Grid>
       
          <Grid.Triggers>
              <EventTrigger RoutedEvent="Grid.Loaded">
                  <BeginStoryboard>
                      <Storyboard>
                          ...
                      </Storyboard>
                  </BeginStoryboard>
              </EventTrigger>
          </Grid.Triggers>
       
          <!-- add some controls to animate with the storyboard -->
       
      </Grid>

Here are some examples of XAML fragments used for pre-loader animations.

The easiest approach is to simply rotate an image.

<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>

Another approach is to use a set of delayed animations like,

<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>

 

You can find my example code here. Note you’ll need to add a large (10s of MBs) to the Silverlight project to simulate a lengthy enough download to see your pre-loader.