Monday, October 05, 2009
Dynamically Loading Resource Dictionaries in Silverlight 3
At this fall’s BASTA conference in Mainz, I presented a session on “Reusable
Silverlight Components”. One of the things I showed in that session was how to
create Silverlight components that can be hosted in different sites and also be
completely re-styled and rebranded by means of dynamically loaded Resource
Dictionaries.
Silverlight v3 is the first version of Silverlight that supports resource
dictionaries. This makes it much easier to maintain resources generically in
separate XAML files, and even switch between different sets of resources. One of
the possibilities that often goes overlooked however, is that resource
dictionaries can be loaded completely dynamically from any URL. I often use this
in scenarios where I pass parameters to a Silverlight control, where one of the
parameters is the URL of such a resource dictionary. I then load that dictionary
dynamically, so everything in that application references that dictionary. The
basic idea is the dynamic load process from a URL. This can be done like so:
WebClient request =
new WebClient();
request.DownloadStringCompleted +=
new DownloadStringCompletedEventHandler(request_DownloadStringCompleted);
request.DownloadStringAsync(
new Uri("http://domain.com/mydictionary.xaml", UriKind.Absolute));
This triggers an asynchronous string download from the specified URL. The
associated event handler fires when the download is complete and assigns the
loaded resource dictionary:
void
request_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
string
resourceXaml = e.Result;
ResourceDictionary
dictionary =
Application.Current.Resources as ResourceDictionary;
dictionary.MergedDictionaries.Add(
(ResourceDictionary)XamlReader.Load(resourceXaml));
}
This accesses the current application resources (which should be a resource
dictionary, although some error handling may be appropriate here) and then uses
a XamlReader to load the retrieved XAML string, casts it to a resource
dictionary (which it may not be, so more error handling is in order here) and
then simply adds it to the collection of available resource dictionaries.
There are a few more things of interest here that are worth pointing out:
First of all, Silverlight 3 still doesn’t support dynamic resources. Static
resources get assigned as soon as an interface loads and can’t be changed later.
This means that the new resource dictionaries should be added before any real UI
loading is done. I generally like to allow for a “resourcedictionaries”
parameter passed to the Silverlight control, but I make the parameter optional.
For this reason, I generally have this kind of code in my Startup event handler
in App.xaml.cs:
private void
Application_Startup(object sender, StartupEventArgs e)
{
if (e.InitParams.ContainsKey("resourcedictionary"))
{
var content = new Grid();
this.RootVisual = content;
content.Children.Add(new LoadingAnimation());
WebClient request = new WebClient();
request.DownloadStringCompleted
+=
new DownloadStringCompletedEventHandler(request_DownloadStringCompleted);
request.DownloadStringAsync(
new Uri(e.InitParams["resourcedictionary"], UriKind.Absolute));
}
else
{
var root = new Page1();
}
}
This code checks for the parameter. If it isn’t present, the root UI
(Page1.xaml in this case) is loaded right away. Otherwise, I create a Grid() as
a root container (the RootVisual setting can only be assigned once, so I am
using the Grid object as a container which I can then use to load other UI into)
and I then load a temporary loading screen while resource dictionaries are
downloaded (you never know how long that might take). Then, when the dictionary
is downloaded, I merge it into the resources and then I load the real UI into
the Grid:
void
request_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
string resourceXaml = e.Result;
ResourceDictionary dictionary =
Application.Current.Resources as
ResourceDictionary;
dictionary.MergedDictionaries.Add(
(ResourceDictionary)XamlReader.Load(resourceXaml));
var grid = this.VisualRoot as
Grid;
grid.Children.Clear();
grid.Children.Add(new Page1());
}
Using this approach, the main UI (Page1) gets loaded after the custom
dictionaries are downloaded and thus all static resources pick up the new
styles.
Note: If you use the Implicit Style Manager, some of this is not as
critical, since the ISM manually applies styles whenever it is invoked. However,
since you are likely to still use named styles (which are always static), you
probably still have the same problem.
Another interesting thing to note here is that I am simply adding the new
dictionaries to the collection of merged dictionaries. The way Silverlight looks
up resources, the resources added last are found first. So if my application
already has a dictionary with a style called “StandardButtonStyle” and a
dictionary that is added later also has a style of the same name, the one loaded
last is found and used. This means that dynamically loaded resource dictionaries
can define a few new styles as needed. Since the standard resource dictionaries
remain in place, Silverlight will find all the default styles there, but the new
dictionaries can override only specific ones. If you completely replace all of
the application’s resources, then the newly loaded dictionaries would have to
define every single resource or else the control would show an error and
probably fail to load. So adding resource dictionaries in addition is generally
a nifty technique that works very well in the real world.
Posted @ 12:14 PM by Egger, Markus (markus@code-magazine.com) -
Comments (6)