Dispatcher and Multi-threading in Silverlight 3

In Silverlight 3, as with WinForms and WPF,  we need to be careful about cross thread communication. Controls instantiated in the XAML DOM belong to the UI thread and only the UI thread can update them. To see this, try updating the UI from a different thread,

public MainPage() {
    InitializeComponent();
 
    // Explicitly start a new thread,
    Thread myThread = new Thread(FillRectangle);
    myThread.Start();
}
 
void FillRectangle() {
    // Try to update the UI thread...
    MyRectangle.Fill = new SolidColorBrush(Colors.Red);
}
 

You’ll see an error like this,

Capture

To alleviate this Silverlight gives us two constructs,

1. The Dispatcher class: This class guarantees  that the code will be executed on the UI thread.

public MainPage() {
    InitializeComponent();
 
    // Explicitly start a new thread
    Thread myThread = new Thread(FillRectangle);
    myThread.Start();
}
 
void FillRectangle() {
    // Can't update the UI thread directly
    //MyRectangle.Fill = new SolidColorBrush(Colors.Red);

    // But perfectly safe to update the UI using the Dispatcher
    Dispatcher.BeginInvoke(() => MyRectangle.Fill = new SolidColorBrush(Colors.Red));
}
 

2. The BackgroundWorker class: Using this class gives you a new thread but also gives you a way to update the UI in a safe way.

public MainPage() {
    InitializeComponent();
    BackgroundWorker bg = new BackgroundWorker();

    // This method is called on the background thread
    bg.DoWork += CrossThreadUnsafe;

    // This method is called on the UI thread
    bg.RunWorkerCompleted += CrossThreadSafe;

    // Start the thread
    bg.RunWorkerAsync();
}
 
void CrossThreadUnsafe(object sender, DoWorkEventArgs e) {
    // Don't update the UI here - uncommenting will cause 'Invalid cross-thread access error'
    //MyRectangle.Fill = new SolidColorBrush(Colors.Red);
}
 
void CrossThreadSafe(object sender, RunWorkerCompletedEventArgs e) {
    // Perfectly safe to update the UI from here
    MyRectangle.Fill = new SolidColorBrush(Colors.Red);
}
 

Or if you want, write your own BackgroundWorker class.

Also, Silverlight provides a DispatcherTimer  class which is a UI-thread safe timer (as opposed to the Timer class), for an example see here.

Digg This
Advertisements

Five different ways to animate in Silverlight 3

There are a variety of ways to animate an object in Silverlight, let’s take a look at them today. For demo purposes say we’re trying to move a simple rectangle defined in xaml like this,

<Canvas x:Name="LayoutRoot" Background="White">
        <Rectangle x:Name="MyRectangle"  Canvas.Top="100" 
                   Canvas.Left="100" Height="50" Width="50" Fill="Black" />
</Canvas>
 

1. Using Storyboard.Begin():

// Animate the canvas left property
DoubleAnimation daLeft = new DoubleAnimation();
daLeft.To = 200.0;
daLeft.Duration = new Duration(new TimeSpan(0, 0, 0, 1));

// Set the storyboard target and target property
Storyboard.SetTarget(daLeft, MyRectangle);
Storyboard.SetTargetProperty(daLeft, new PropertyPath(Canvas.LeftProperty));

// Add the animation to the storyboard and start it
Storyboard st = new Storyboard();
st.Children.Add(daLeft);
st.Begin();
 

2. Using an empty Storyboard as a timer:

Storyboard emptyStoryboard = new Storyboard();

public MainPage() {
    InitializeComponent();

    emptyStoryboard.Duration = TimeSpan.FromMilliseconds(0);
    emptyStoryboard.Completed += new EventHandler(AnimateRectangle);
    emptyStoryboard.Begin();
}

void AnimateRectangle(object sender, EventArgs e) {
    // Animate the canvas left property
    MyRectangle.SetValue(Canvas.LeftProperty, 
                                (double)MyRectangle.GetValue(Canvas.LeftProperty) + 1.0);

    if ((double)MyRectangle.GetValue(Canvas.LeftProperty) > 200.0)
        emptyStoryboard.Stop();
    else
        emptyStoryboard.Begin();        
}
 

3. Using System.Windows.Media.CompositionTarget.Rendering:

public MainPage() {
    InitializeComponent();

    CompositionTarget.Rendering += new EventHandler(AnimateRectangle);
}

void AnimateRectangle(object sender, EventArgs e) {
    // Animate the canvas left property
    MyRectangle.SetValue(Canvas.LeftProperty, 
                                (double)MyRectangle.GetValue(Canvas.LeftProperty) + 1.0);

    if ((double)MyRectangle.GetValue(Canvas.LeftProperty) > 200.0)
        MyRectangle.SetValue(Canvas.LeftProperty, 200.0);
}
 

4. Using a System.Windows.Threading.DispatcherTimer:

private DispatcherTimer Timer = new DispatcherTimer();

public MainPage() {
    InitializeComponent();

    Timer.Interval = TimeSpan.Zero;
    Timer.Tick += new EventHandler(AnimateRectangle);
    Timer.Start();
}

void AnimateRectangle(object sender, EventArgs e) {            
    // Animate the canvas left property, this is UI-thread safe            
    MyRectangle.SetValue(Canvas.LeftProperty, 
                                (double)MyRectangle.GetValue(Canvas.LeftProperty) + 1.0);

    if ((double)MyRectangle.GetValue(Canvas.LeftProperty) > 200.0)
        Timer.Stop();
}

 

5. Using a System.Threading.Timer:

Timer Timer = null;

public MainPage() {
    InitializeComponent();
    Timer = new Timer(Animate, null, 0, 1); 
}

void Animate(object state) {
    // This is required because the Animate method is called on a different thread than the UI
    Dispatcher.BeginInvoke(new Action(AnimateRectangle));
}

void AnimateRectangle() {
    // Animate the canvas left property 
    MyRectangle.SetValue(Canvas.LeftProperty, 
                                (double)MyRectangle.GetValue(Canvas.LeftProperty) + 1.0);

    if ((double)MyRectangle.GetValue(Canvas.LeftProperty) > 200.0)
        Timer.Dispose();
} 
Digg This

Changing the namespace of a Silverlight 3 project (in VS2008)

Say you want to change the namespace of your Silverlight 3 app (this should work for Silverlight 2 too) from the default,

namespace SilverlightApplication1
to,
namespace My.Silverlight.Prototypes.Demo
 

Then the changes you need to make in the code are,

1. Change the namespace in App.xaml.cs and in App.xaml change the x:Class,

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="My.Silverlight.Prototypes.Demo.App">
 

2. Change the namespace in MainPage.xaml.cs and in MainPage.xaml change the x:Class,

<UserControl x:Class="My.Silverlight.Prototypes.Demo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
 

3. Trying to run the app now will result in an error,

Error: Sys.InvalidOperationException: InitializeError error #2103 in control ‘Silverlight1’: Invalid or malformed application: Check manifest

4. This is because the startup object is wrong, right-click the SilverlightApplication1 and select ‘Properties’, and make sure the ‘Startup object’ is set to ‘My.Silverlight.Prototypes.Demo.App’.

Properties

5. Everything should run now, though I recommend changing the assembly name, default namespace, and xap file name,

PropertiesII

6. If you follow step 5, don’t forget to change your aspx/html page to point to the new xap file,

<asp:Silverlight ID="Silverlight1" runat="server" Source="~/ClintBin/My.Silverlight.Prototypes.Demo.xap" 
MinimumVersion="3.0.40307.0" Width="100%" Height="100%" />

 

else, you’ll get the following error, because the aspx page couldn’t find the xap file.

Error: Sys.InvalidOperationException: InitializeError error #2104 in control ‘Silverlight1’: Could not download the Silverlight application. Check web server settings

Note: If your Silverlight content doesn’t show up in your aspx/html page after step 6, make sure you Clean and Rebuild your Silverlight application and regenerate your xap file by deleting it.

Digg This

How to derive from a base page in Silverlight

[This post comes to you curtsey of Windows Live Writer – an excellent blogging tool, and the code snippets are from the Paste from Visual Studio plugin.]

Say you want to have a base page XAML that offers some functionality, and you’d like to keep that functionality but insert your own. For example, you want to write XAML for a window with a title bar, border, and a close window icon, and then you want to write XAML for the different content that will be displayed inside the window. The different content XAMLs can be swapped in and out and they take advantage of the base XAML that provides the title bar, border, and close window functionality.

We’ll create a base XAML called say, DemoControl.xaml (which derives from UserControl), this XAML contains the titlebar and the close icon (no border to keep it simple),

<UserControl x:Class="DemoControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Canvas x:Name="LayoutRoot">
        <Canvas x:Name="Toolbar" Opacity="1.0">
            <Canvas x:Name="TitleBar" Background="Brown" Margin="0,-40" Height="40" Width="200" />
            <Image x:Name="CloseIcon" Margin="180,-40" Height="32" Width="32" Source="delete.png" />
        </Canvas>
    </Canvas>
</UserControl>

Then we’ll create the derived control called say About.xaml, that derives from DemoControl,

<Demo:DemoControl x:Class="About"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Demo="clr-namespace:Demo">
</Demo:DemoControl>

<!--Make sure you include the namespace of the DemoControl!—>

We can’t put any XAML inside About.xaml, but we can inject another XAML via code, (in About.xaml.cs do this),

Canvas cv = (Canvas)this.FindName("LayoutRoot");
StreamResourceInfo sri = Application.GetResourceStream(
                         new Uri("Demo;component/XamlFiles/AboutContent.xaml", UriKind.Relative));
using (TextReader tr = new StreamReader(sri.Stream))
{
    String url = tr.ReadToEnd();
    UserControl rootObject = XamlReader.Load(url) as UserControl;
    rootObject.Name = "test";
    Canvas cd = (Canvas)rootObject.FindName("LayoutRoot");
    cv.Children.Add(rootObject);
}

Where AboutContent.xaml, could be any XAML derived from UserControl,

<UserControl 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
    <Canvas x:Name="LayoutRoot" Height="631" Width="450" Background="White">
        <!—Place any content here!—>
    </Canvas>
</UserControl>

Thus, we can insert AboutContent.xaml into the derived page About.xaml and have the titlebar and closeicon show up without AboutContent.xaml having to write or wire that code.

Digg This