Silverlight 3 – System Colours

Silverlight 3 has a new class called SystemColors and hanging off that class are a bunch of properties as below;

image

If I write a Silverlight application today and draw a button on the screen as in;

        <Button
            Content="Click Me"
            FontSize="24"
            HorizontalAlignment="Center"
            VerticalAlignment="Center" />

then that displays as below;

image

and you can see it running right next to Calculator. However…if I change my system colours in Windows to (e.g.) one of the high contrast settings;

image

then this is what I end up with;

image

that is – Calculator has played ball and so has FireFox but my Silverlight application isn’t really playing ball in that it has maintained its look and feel in spite of the system settings.

It’d be nice if there was the possibility of the Silverlight application picking up those settings if the user/developer wants it to.

Now, the SystemColors class is nice but it just has a bunch of static properties on it and nor is there any kind of change notification present when the user changes them.

So…I figure it’s not a major loss if the user has to refresh the browser (or perhaps click a Silverlight UI button) to tell the Silverlight application that they have changed their theme. It’s liveable.

With that in mind, I set about exposing some of those static properties on SystemColors as non-static properties so that they can be used from XAML.

Here’s some attempts 🙂

public class MySystemColours
  {
    public Brush ControlTextBrush
    {
      get
      {
        if (controlTextBrush == null)
        {
          controlTextBrush = new SolidColorBrush(SystemColors.ControlTextColor);
        }
        return (controlTextBrush);
      }
    }
    Brush controlTextBrush;
  }

along with some XAML using it;

<UserControl
    x:Class="SilverlightApplication36.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SilverlightApplication36">
    <UserControl.Resources>
        <local:MySystemColours x:Key="mySystemColours" />
    </UserControl.Resources>
    <Grid
        x:Name="LayoutRoot">
        <Grid.Resources>
        </Grid.Resources>
        <Button
            Foreground="{Binding Source={StaticResource mySystemColours},Path=ControlTextBrush}"
            Content="Click Me"
            FontSize="24"
            HorizontalAlignment="Center"
            VerticalAlignment="Center" />
    </Grid>
</UserControl>

now, this is kind of “ok” in that it seems to work but I don’t really want to have to explicitly set Foreground and Background and so on for every control instance. I really want a Style that can be applied to say “I am using the system style”.

So, I’d like to build a Style that makes use of the brushes like ControlTextBrush and so on in order to colour Buttons, CheckBoxes, etc. etc. etc.

That leads me to a second attempt something like this from a XAML perspective ( code is the same );

<UserControl
    x:Class="SilverlightApplication36.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SilverlightApplication36">
    <UserControl.Resources>
        <local:MySystemColours x:Key="mySystemColours" />
        <Style
            x:Key="mySystemStyle"
            TargetType="Button">
            <Setter
                Property="Foreground"
                Value="{Binding Source={StaticResource mySystemColours},Path=ControlTextBrush}" />
        </Style>
    </UserControl.Resources>
    <Grid
        x:Name="LayoutRoot">
        <Grid.Resources>
        </Grid.Resources>
        <Button
            Style="{StaticResource mySystemColours}"
            Content="Click Me"
            FontSize="24"
            HorizontalAlignment="Center"
            VerticalAlignment="Center" />
    </Grid>
</UserControl>

and that feels like it’s going to be a lot better except that it doesn’t actually work 🙂 I think the problem being that I’m trying to bind the value of a Brush and (in Silverlight) you can’t bind the values of Brushes or Colors, you can only apply bindings to things that are derived from FrameworkElement. There’s a thread on the forums about this here. If anyone knows differently then let me know as this is the solution I’d have liked to go with.

So…what to do?

Well, we can data bind Styles but we can’t seem to data-bind the setters within them that set up brushes or colors. So, maybe I can create the whole style at runtime and add the properties myself as in;

 public class MySystemStyles
  {
    public Style ButtonStyle
    {
      get
      {
        if (buttonStyle == null)
        {
          buttonStyle = MakeStyle(typeof(Button));
        }
        return (buttonStyle);
      }
    }
    static Style MakeStyle(Type t)
    {
      Style style = new Style(t);

      foreach (var item in propertiesValues)
      {
        style.Setters.Add(new Setter(item.Key, item.Value));
      }
      return (style);
    }
    static Dictionary<DependencyProperty, object> propertiesValues =
      new Dictionary<DependencyProperty, object>()
      {
        { Control.ForegroundProperty, new SolidColorBrush(SystemColors.ControlTextColor) },
        { Control.BackgroundProperty, new SolidColorBrush(SystemColors.ControlColor) }
      };

    Style buttonStyle;
  }

Now that “kind of works” except that the default template for Button doesn’t just rely on Foreground and Background. It uses a few more colours than that. Specifically, it uses a BorderBrush but it also has some colours that aren’t available as properties on Button as far as I can tell and so just changing these 2 properties isn’t going to cut-it.

I’d need an alternative version of Button that only used Foreground, Background and BorderBrush. That’s fine but then how do I go about combining the style that I’m creating in code dynamically above with the style that re-templates my Button for me to only use those 3 colours?

I figured that I might be able to use a BasedOn style but doing that from XAML as in;

   <UserControl.Resources>
        <local:MySystemStyles
            x:Key="mySystemStyles" />
        <Style
            x:Key="buttonStyle"
            BasedOn="{Binding Source={StaticResource mySystemStyles},Path=ButtonStyle}">

seems not to work for me at runtime (i.e. runtime error on the BasedOn binding).

Maybe I can flip it around. Maybe I can tell my MySystemStyles class that when it  builds a style at runtime, it should base it on an already present style. Bit messy but maybe that’s where I have to go as in;

  public class MySystemStyles
  {
    public Style BasedOnButtonStyle
    {
      get
      {
        return (basedOnButtonStyle);
      }
      set
      {
        basedOnButtonStyle = value;
      }
    }    
    public Style ButtonStyle
    {
      get
      {
        if (buttonStyle == null)
        {
          buttonStyle = MakeStyle(typeof(Button), basedOnButtonStyle, buttonPropertyValues);
        }
        return (buttonStyle);
      }
    }
    static Style MakeStyle(Type t, Style basedOnStyle, Dictionary&lt;DependencyProperty, object&gt; propertyValues)
    {
      Style style = new Style(t);

      style.BasedOn = basedOnStyle;

      foreach (var item in propertyValues)
      {
        style.Setters.Add(new Setter(item.Key, item.Value));
      }
      return (style);
    }
    static Dictionary&lt;DependencyProperty, object&gt; buttonPropertyValues =
      new Dictionary&lt;DependencyProperty, object&gt;()
      {
        { Control.ForegroundProperty, new SolidColorBrush(SystemColors.ControlTextColor) },
        { Control.BackgroundProperty, new SolidColorBrush(SystemColors.ControlColor) },
        { Button.BorderBrushProperty, new SolidColorBrush(SystemColors.ActiveBorderColor) }
      };

    Style buttonStyle;
    Style basedOnButtonStyle;
  }

and then I can perhaps use that in XAML as in;

<UserControl
    x:Class="SilverlightApplication36.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    xmlns:local="clr-namespace:SilverlightApplication36">
    <UserControl.Resources>
        <Style
            x:Key="buttonStyle" TargetType="Button">
            <Setter
                Property="Template">
                <Setter.Value>
                    <ControlTemplate
                        TargetType="Button">
                        <Grid>
                            <vsm:VisualStateManager.VisualStateGroups>
                                <vsm:VisualStateGroup
                                    x:Name="CommonStates">
                                    <vsm:VisualState
                                        x:Name="Normal" />
                                    <vsm:VisualState
                                        x:Name="MouseOver">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames
                                                Storyboard.TargetName="BackgroundAnimation"
                                                Storyboard.TargetProperty="Opacity">
                                                <SplineDoubleKeyFrame
                                                    KeyTime="0"
                                                    Value="1" />
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                    <vsm:VisualState
                                        x:Name="Pressed">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames
                                                Storyboard.TargetName="BackgroundAnimation"
                                                Storyboard.TargetProperty="Opacity">
                                                <SplineDoubleKeyFrame
                                                    KeyTime="0"
                                                    Value="1" />
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                    <vsm:VisualState
                                        x:Name="Disabled">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames
                                                Storyboard.TargetName="DisabledVisualElement"
                                                Storyboard.TargetProperty="Opacity">
                                                <SplineDoubleKeyFrame
                                                    KeyTime="0"
                                                    Value=".55" />
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                </vsm:VisualStateGroup>
                                <vsm:VisualStateGroup
                                    x:Name="FocusStates">
                                    <vsm:VisualState
                                        x:Name="Focused">
                                        <Storyboard>
                                            <DoubleAnimationUsingKeyFrames
                                                Storyboard.TargetName="FocusVisualElement"
                                                Storyboard.TargetProperty="Opacity">
                                                <SplineDoubleKeyFrame
                                                    KeyTime="0"
                                                    Value="1" />
                                            </DoubleAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                    <vsm:VisualState
                                        x:Name="Unfocused" />
                                </vsm:VisualStateGroup>
                            </vsm:VisualStateManager.VisualStateGroups>
                            <Border
                                x:Name="Background"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="3">
                                <Grid
                                    Margin="1"
                                    Background="{TemplateBinding Background}">
                                    <Border
                                        x:Name="BackgroundAnimation"
                                        Opacity="0"
                                        Background="{TemplateBinding Background}" />
                                </Grid>
                            </Border>
                            <ContentPresenter
                                x:Name="contentPresenter"
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                Margin="{TemplateBinding Padding}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                Content="{TemplateBinding Content}"
                                ContentTemplate="{TemplateBinding ContentTemplate}" />
                            <Rectangle
                                x:Name="DisabledVisualElement"
                                Fill="#FFFFFFFF"
                                RadiusX="3"
                                RadiusY="3"
                                IsHitTestVisible="false"
                                Opacity="0" />
                            <Rectangle
                                x:Name="FocusVisualElement"
                                Stroke="{TemplateBinding BorderBrush}"
                                StrokeThickness="1"
                                RadiusX="2"
                                RadiusY="2"
                                Margin="1"
                                IsHitTestVisible="false"
                                Opacity="0" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <local:MySystemStyles
            x:Key="mySystemStyles" 
            BasedOnButtonStyle="{StaticResource buttonStyle}"/>
    </UserControl.Resources>
    <Grid
        x:Name="LayoutRoot">
        <Grid.Resources>
        </Grid.Resources>
        <Button
            Style="{Binding Source={StaticResource mySystemStyles},Path=ButtonStyle}"
            Content="Click Me"
            FontSize="24"
            HorizontalAlignment="Center"
            VerticalAlignment="Center" />
    </Grid>
</UserControl>

now this does pretty much do what I originally set out to do in that if I change my colour scheme in Windows and then refresh the Silverlight application then my button does indeed pick up the modified system colours.

But that’s only (so far) for 3 properties on Button. I’d have to build a set of styles for every control (do-able) and then extend my MySystemStyles class so that it had properties such as ComboBoxStyle, TreeViewStyle, TextBoxStyle and so on and so forth and it would also have to include properties like BasedOnComboBoxStyle and BasedOnTreeViewStyle.

So…it can be made to work but there has to be a better way than this?

Anyone got ideas? 🙂