tlind
tlind

Reputation: 251

Automatically apply default styles defined in separate assembly

I have the following requirements:

  1. My WPF application consists of several modules (assemblies), some of them UI-related.

  2. I want to create a single assembly containing a common set of styles for certain controls (e.g. a custom default button style) that should be applied automatically in all other UI-related assemblies, just by including that one assembly, and without me having to specify explicit resource keys.

  3. I do not provide styles for every kind of control, so those without a custom style should keep the default Aero theme (including content templates etc.).

  4. I do not want to write my own, extended Button class, or something like that.

  5. I want this to work in Visual Studio at design-time as well, both in the final app and within the other UI-related modules.

As the styles are defined inside an assembly, I obviously cannot have an App.xaml there. I therefore assume that I have to include them from Generic.xaml. As Generic.xaml only serves as a fallback when there is no style defined in the standard (Aero) theme, WPF ignores my styles in Generic.xaml.

The next step would probably be to create my very own theme (that somehow merges the default Aero styles). But how do I tell VS to use that theme in both the app and the modules, instead of e.g. Aero? I guess I have to do this declaratively as I need design-time support for my custom styles.

Upvotes: 2

Views: 3446

Answers (3)

GetFuzzy
GetFuzzy

Reputation: 2158

I had been struggling with these same issues for a PRISM application I'd been working on. After doing some stack over flow research I was surprised to find that the simplest answer to having the resources work at design time was to add an App.Xaml to each of my UI based modules. When the application is complied, they will all be ignored. But at design time they will be used by the designer. Following the rest of the advice above you'd have an App.Xaml that has a merged resource dictionary pointing back to a resource library that has all your styles.

This is the simplest way I've found to get at styles during design time.

<Application x:Class="ProjHydraulics.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Application.Resources>

    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary  Source= "pack://application:,,,/Infrastructure;component/ResourceDictionaries/ResourceLibrary.xaml"/>
        </ResourceDictionary.MergedDictionaries>
        <Style TargetType="{x:Type Rectangle}"/>
    </ResourceDictionary>

</Application.Resources>

Building the knowledge of those before me, I put together a blog post detailing my experience with resource dictionaries in PRISM.

Upvotes: 0

Mike Strobel
Mike Strobel

Reputation: 25623

Simply adding a reference to the style assembly will be insufficient; you'll have to do something to make WPF merge the resources in. But we can do this in such a way that you'll only need to add a single line of C# (or a few lines of XAML) to your application assembly.

The most straightforward solution is probably to create a strongly-typed ResourceDictionary in your shared styles assembly, and add it into your app-level ResourceDictionary at start-up.

For example, create a CustomStyles.xaml in your shared styles assembly, and pull all of your style resources into that file (either directly or via MergedDictionaries). Make sure the Build Action is set to "Page", and add an x:Class directive to the ResourceDictionary element like so:

<ResourceDictionary x:Class="YourNamespace.CustomStyles"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <!-- Your styles declared or imported here -->
</ResourceDictionary>

For styles meant to replace built-in or third-party control styles, you can declare the styles as implicit, i.e., leave the x:Key off entirely, or use the control's type as the key, e.g., x:Key="{x:Type ComboBox}".

Adding the x:Class directive probably won't be enough to make Visual Studio generate a CustomStyles() constructor that actually loads the XAML content, so you'll probably need to add a CustomStyles.xaml.cs file manually and give it a constructor that calls InitializeComponent() (VS should still generate this):

namespace YourNamespace
{
    partial class CustomStyles
    {
        public CustomStyles()
        {
            InitializeComponent();
        }
    }
}

In your application, you need to get this dictionary merged into your Application.Resources dictionary. You can do this from the App.xaml file if you like:

<Application x:Class="YourNamespace.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cs="clr-namespace:YourNamespace;assembly=YourCustomStylesAssembly">
  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <cs:CustomStyles />
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Application.Resources>
</Application>

...or you can do it on the C# side:

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        this.Resources.MergedDictionaries.Add(new CustomStyles());
    }
}

Now, the tricky part is going to be getting these styles to work in the XAML Designer. One solution that comes to mind is to add a custom attached property that you can set on all your views, and which is only applied if you're running in the designer:

partial class CustomStyles
{
    public static readonly DependencyProperty EnableDesignTimeStylesProperty =
        DependencyProperty.RegisterAttached(
            "EnableDesignTimeStyles",
            typeof(bool),
            typeof(CustomStyles),
            new PropertyMetadata(
                default(bool),
                OnEnableDesignTimeStylesChanged));

    private static CustomStyles DesignTimeResources;

    private static void OnEnableDesignTimeStylesChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        if (!DesignerProperties.GetIsInDesignMode(d))
            return;

        var element = d as FrameworkElement;
        if (element == null)
            return;

        if (DesignTimeResources == null)
            DesignTimeResources = new CustomStyles();

        if ((bool)e.NewValue)
            element.Resources.MergedDictionaries.Add(DesignTimeResources);
        else
            element.Resources.MergedDictionaries.Remove(DesignTimeResources);
    }

    public static void SetEnableDesignTimeStyles(
        DependencyObject element,
        bool value)
    {
        element.SetValue(EnableDesignTimeStylesProperty, value);
    }

    public static bool GetEnableDesignTimeStyles(DependencyObject element)
    {
        return (bool)element.GetValue(EnableDesignTimeStylesProperty);
    }
}

Then, on your views, just set CustomStyles.EnableDesignTimeStyles="True" to force the designer to merge in the style resources. At runtime, DesignerProperties.GetIsInDesignMode(d) will evaluate to false, and you won't end up loading a new copy of your styles in every view; you'll just inherit them from the app-level resources.

Upvotes: 5

Brannon
Brannon

Reputation: 5414

I don't know a way to apply them all automatically. In fact I think the combo "automatic, designer-supported, and multiple assemblies" is impossible. However, it is easy enough to add a header reference to each of your controls:

Step 1: merge or add all your styles to a dictionary in a "styles" project referenced by all your other projects.

Step 2: include a reference to this dictionary in each of your control and other resource dictionary XAML files. It will look smoething like this:

<ResourceDictionary.MergedDictionaries>
   <SharedResourceDictionary Source="pack://application:,,,/My.Ui.Resources;component/Themes/ColorSkins/LightTheme.xaml" />
...

Note the use of SharedResourceDictionary to not duplicate instances. See http://blogs.msdn.com/b/wpfsdk/archive/2007/06/08/defining-and-using-shared-resources-in-a-custom-control-library.aspx

and SharedResourceDictionary: edit resource in Blend

If all of your controls inherit from the same base, it may be useful to make your own base class that includes them programmatically.

Upvotes: 0

Related Questions