The idea is really great: Write your code once, and reuse it across all your platforms. This is one of the great things about .NET. I can reuse business logic in ASP.NET, Mobile, Desktop, Embedded devices, Phones, Web services and so on. Usually the only difference is the UI that behaves a little different on the various platforms.
With WPF and Silverlight, even the UI code can be reused between Desktop and Web, since Silverlight is a subset of WPF. That means that any Silverlight code should be able to run in a WPF application, right? Well at least that’s what Microsoft keeps telling us, and you will fine numerous blog posts discussing it. However, in reality it’s not that straight forward. I’ve been trying to reuse some Silverlight code in a WPF application, and ran into a lot of headaches. What I found is that Silverlight is NOT a subset of WPF and .NET. It is a completely new framework with the subset as a design goal, but Microsoft made several mistakes, some even so bad that fixing it in either WPF or Silverlight would cause a breaking change. In other cases the XAML syntax is incompatible different. This means that you won’t get around using several compiler conditionals and duplicate XAML files.
Below is a list of some of the things I’ve run into, and I’ll try and add to it if I found more. I you know of more, or find errors or explanation to differences please add comments below.
For code that is only for Silverlight I have declared a SILVERLIGHT compiler conditional. so anything inside a #if SILVERLIGHT block is Silverlight only code, and vice verse if it’s in #if !SILVERLIGHT or #else it’s for WPF.
As a side note, the following blog posts discusses some good practices for reusing the same code files in your WPF and Silverlight projects: Sharing source code between .NET and Silverlight. However it hardly deals with code differences like the ones below.
Loading a bitmap
Loading bitmaps are probably one of the biggest differences in the two frameworks (that I’ve seen). In WPF you must call BeginInit and EndInit, and checking for download errors are very different, which requires you to use two different handlers for handling an image load error.
BitmapImage bmi = new BitmapImage();
#if
!SILVERLIGHTbmi.BeginInit();
#endif
Image img = new Image();
bmi.UriSource = new Uri(url, UriKind.Absolute);
#if
SILVERLIGHTimg.ImageFailed += new EventHandler<ExceptionRoutedEventArgs>(img_ImageFailed); //Silverlight specific error handler
#else
bmi.DownloadFailed += new EventHandler<System.Windows.Media.ExceptionEventArgs>(bmi_DownloadFailed); //WPF specific error handler
bmi.EndInit();
#endif
img.Source = bmi;
Modifying Animations
WPF is very restrictive when it comes to working with animations. When you initialize the animation, you must use an overload that takes the element you are modifying as well as specifying that its modifiable. Silverlight doesn’t have any of these overloads, and is there fore not required.
#if SILVERLIGHT
myStoryboard.Begin();
myStoryboard.Stop();
#else
myStoryboard.Begin(element, true); //true allows for changing animation later
myStoryboard.Stop(element); //element parameter required when Begin was called with element
#endif
Delay Signing
Silverlight does not support delay signing of your assemblies. This might not be a big issue for you though.
Binding to Dictionary
This is more of a subset limitation, but it’s an annoying one, that is fairly tricky to get around.
In WPF you can bind a Dictionary<string,object> object as simple as :
<TextBlock Text="{Binding Path=MyDictionary.[KeyName]}" />
However in Silverlight you have to create your own value converter:
public class DictionaryItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dict = value as Dictionary<string, string>;
if (dict != null)
{
return dict[parameter as string];
}
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Using the converter you can then bind your dictionary:
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<local:DictionaryItemConverter x:Name="myDictConvert" />
</Grid.Resources>
<TextBlock Text="{Binding Converter={StaticResource myDictConvert}, ConverterParameter=KeyName}" />
</Grid>
(note that you will have to define the local: namespace to point to the namespace/assembly where DictionaryItemConverters is placed).
Value converters are pretty powerful though, so this lesson might come in handy later. This approach does work for WPF too.
Accessing resources declared in XAML from code
Accessing resources you have in your XAML is a common pitfall. If I were to declare a resource in a grid like this:
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<local:MyClass x:Key="myResource" />
</Grid.Resources>
</Grid>
In WPF you would access it like this:
object o = LayoutRoot.Resources["myResource"];
However in Silverlight that would return null! Instead in Silverlight you have to use x:Name instead of x:Key in your XAML:
<local:MyClass x:Name="myResource" />
Unfortunately in Silverlight you can’t declare both a Key and a Name in your XAML (you can in WPF), so you will have to maintain two different XAML files. In WPF you are required to specify a x:Key attribute, so you can’t just make do with the name attribute either.
Control Templates
When you create a control template, you will have to assign the type that the control template belongs to. Here’s the syntax in Silverlight:
<ControlTemplate TargetType="local:MyControl">
And in WPF:
<ControlTemplate TargetType="{x:Type local:MyControl}">
Debug.WriteLine
This one is a little interesting, because it's a method in Silverlight that proves that it’s not necessarily a subset of WPF, since here’s a method that actually have different overloads in Silverlight and WPF. I often use System.Diagnostics.Debug.WriteLine to write out values or warnings that I or the developer should be aware of, but not necessarily is an error.
Here are the overloads in Silverlight:
public static void WriteLine(object value);
public static void WriteLine(string message);
public static void WriteLine(string format, params object[] args); //NOT IN WPF!
and WPF:
public static void WriteLine(object value);
public static void WriteLine(string message);
public static void WriteLine(object value, string category);
public static void WriteLine(string message, string category);
Notice that the 3rd method in Silverlight which is equivalent of using string.Format, doesn’t exist in WPF. Therefore always use WriteLine(string.Format(format,args)) instead.
OnApplyTemplate fired in different order
The OnApplyTemplate call on your controls are fired at different times in WPF and Silverlight. This can cause a lot of problems if you rely on certain events to have happened before the OnApplyTemplate event has triggered, or vice versa. This is a case where you have to be really careful with code-reuse in Silverlight and WPF.
In the example below I created a simple sample and ran it in Silverlight and WPF, with breakpoints in each loaded handler, constructor and OnApplyTemplate in my custom control. The XAML is below (simplified and removed namespace declarations for readability):
<UserControl Loaded="UserControl_Loaded”>
<my:Control Loaded="MyControl_Loaded />
</UserControl>
Order the methods were hit:
|
Silverlight |
WPF |
1. |
UserControl Constructor |
UserControl Constructor |
2. |
MyControl Constructor |
MyControl Constructor |
3. |
MyControl Loaded |
MyControl.OnApplyTemplate |
4. |
UserControl Loaded |
UserControl Loaded |
5. |
MyControl.OnApplyTemplate |
MyControl Loaded |
See this post for a workaround.
Case sensitivity
Silverlight in general seems less restrictive when it comes to your XAML. For instance case sensitivity. I was recently trying to use a class modifier on my UserControl using the following:
<UserControl x:ClassModifier=”Internal”>
However this doesn’t work in WPF. It turns out that the “internal” keyword must be lowercase in WPF.
--------------------------
Jeff Wilcox also has a list of issues that he hit in his gravatar project described in this blog post: http://www.jeff.wilcox.name/2009/03/gravatar-control/