Silverlight 3: “Bye Bye” Beta, “Hello” RTM :-)

I got hold of the release of Silverlight 3 today and thought that I’d take a look and see if I could spot changes from the beta ( rather than write a complete list of what’s new in V3 – that’s a different post 🙂 );

Firstly, I should say that I downloaded bits from here

There’s a changes document in the SDK so if you want a definitive guide then go read that but ( for my own purposes ) I had a look around and wrote up what I found below.

The very first thing I noticed when doing a File->New->Project is that the Visual Studio project template no longer throws in a UserControl with a fixed Width and Height. Woohoo! It’s using the new DesignWidth and DesignHeight properties from Blend. Cool. Glad to see that.

Beyond that, here’s a few others things that I spotted on my first walk-through of the RTM bits – there are some things that I’m not 100% whether they have newly arrived or whether I’ve just missed them previously and, similarly, I’m sure there’s are many more things beyond this list so don’t treat this as a definitive list.

Update: Between the time that I started writing this post and the time when I published it, I noticed that Tim has dropped a post up here which is more comprehensive so if you’re reading this and you’ve not read Tim’s post then make sure you read his post too.

Out-Of-Browser Applications

Firstly, there’s a new set of properties that you can specify for out-of-browser;

image

and that then launches a dialog with more options;

image

of the settings there – I found the “Use GPU Acceleration” setting really interesting as I guess ( and it’s just a guess ) that this setting is switching a flag which makes the offline application host instantiate the Silverlight plug-in it with enableGPUAcceleration switched on ( note – that doesn’t mean that everything is suddenly magically GPU accelerated but that’s another story ).

Using this window populates an OutOfBrowserSettings.xml file in the project and that looks to get ultimately combined with the AppManifest.xml file into the AppManifest.xml file that goes into the XAP. I was pleased to see that <OutOfBrowserSettings.Blurb> looks to have made it through to the RTM! 🙂

There are other parameters on the Silverlight plug-in that I don’t think are settable in the same way here although if you need those settings for debugging purposes you can find the index.html file under your equivalent of c:\users\mtaulty\appdata\locallow\Microsoft\Silverlight\OutOfBrowser and temporarily alter the .HTML file if that helps.

There’s also control there for the icons ( as previously ) and for the Window size.

It also looks like some of the code aspects of this have changed – what I noticed;

  1. Installed status is determined from Application.Current.InstallState and can be Installed, InstallFailed, Installing, NotInstalled
  2. Installation is now started by calling Application.Current.Install() rather than Detach()
  3. Monitoring changes to installation state is done by Application.Current.InstallStateChanged
  4. Can’t remember whether this flag was there before or not but Application.Current.IsRunningOutOfBrowser is also relevant here.

In my initial experiment I noticed that removing the locally installed application did not seem to alter Application.Current.InstallState for a running browser-based version of the application.

It also looks like the update model has changed. Previously, you ran the offline application and it would go back to the server that it came from, check for any updates and fire an event when it spotted them and brought them down.

That has been brought under the control of the developer with Application.Current.CheckAndDownloadUpdateAsync which then fires CheckAndDownloadUpdateCompleted with a CheckAndDownloadUpdateCompletedEventArgs which has a bool UpdateAvailable which tells you whether there’s been an update or not. I don’t think there’s a way of separating out the check for an update from the download of the update.

Once you’ve made the call, the next time the user runs the app they’ll get the update – worked fine for me against my local Cassini web server which is something I struggled with on the beta.

It looks like you can also hide the right-mouse-menu that Silverlight displays to offer the “Install this application….” if you want to only offer installation via your own custom UI and not by the standard mechanism – whilst this option doesn’t show up in VS, it’s documented for the OutOfBrowserSettings.xml file in the SDK under “ShowInstallMenuItem”.

 

HTTP Stack – Client HTTP Handling

I need to return to this – I hadn’t noticed this in the beta but it looks like in the RTM you have the option to use either the browser network stack ( as in Silverlight 2 ) which ( for example ) limits you down to only using HTTP GET/POST or you can use the client networking stack ( and mix/match ) which looks to;

  1. Take away that limitation and allow you to get to other HTTP verbs
  2. Get hold of cookies

Interesting stuff – I need to revisit. This has links up to the out-of-browser capabilities because in the browser you could always do one of two things around HTTP;

  1. Use the Silverlight managed capabilities
  2. Interop your way across to XmlHttpRequest if you needed more control

However…once you went out of browser you lost the ability to do the interop to XmlHttpRequest and so that became an issue and I think this alternate HTTP handling via the client stack mitigates that.

Interesting, interesting, interesting – I suspect that this also impacts some WCF capabilities so need to do a bit of reading there.

 

Controls – Moved to the Silverlight Toolkit

It looks like a number of controls have moved from Silverlight to the Silverlight Toolkit including – DockPanel, WrapPanel, Expander, ViewBox, DataForm. That comes straight from the release notes so if you’re wanting to find these controls ( and they’re all very useful controls to have in your toolbox ) then you need to be looking to the Toolkit to provide them.

 

Application Library Caching ( or “Transparent Platform Extensions” ) – Changes

I’ve demo’d this to many folks on the beta.

The essential idea is that if you reference some assembly from your application such as System.Xml.Linq.dll then in Silverlight 2 that assembly goes into your XAP file and you pay the price for deploying it in that your XAP file grows for each assembly that you reference and every time you rev the app the browser will have to download the whole XAP again even though large chunks of it ( e.g. System.Xml.Linq.dll ) aren’t going to change.

In Silverlight 3 you have the option;

image

in the beta what that did was it left System.Xml.Linq.dll ( as an example ) outside of your XAP and downloaded as a separate file from Microsoft.com.

It doesn’t work with out-of-browser applications as I found to my cost in a live demo one day 🙂

In the RTM, you’ll get a message box if you try to combine this feature with “out-of-browser” as in;

image

so that would have saved my embarrassment 🙂

The behaviour has also changed.

When you reference an assembly we check to see whether the assembly has an .extmap.xml file alongside it. If not, it’s a regular reference and would get bundled into the XAP – e.g. if you just build a class library and reference it then this looks to be what happens and I’m not ( yet ) aware of any Visual Studio option which will magically make a .extmap.xml file for a class library.

If you do have an .extmap.xml file with the assembly then Visual Studio will reference it as an external application part. Here’s the .extmap.xml file for System.Xml.Linq.dll;

<?xml version="1.0"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <assembly>
    <name>System.Xml.Linq</name>
    <version>2.0.5.0</version>
    <publickeytoken>31bf3856ad364e35</publickeytoken>
    <relpath>System.Xml.Linq.dll</relpath>
    <extension downloadUri="System.Xml.Linq.zip" />
  </assembly>


</manifest>

The downloadUri is important in that if it’s a relative file name as above then Visual Studio will package the assembly into that zip file for you and copy it to the output folder and if two assemblies share the same filename then they’ll end up in the same ZIP file and ( for my System.Xml.Linq.dll example ) I then end up with a manifest that looks like;

  <Deployment.ExternalParts>
    <ExtensionPart Source="System.Xml.Linq.zip" />
  </Deployment.ExternalParts>

so the assembly is no longer coming from Microsoft.com – it’s coming from my site but I still get the benefit of not having this external part linked to my application XAP from the point of view of making good use of the browser’s cache.

If the downloadUri is not a relative file name then Visual Studio doesn’t do anything about it other than generate the right manifest but that means that you don’t have to use relative URLs to actually download these bits and so you could have 5 apps all pulling System.Xml.Linq.dll from a single ZIP file in a single place.

 

Application Extension Services

Pretty sure I made a video about this once and a search around reveals that I did but it looks to have changed somewhat in the RTM – as far as I can tell the interfaces here IApplicationService and IApplicationLifetimeAware haven’t changed but the way you register them has changed naming a little so I now have something like;

    <Application.ApplicationLifetimeObjects>
        <local:MyService />
    </Application.ApplicationLifetimeObjects>

in my App.xaml registering my service class MyService which implements IApplicationService. The way in which you “locate” services has also changed in that you now have Application.Current.ApplicationLifetimeObjects which is a collection indexible only by integer which feels a little brittle to me and the docs call that out by suggesting that you implement a static property on your service as a better way of someone locating an instance of the service.

 

DataForm Customisation

The way in which the fields of a DataForm are customised looks to have changed. In the beta there were a number of specific controls such as DataFormTextField that you could use as a “higher level alternative” to customising the DataForm by completely replacing the template that it uses for display or edit.

That kind of customisation has changed. If I’ve got an instance of this kind of class providing the DataContext for a DataForm;

  public class Person
  {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
  }

then by default the DataForm will generate the UI for me based on AutoGenerateFields but if I switch that off then I can try and explicitly replicate what it would generate for me by doing something like;

        <df:DataForm
            x:Name="myForm"
            CurrentItem="{Binding}"
            AutoEdit="False"
            AutoGenerateFields="False">
            <df:DataForm.ReadOnlyTemplate>
                <DataTemplate>
                    <StackPanel>
                        <df:DataField
                            LabelPosition="Left">
                            <TextBlock
                                Text="{Binding FirstName}" />
                        </df:DataField>
                        <df:DataField
                            LabelPosition="Left">
                            <TextBlock
                                Text="{Binding LastName}" />
                        </df:DataField>
                        <df:DataField
                            LabelPosition="Left">
                            <TextBlock
                                Text="{Binding Age}" />
                        </df:DataField>
                    </StackPanel>
                </DataTemplate>
            </df:DataForm.ReadOnlyTemplate>
            <df:DataForm.EditTemplate>
                <DataTemplate>
                    <StackPanel>
                        <df:DataField
                            LabelPosition="Left">
                            <TextBox
                                Text="{Binding FirstName}" />
                        </df:DataField>
                        <df:DataField
                            LabelPosition="Left">
                            <TextBox
                                Text="{Binding LastName}" />
                        </df:DataField>
                        <df:DataField
                            LabelPosition="Left">
                            <TextBox
                                Text="{Binding Age}" />
                        </df:DataField>
                    </StackPanel>
                </DataTemplate>
            </df:DataForm.EditTemplate>
        </df:DataForm>
    </Grid>

 

additionally, there was a Bindable attribute in the beta that you could use to control which parts of your type showed up in the UI automatically generated by the DataForm or DataGrid. That attribute has gone but you can achieve the same thing with the Display attribute ( which was there already ) and the Edtiable attribute ( not sure if that was there in the beta ).

 

WriteableBitmap

The API has changed a little here to remove things like Lock and Unlock and you use Invalidate instead so if image1 is an Image then this paints stripes of red/green;

     WriteableBitmap bitmap = new WriteableBitmap(192, 192);

      for (int i = 0; i < 192 * 192; i++)
      {
        unchecked
        {
          bitmap.Pixels = (i & 1) == 0 ? (int)0xFFFF0000 : (int)0xFF00FF00;
        }
      }      

      image1.Source = bitmap;

      bitmap.Invalidate();

and if myGrid is a DataGrid and image2 is an Image then this paints the Image with a rendered grid;

   WriteableBitmap bitmap = new WriteableBitmap(192, 192);

      bitmap.Render(myGrid, new ScaleTransform()
      {
        ScaleX = 192 / myGrid.ActualWidth,
        ScaleY = 192 / myGrid.ActualHeight
      });
      bitmap.Invalidate();

      image2.Source = bitmap; 

and you can now grab the values of the pixels after you’ve set them albeit as a set of integer values that you’d then need to encode in some way if you wanted to persist it as (say) a PNG or JPEG or similar.

 

Browser Zooming

Zooming the web browser now zooms the Silverlight content.

I’m not 100% sure on this one yet but I think that if you handle the Resize event or if you handle the new Zoomed event on the Application.Current.Host.Content then you won’t get automatically zoomed whereas if you don’t do either of those that then Silverlight looks to do the zoom for you ( one more caveat – unless you’ve opted out by setting the new enableAutoZoom property on the plug-in instantiation to false ).

So, with a UI like this;

<UserControl
    x:Class="SilverlightApplication24.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignWidth="640"
    d:DesignHeight="480">
    <Grid
        x:Name="LayoutRoot">
        <TextBlock
            x:Name="txtZoomFactor"
            Text="{Binding ZoomFactor}">
            <TextBlock.RenderTransform>
            <ScaleTransform
                x:Name="scale" />
                </TextBlock.RenderTransform>
        </TextBlock>
    </Grid>
</UserControl>

I could try and make sure only my TextBlock zooms with a bit of code like;

  public class BindableZoomFactor : INotifyPropertyChanged
  {
    public BindableZoomFactor(Content content)
    {
      PropertyChanged += (s, e) => { };

      this.content = content;

      this.content.Zoomed += (s, e) =>
        {
          PropertyChanged(this, new PropertyChangedEventArgs("ZoomFactor"));
        };
    }
    public double ZoomFactor
    {
      get
      {
        return (content.ZoomFactor);
      }
      set
      {
      }
    }
    Content content;

    public event PropertyChangedEventHandler PropertyChanged;
  }
  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      BindableZoomFactor factor = new BindableZoomFactor(
        Application.Current.Host.Content);

      this.DataContext = factor;

      factor.PropertyChanged += (s, e) =>
        {
          // Shame I can't bind these too...
          scale.ScaleX = scale.ScaleY = factor.ZoomFactor;
        };
    }
  }

and then as the user zooms the browser, only my TextBlock zooms rather than the whole UI.

 

Mouse Wheel Support

Anyone who’s played with (e.g.) DeepZoom will be pleased to see a MouseWheel event show up on UIElement – was this in the beta? I missed it if it was.

 

Projection

I’ve said many times in demos around the new 2.5D support via the Projection property that the only value you can provide is an instance of PlaneProjection. No longer true in the RTM bits – there’s also a Matrix3DProjection that you can supply as a value for Projection.

 

Analytics Class

Again – I hadn’t spotted this one previously but it can report information such as how much CPU is in use and how much of that is being used by our process – it can also return a little info about the graphics driver.

 

ClearType Support

Hope I get this right but text in SL3 is rendered with ClearType by default – here’s a zoomed piece of text;

image

where you can clearly see the colours being used which is indicative of rendering with ClearType on ( as far as I know and that’s the bit I hope I got right ).

 

Virtualization in ItemsControls

The ListBox in Silverlight 3 virtualises by default. Virtualisation is something that’s present in WPF – the idea is that if you have an ItemsControl like a ListBox displaying 10,000 items then you don’t need to necessarily create the UI for all of the 10,000 items. You can just create the UI for the items that are on the screen ( e.g. items 1-5 ).

There’s a second aspect to this which is to recycle item containers – continuing that example, as item 1 scrolls off the screen then as long as item 6 is displayed in a consistent manner you can probably re-use the UI that was created to display item 1 and just slot in the data values for item 6.

There’s a third aspect to this 🙂 which is whether you use logical or physical scrolling – logical scrolling is based on scrolling through items where as physical scrolling is based on scrolling through "areas of screen” in terms of actual pixel-based sizes.

WPF also has the idea of deferred scrolling which on a scroll from item 5 to item 500 does not display all the intermediate items that scroll past as you make that transition but, as far as I know, that’s not here in Silverlight 3.

An example – if I have a UI like this;

<UserControl
    x:Class="SilverlightApplication22.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignWidth="640"
    d:DesignHeight="480"
    xmlns:local="clr-namespace:SilverlightApplication22">
    <Grid
        x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ListBox
            Margin="10"
            x:Name="lstData" 
            ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <local:MyGrid>
                        <TextBlock
                            Text="{Binding InstanceNumber}" />
                    </local:MyGrid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox
            Grid.Row="1"
            Margin="10"
            ItemsSource="{Binding}"
            x:Name="lstDiagnostics">

        </ListBox>
    </Grid>
</UserControl>

with code behind which tries to count how many instances of MyGrid have been created and also how many times the InstanceNumber property has been accessed;

  public class Diagnostics
  {
    private Diagnostics()
    {
      Messages = new ObservableCollection<string>();
    }
    static Diagnostics()
    { 
      instance = new Diagnostics();
    }
    public static Diagnostics Instance
    {
      get
      {
        return(instance);
      }
    }
    public void Log(string message)
    {
      Messages.Add(string.Format("{0} [{1}]",
        DateTime.Now.ToShortTimeString(), message));
    }
    static Diagnostics instance;
    public ObservableCollection<string> Messages { get; set; }
  }
  public class MyGrid : Grid
  {
    public MyGrid()
    {
      instanceNumber = ++instanceCount;

      Diagnostics.Instance.Log(string.Format("Created grid {0}",
        instanceNumber));
    }
    int instanceNumber;
    static int instanceCount;
  }
  public class MyData
  {
    public MyData(int i)
    {
      instanceNumber = i;
    }
    public int InstanceNumber
    {
      get
      {
        if (!accessed)
        {
          Diagnostics.Instance.Log(string.Format(
            "Accessed instance {0} for first time", instanceNumber));
          accessed = true;
        }
        return (instanceNumber);
      }
    }
    int instanceNumber;
    bool accessed;
  }
  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();

      lstDiagnostics.DataContext = Diagnostics.Instance.Messages;

      lstData.DataContext = from i in Enumerable.Range(1, 1000)
                            select new MyData(i);
    }
  }

then it makes it fairly easy to see that ( by default ) Silverlight’s ListBox will only create the number of grids that it needs for display and will recycle them and that it only accesses properties providing data-bound values as those values are scrolled onto the screen as in;

image

so you can see that even though I have 1000 items in my top ListBox, the diagnostics says that we’ve only created 17 grids and accessed 17 instances. If I scroll the ListBox around a little, I won’t see any more grids created ( unless I re-size it to force the issue ).

Unlike WPF, you can’t just switched virtualisation on/off in Silveright by setting a property on the control – the ListBox template is built to use VirtualizingStackPanel so if you want to have a non-virtualising version then you’ll need to re-template the ListBox to go back to using a plain old StackPanel.

Multi-Touch Support

This lines up with some of what I’ve been looking at recently on Windows 7 and it seems at the lower level end of the spectrum in that you look to get basic touch message support but not gesture support or inertia support – not as far as I’ve seen so far. So, I can make something like a Canvas;

<UserControl
    x:Class="SilverlightApplication27.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignWidth="640"
    d:DesignHeight="480">
    <Canvas
        x:Name="canvasDrawing"
        Background="Yellow">
        
    </Canvas>
</UserControl>

and then write a bit of code behind that using the Touch and TouchPoint classes to pick up touch events and draw some lines in response to them;

  internal class ColorManager
  {
    public ColorManager()
    {
      colors = new Dictionary<int, Color>();

      colorOptions = new Stack<Color>();

      // Beyond 3 touch points and we explode...
      colorOptions.Push(Color.FromArgb(255, 255, 0, 0));
      colorOptions.Push(Color.FromArgb(255, 0, 255, 0));
      colorOptions.Push(Color.FromArgb(255, 0, 0, 255));
    }
    public Color GetColorForDevice(int deviceId)
    {
      if (!colors.ContainsKey(deviceId))
      {
        colors[deviceId] = colorOptions.Pop();
      }
      return (colors[deviceId]);
    }
    Stack<Color> colorOptions;
    Dictionary<int, Color> colors;
  }

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

      state = new Dictionary<int, Polyline>();
      colorManager = new ColorManager();

      Touch.FrameReported += OnTouchFrame;
    }
    void OnTouchFrame(object sender, TouchFrameEventArgs e)
    {
      foreach (TouchPoint pt in e.GetTouchPoints(canvasDrawing))
      {
        switch (pt.Action)
        {
          case TouchAction.Down:
            Polyline line = new Polyline()
            {
              Stroke = new SolidColorBrush(colorManager.GetColorForDevice(
                pt.TouchDevice.Id)),
              StrokeThickness = Math.Ceiling(Math.Sqrt(Math.Pow(pt.Size.Width, 2) +
                Math.Pow(pt.Size.Height, 2))),
            };
            state[pt.TouchDevice.Id] = line;
            line.Points.Add(pt.Position);
            canvasDrawing.Children.Add(line);
            break;
          case TouchAction.Move:
            Polyline currentLine = state[pt.TouchDevice.Id];
            currentLine.Points.Add(pt.Position);
            break;
          case TouchAction.Up:
            state.Remove(pt.TouchDevice.Id);
            break;
          default:
            break;
        }
      }
    }
    Dictionary<int, Polyline> state;
    ColorManager colorManager;
  }

the key thing seeming to be the event Touch.FrameReported and then getting hold of the actual TouchPoints via the GetTouchPoints() method on the TouchFrameEventArgs. That’s as far as I’ve gone with this so far so if there is additional support ( beyond the touch events mimicking the mouse ) then I’ll update the post when I’ve learned more.