Rebuilding the PDC 2010 Silverlight Application (Part 2)

Following on from that previous post, I wanted to have a MainView on the application that knows whether the app is in the browser, out-of-browser and/or elevated.

Ultimately, this app is going to call the PDC OData service at a domain that does not have a client access policy file allowing cross-domain calls and I’m only going to get that working from an out-of-browser elevated application so my functionality in the browser is going to be purely around offering the user the chance to install the app out of browser.

Part 2 – Handling In/Out of Browser

The previous work I did makes this pretty easy. If I factor things carefully then I can have my top-level XAML view look something like this;

<DeferredView “MainView”/>

and then I can put 2 different implementations exporting themselves as “MainView” built into 2 separate XAPs and ensure that only one of those XAPs loads when the application is in the browser and the other loads if the application is out of the browser and that lets me easily load 2 different completely sets of views depending on the application’s in-browser/out-of-browser context.

Job done Smile

In fact, almost all of my XAPs only need to load if I am out of browser which should mean that the application loads really quickly in the browser as it’s just loading one or two XAPs.

I added a new project into my solution which builds out into a XAP. This is called InBrowserViews and, as the name suggests, it would only ever get loaded up in the browser.

Now I’ve got 2 places that provide a MainView;

image

and the out-of-browser one is the same dummy one that I had in the previous post whereas the in-browser one looks like this;

<UserControl
  x:Class="PDCTutorial.InBrowserViews.Views.MainView"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:util="clr-namespace:PDCTutorial.Utility;assembly=PDCTutorial.Utility"
  xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
  mc:Ignorable="d"
  d:DesignHeight="300"
  d:DesignWidth="400"
  util:BindableVsmState.VsmState="{Binding ViewState,Mode=TwoWay}">
  <Grid
    x:Name="LayoutRoot"
    Background="White">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup
        x:Name="DisplayStates">
        <VisualState
          x:Name="Installed">
          <Storyboard>
            <ObjectAnimationUsingKeyFrames
              Storyboard.TargetProperty="(UIElement.Visibility)"
              Storyboard.TargetName="viewbox">
              <DiscreteObjectKeyFrame
                KeyTime="0">
                <DiscreteObjectKeyFrame.Value>
                  <Visibility>Collapsed</Visibility>
                </DiscreteObjectKeyFrame.Value>
              </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
          </Storyboard>
        </VisualState>
        <VisualState
          x:Name="Uninstalled">
          <Storyboard>
            <ObjectAnimationUsingKeyFrames
              Storyboard.TargetProperty="(UIElement.Visibility)"
              Storyboard.TargetName="viewbox1">
              <DiscreteObjectKeyFrame
                KeyTime="0">
                <DiscreteObjectKeyFrame.Value>
                  <Visibility>Collapsed</Visibility>
                </DiscreteObjectKeyFrame.Value>
              </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
          </Storyboard>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <Viewbox
      x:Name="viewbox1">
      <TextBlock
        x:Name="textBlock"
        Text="App Already Installed" />
    </Viewbox>
    <Viewbox
      x:Name="viewbox">
      <Button
        Content="Click to Install"
        Command="{Binding InstallCommand}" />
    </Viewbox>
  </Grid>
</UserControl>

You’ll notice that I just have 2 ViewBoxes with one containing a Button that can drive an installation and one containing a simple piece of text to tell the user that the application is already installed.

There’s a couple of visual states defined – Installed and Uninstalled which show/hide the right bits of UI respectively.

This view uses a tiny bit of code behind in order to export itself as “MainView” via MEF and to import its view model;

 [ExportView(ViewName = "MainView")]
  public partial class MainView : UserControl
  {
    public MainView()
    {
      InitializeComponent();
    }
    [Import("MainViewModel")]
    public object ViewModel
    {
      set
      {
        this.DataContext = value;
      }
    }
  }

The ViewModel for this view tries to determine whether the application is [installed/uninstalled] and [in-browser/out-of-browser] and I took the decision to directly bind that status to the visual state manager in the View.

You’ll notice back in that View’s XAML that there’s an attached property BindableVsmState.VsmState and this binds to the ViewModel such that when the ViewModel changes its property called ViewState we should see the VSM move states. The ViewModel looks like;

 [Export("MainViewModel", typeof(object))]
  public class MainViewModel : PropertyChangedNotification
  {
    public MainViewModel()
    {
      Application.Current.InstallStateChanged += OnInstallStateChanged;
      OnInstallStateChanged(null, null);

      this.InstallCommand = new ActionCommand(OnInstallCommand);
    }
    void OnInstallCommand()
    {
      Application.Current.Install();
    }
    void OnInstallStateChanged(object sender, EventArgs e)
    {
      switch (Application.Current.InstallState)
      {
        case InstallState.InstallFailed:
          break;
        case InstallState.Installed:
          this.ViewState = ViewStateInstalled;
          break;
        case InstallState.Installing:
          break;
        case InstallState.NotInstalled:
          this.ViewState = ViewStateUninstalled;
          break;
        default:
          break;
      }
    }
    public string ViewState
    {
      get
      {
        return (_ViewState);
      }
      set
      {
        _ViewState = value;
        RaisePropertyChanged("ViewState");
      }
    }

    public ICommand InstallCommand
    {
      get
      {
        return (_InstallCommand);
      }
      set
      {
        _InstallCommand = value;
        RaisePropertyChanged("InstallCommand");
      }
    }
    ICommand _InstallCommand;

    string _ViewState;
    const string ViewStateInstalled = "Installed";
    const string ViewStateUninstalled = "Uninstalled";
  }

and I should probably do something about these hard-coded strings which are duplicated between the View and the ViewModel. I could also be a little less directly dependent on Application.Current.Install and move that to an interface to make this more testable.

That binding uses a class BindableVsmState – I reworked this from a sample that I found online and now I can’t find the original sample online again so apologies to the original author for that but mine ended up looking like this;

public static class BindableVsmState
  {
    public static string GetVsmState(DependencyObject obj)
    {
      return ((string)obj.GetValue(VsmStateProperty));
    }
    public static void SetVsmState(DependencyObject obj, string value)
    {
      obj.SetValue(VsmStateProperty, value);
    }
    public static readonly DependencyProperty VsmStateProperty =
      DependencyProperty.RegisterAttached("VsmStateProperty", typeof(string),
        typeof(BindableVsmState),
        new PropertyMetadata(OnPropertyChanged));

    public static void OnPropertyChanged(DependencyObject sender,
      DependencyPropertyChangedEventArgs args)
    {
      string stateName = args.NewValue as string;
      Control control = sender as Control;

      if (!string.IsNullOrEmpty(stateName) &&
        (control != null)) 
      {
        VisualStateManager.GoToState(control, stateName, true);
      }
    }
  }

and that lets me declaratively link the VSM’s state to a property on the DataContext.

I altered my configuration in my main shell’s app.xaml which decides which XAPs to load in which context;

<Application
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="PDCTutorial.MainShell.App"
  xmlns:xm="clr-namespace:PDCTutorial.XapManagement;assembly=PDCTutorial.XapManagement">
  <Application.ApplicationLifetimeObjects>
    <xm:AppXapConfiguration XapsDownloaded="OnXapsDownloaded">
      <xm:AppXapConfiguration.Xaps>
        <xm:XapConfiguration
          Name="ViewManagement"
          Source="PDCTutorial.DeferredViewManagement.xap"
          LoadContext="All"/>
        <xm:XapConfiguration
          Name="DataModel"
          Source="PDCTutorial.DataModel.xap" 
          LoadContext="OutOfBrowser"/>
        <xm:XapConfiguration
          Name="MainShell"
          Source="PDCTutorial.MainShell.xap" 
          LoadContext="All"/>
        <xm:XapConfiguration
          Name="Views"
          Source="PDCTutorial.Views.xap" 
          LoadContext="OutOfBrowser"/>
        <xm:XapConfiguration
          Name="InBrowserViews"
          Source="PDCTutorial.InBrowserViews.xap"
          LoadContext="InBrowser" />
      </xm:AppXapConfiguration.Xaps>
    </xm:AppXapConfiguration>
  </Application.ApplicationLifetimeObjects>
</Application>

and so in the browser only the ViewManagement and InBrowserViews will load, hopefully keeping things pretty small and quick.

A different set of XAPs will load out-of-browser.

With this all in place, I can now run my application in the browser;

image

and I can choose the option to install;

image

and it’s working nicely in the browser and out-of-browser.

What’s Still Missing

There’s a tonne of work to do here yet including things like;

  1. Build the data model
  2. Build some kind of downloading component
  3. Build the tracks view, the sessions view and the download view
  4. Come up with some messaging infrastructure via which one view can talk to another
  5. Make it look a lot better than it does right now

Where’s the Source?

Here’s the source code for download.

What’s Next?

It’s time to start calling some data services and get some real live data onto the screen.