MSDN Blog Schweiz

Aktuelle Microsoft-News, Anleitungen, Downloads, Tools und Veranstaltungen für Schweizer Entwickler.

Silverlight White-labeling: "Dynamically" downloading and applying styles from a web server

Silverlight White-labeling: "Dynamically" downloading and applying styles from a web server

  • Comments 3

There are cases where your Silverlight app will be hosted or displayed on different web sites.
Naturally it comes with the need to adapt some of the visuals to the target web site. Classics are colors, fonts and images.
On a web page you will relay on loading a different CSS sheet. By default in Silverlight you will store those resources either on the page or better on App.xaml.
Of course you would like to have them externally especially if you will have multiple apps relaying on these styles.
If a company changes the colors or logo you can centrally manage the changes for all the apps that you are providing.

How can you accomplish this in Silverlight 2?
One limitation of Silverlight is that a style can be applied only once at loading time of the controls/page. So we need to pass a parameter at startup specifying which style to load.
The trick is then to replace (merge) the current app resources with the one that are contained in the branded resource file.

Let proceed step by step.

First let's look at the App.xaml where the style are normally stored:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
             x:Class="DynStyle.App"
               xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
             >
    <Application.Resources>
        <SolidColorBrush x:Key="Brush1"
                     Color="Gray" />
 
        <Style x:Key="ButtonStyle1" TargetType="Button">
            <Setter Property="Background" 
                Value="Gray" />
        </Style>
        
        <Style x:Key="ImageStyle1" TargetType="Image">
            <Setter Property="Source" Value="http://localhost:54046/sllogo.png"></Setter>
 
        </Style>
    </Application.Resources>
</Application>

Now what we want to do is to be able to change some of these resources at startup. So we can prepare two additional loose xaml file that we will store on the web site like they where two html pages. You can copy the App.xaml, change the style and store it in your preferred location on a web server.

I have created a BlueStyle.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ext="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Extended"
                    xmlns:local="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
                    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
    
    <SolidColorBrush x:Key="Brush1"
                     Color="Blue" />
 
    <Style x:Key="ButtonStyle1" TargetType="Button">
        <Setter Property="Background" 
                Value="Blue" />
    </Style>
 
  <Style x:Key="ImageStyle1" TargetType="Image">
    <Setter Property="Source" Value="http://localhost:54046/wllogo.png"></Setter>
 
  </Style>
 
</ResourceDictionary>

And a RedStyle.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ext="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Extended"
                    xmlns:local="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
                    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
    
    <SolidColorBrush x:Key="Brush1"
                     Color="Red" />
 
    <!--<Style x:Key="ButtonStyle1" TargetType="Button">
        <Setter Property="Background" Value="Red" />
    </Style>-->
  
    <Style x:Key="ImageStyle1" TargetType="Image">
      <Setter Property="Source" Value="http://localhost:54046/xblogo.png"></Setter>
    </Style>
 
</ResourceDictionary>

To note that in the RedStyle I have commented out the style for the button because in this case I want to keep the original style stored in App.xaml

Also if you want to add a Style for an "external control" you need to declare the name space in this way:

<vsm:Style xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls" x:Key="AutoCompleteBoxStyle1"  TargetType="controls:AutoCompleteBox">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate>
         ..........................................................
         ..........................................................
         ..........................................................
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </vsm:Style>

Now is time to define an optional initialization parameter. Eventually you can parse the page url and add the parameter in there if you prefer.
On the hosting .apsx page add the initParams:

<param name="initParams" value="<%="Style=" + Request["Style"] %>" />

 

<div id="silverlightControlHost">
        <object data="data:application/x-silverlight," type="application/x-silverlight-2" width="100%" height="100%">
            <param name="source" value="ClientBin/DynStyle.xap"/>
            <param name="onerror" value="onSilverlightError" />
            <param name="background" value="white" />
            <param name="initParams" value="<%="Style=" + Request["Style"] %>" />
            <a href="http://go.microsoft.com/fwlink/?LinkID=115261" style="text-decoration: none;">
                 <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
            </a>
        </object>
        <iframe style='visibility:hidden;height:0;width:0;border:0px'></iframe>
    </div>

Let's then read the parameter in the App.xaml.cs:

 private void Application_Startup(object sender, StartupEventArgs e)
 {
           
                if (e.InitParams["Style"] != "")
                    GetStyleFromWeb(e.InitParams["Style"]);
                else
                {
                    this.RootVisual = new Page();
                }
 
}

In this case if no Style is given I use the default one contained in the app.xaml

The GetStyleFromWeb method looks like this:

 void GetStyleFromWeb(string filename)
 {
            string url = String.Format("http://localhost:54046/{0}.xaml", filename);
 
            WebRequest client = HttpWebRequest.Create(new Uri(url, UriKind.Absolute));
            client.BeginGetResponse(new AsyncCallback(ResponseCallBack), client);
 
}

What I'm doing here is to go and request the specific style resource file form the root of my web site. You could use a relative url if you want but it would be at the level of where the .xap file is stored. In normal VS template it will be in a ClientBin folder.

As always all the requests are asynchronous so we need to catch the result:

private void ResponseCallBack(IAsyncResult asynchronousResult)
{
                 
            HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;
            HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
 
 
            StreamReader sr = new StreamReader(response.GetResponseStream());
            XElement styleElement = XElement.Parse(sr.ReadToEnd());
 
 
            Deployment.Current.Dispatcher.BeginInvoke(() => UpdateAppResource(styleElement));
 
 }
 

Here I read the result with the LINQ to XML XElement and then I must go back to the thread that "owns" the app.xaml execution to change the resources.
This is done through the Deployment Dispatecher.

The UpdateAppResource method looks like this:

public void UpdateAppResource(XElement styleElement)
{
          
            if (styleElement != null)
            {
                IEnumerable<XElement> elemets = from element in styleElement.Elements() select element;
 
                //to call if you want to replace all resources
                //App.Current.Resources.Clear();
 
                foreach (XElement element in elemets)
                {
 
                    XName x = XName.Get("{http://schemas.microsoft.com/winfx/2006/xaml}Key");
                    string elementKey = element.Attribute(x).Value;
 
                    //remove this line if you already cleared all with App.Current.Resources.Clear();
                    if (App.Current.Resources.Contains(elementKey))
                        App.Current.Resources.Remove(elementKey);
                    
 
                    App.Current.Resources.Add(elementKey, XamlReader.Load(element.ToString()));
               
                }
 
 
            }
 
            this.RootVisual = new Page();
 }

A couple of notes here, as you can see in my case I'm merging the current resources with the downloaded resources, but if you prefer to completely replace the resources simply follow the comments in the previous code snipped. Second the resources must be there before you load any control that bind them, so make sure that this line this.RootVisual = new Page(); is after you inject the new resources.

Now if you start the project with no parameters you will get this:

http://localhost:54046/DynStyleTestPage.aspx

image

Adding the parameter to the URL you will get:

http://localhost:54046/DynStyleTestPage.aspx?Style=BlueStyle

image

And with the RedStyle:

http://localhost:54046/DynStyleTestPage.aspx?Style=RedStyle

image

To note that the button is still gray demonstrating that the merging works.

 

The advantage of this approach is that you can still use Blend for the design view that the App.xaml still contain the default style.

For an alternative method please look in to the ImplicitStyleManager that we ship in the control toolkit: www.codeplex.com/silverlight

Attached the full sample

Have fun

Ciao

Ronnie Saurenmann

Attachment: DynStyle.zip
  • In this issue: John Stockton, Adam Kinney, Koen Zwikstra, Matthias Shapiro, Swiss MSDN Team Blog, Jordan

  • Hi Ronnie Saurenmann,

    Very nice blog, thank you very much.

    I am getting an error, when i try to load the element in  

    App.Current.Resources.Add(elementKey, XamlReader.Load(element.ToString())

    The error is "Style does not support text content".

    Please help me asap. Thanks.

    regards,

    Dangerous Dave.

  • Hi Ronnie Saurenmann,

    Very nice blog, thank you very much.

    I am getting an error, when i try to load the element in  

    App.Current.Resources.Add(elementKey, XamlReader.Load(element.ToString())

    The error is "Style does not support text content".

    Please help me asap. Thanks.

    regards,

    Dangerous Dave.

Page 1 of 1 (3 items)
Leave a Comment
  • Please add 4 and 1 and type the answer here:
  • Post