Runtime theme switching with explicit styles in Si

2019-05-05 00:50发布

It's been requested that we add dynamic theme switching to our application, and I'm having problems figuring out how to do this.

Here's the current situation: our application has a merged resource dictionary with explicit (not implicit) styles. The views in our application refer to these styles through the StaticResource markup extension. The reason why we're not using implicit styles is that we have multiple looks for common types of controls; for example, buttons in one location look different than buttons in another.

So what we are wanting to do is create themes that replace these named styles with a new set of named styles.

Approach 1

The first attempt was to create resource dictionaries for each theme. I removed one of the styles from the common dictionaries and placed them in each theme dictionary, giving each copy a distinct look, like this:

<!-- RedTheme.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    mc:Ignorable="d"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
  <Style x:Key="HeaderContentStyle" TargetType="ContentControl">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="ContentControl">
          <Grid Margin="0" Background="Red">
            <ContentPresenter Content="{TemplateBinding Content}"/>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>


<!-- BlueTheme.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    mc:Ignorable="d"
                    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                    xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
  <Style x:Key="HeaderContentStyle" TargetType="ContentControl">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="ContentControl">
          <Grid Margin="0" Background="Blue">
            <ContentPresenter Content="{TemplateBinding Content}"/>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

Then, I added code to dynamically load one of the resource dictionaries from XAML and insert it into the application's merged dictionaries:

// In App.xaml.cs
var themeUri = new Uri(
    "OurApp;component/Themes/RedTheme.xaml", UriKind.Relative);
var resourceInfo = GetResourceStream(themeUri);
using (var stream = resourceInfo.Stream)
{
    using (var reader = new StreamReader(stream))
    {
        var xamlText = reader.ReadToEnd();
        var dict = XamlReader.Load(xamlText) as ResourceDictionary;
        Resources.MergedDictionaries.Add(dict);
    }
}

This worked to some extent. If I loaded the "theme" during startup, that theme's style was displayed. However, what didn't work was trying to switch to the other theme after startup. Adding the other theme theme dictionary to the merged dictionaries didn't cause the UI to be modified. Neither did clearing out the old theme dictionary and adding the new one. Neither did doing any of these followed by removing the root visual and re-adding it.

Approach 2

After that, I tried using the Silverlight Toolkit pattern of theming. This didn't work, I assume, because it is meant to switch out implicit styles rather than explicit ones. I created my Theme-derived class, set its resource dictionary URI, and added that theme to a root visual ContentControl. Then, however, when I created my main UI class, the explicit style referenced by the UI couldn't be found, so a runtime exception occurred.

So then I tried loading one of the theme resource dictionaries into the application's merged dictionaries. This allowed the main UI class to be created and placed inside of the Style object. However, the explicit styles inside of the Style's resource dictionary failed to override the styles defined inside of the application's merged dictionaries. Therefore, nothing appeared to change.

Current Approach

So now I'm considering a third approach. It will involve an attached property used something like this: Theming.Style="StyleName". Then, elsewhere in the application, an association between theme names and style name overrides will be maintained. Whenever the theme changes, the right styles for the theme will be applied.

Dilemma

I'd rather not reinvent a wheel if there's already one out there. Is there already something built into Silverlight that allows for switching themes containing explicit styles? Are there any third-party libraries that would let me do what I want?

1条回答
女痞
2楼-- · 2019-05-05 01:16

I'm no longer at the company, so I can't post the specific code we ended up using, but we did go with an attached property approach. We replaced the assignment to the Style property for objects that needed to be dynamically skinned with a Theming.Style attached property set to the same value. Then, in our resource dictionaries, we created styles prefixed with a theme name, for example, where we once had a "StandardButton" style, we would create additional styles with the names "Blue|StandardButton" and "Clean|StandardButton". Our theming engine would then be able to look up those theme-specific styles whenever we switched themes and apply them to the elements with the attached properties.

Hopefully this is a helpful approach to others, but preferably there's a library out there that already solves this problem.

查看更多
登录 后发表回答