49
votes

I have a C# (2008/.NET 3.5) class library assembly that supports WPF (based on this article).
I've created several windows, and am now attempting to create a common style set for them. However, as it's a class library (instead of a WPF app), I don't have an app.xaml (and its contained Application & corresponding Application.Resources) in which to store these styles for global access.

So: How can I create a top-level set of style definitions that'll be seen by all xaml files in the assembly, given that I do not have app.xaml (see above)? And/or is it possible to add a working app.xaml to a class library?

FYI, I did try creating a ResourceDictionary in a ResourceDictionary.xaml file, and include it in each window within a "Window.Resources" block. That turned out to solve the styling of Buttons, etc... but not for the enclosing Window. I can put Style="{StaticResource MyWindowStyle}" in the Window's opening block, and it compiles and shows up in the VS Design window fine, but during actual runtime I get a parse exception (MyWindowStyle could not be found; I'm guessing Visual Studio sees the dictionary included after the line in question, but the CRL does things more sequentially and therefore hasn't loaded the ResourceDictionary yet).


Thanks for the ideas, but still no go... apparently a class library does NOT support the generic.xaml usage implicitly. I added generic.xaml to my class library project and set its Build Action to "Resource". It contains:

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="{x:Type Window}" x:Key="MyWindow">
        <Setter Property="Background" Value="Black"/>
    </Style>
</ResourceDictionary>

The window xaml that I want to have use the theme looks like this:

<Window x:Class="MyAssembly.ConfigureGenericButtons"
    x:ClassModifier="internal"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Style="{StaticResource MyWindow}"
    Title="ConfigureGenericButtons">
...Buttons, etc...
</Window>

Although the VS Design window doesn't show the window using the MyWindow style (ie black background), it compiles fine and starts up. However, when the app containing this class library makes a call that causes this window to display, I get a XamlParseException:

Cannot find resource named '{MyWindow}'.

I also tried leaving out the Style parameter, to see if the window would use the style by default (and I tried that both with the x:Key in generic.xaml included and without). No errors, but anything defined in the generic.xaml didn't show up either.

Am I doing something wrong here, or any other ideas on how one might allow for common custom styles to be used on a Window (ie, not have to define the styles in each Window's xaml) -- with the caveat that this is NOT an application?

7

7 Answers

15
votes

Try adding

Style={DynamicResource MyStyle}

You cannot use a StaticResource in this case.

14
votes

This sounds like a job for theming.

  1. Add a /themes/generic.xaml ResourceDictionary to your project.
  2. Add the following to AssemblyInfo.cs: [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
  3. ?
  4. Profit!

Any resources you add to generic will be used by all controls. Also you can make profile specific themes (Luna, Aero etc.) by including a ResourceDictionary file with the correct theme name in the themes directory.

Heres a link to more info: Create and apply custom themes

3
votes

If you dont have an app.xaml, you can still load it into the appplication-level resources, but you have to write code (not xaml) to do it, similar to this...

void LoadIt()
{
     ResourceDictionary MyResourceDictionary = new ResourceDictionary();
     MyResourceDictionary.Source = new Uri("MyResources.xaml", UriKind.Relative);
     App.Current.Resources.MergedDictionaries.Add(  MyResourceDictionary )
}

check out this site for an example: http://ascendedguard.com/2007/08/one-of-nice-features-about-wpf-is-how.html

1
votes

Dr. WPF (or the person formerly known as Dr. WPF) has a great post on the subject.

Here's an excerpt from the post where they create the Application object and add resources:

if (Application.Current == null)
{
    // create the Application object
    new Application();

    // merge in your application resources
    Application.Current.Resources.MergedDictionaries.Add(
        Application.LoadComponent(
            new Uri("MyLibrary;component/Resources/MyResourceDictionary.xaml",
            UriKind.Relative)) as ResourceDictionary);
}

Since my assembly is hosted via interop I had to add setting the ShutdownMode as follows and shutdown when finished:

new Application() { ShutdownMode = ShutdownMode.OnExplicitShutdown };

It worked like a charm.

0
votes

if you load it in Window.Resource, the dictionary is only available for the children of that window. You need to have it available for the window and its children.

Try loading it in your app.xaml file. That should make it an application level resource, not a window-level resource.

0
votes

So after spending a lot of time I finally figured this out. Here's how:

  1. In WPF controls library, add a new folder named themes.
  2. Inside the themes folder, add a resource dictionary named generic.xaml.
  3. Inside generic.xaml, add your resource using the following syntax:

    <SolidColorBrush x:Key="{ComponentResourceKey {x:Type local:UserControl1}, MyEllipseBrush}" Color="Blue" />
    
  4. In your control, use the following syntax to access this resource:

    Background="{StaticResource {ComponentResourceKey {x:Type local:UserControl1}, MyEllipseBrush}}"
    

Things to note:

  1. Step 1 and 2 are normally automatically done for you by Visual Studio when you create a new WPF Controls Library.
  2. I do not fully understand the purpose of ComponentResourceKey's first parameter, but it is required. Use the name of the control that will consume this resource.
  3. Visual Studio's designer was not be able to locate the resource in my case. It could be a cache issue, I'm not sure. At runtime however it works neatly.
  4. You can read more details about this syntax in this MSDN article.

Hope this makes some lives easier.

0
votes

Here is a simple solution for sharing resources "module-wide" across a .NET class library. Importantly, it seems to robustly preserve the XAML Designer display capabilities and behaviors in Visual Studio.

Start by adding a new C# class derived from ResourceDictionary as follows. An instance of this class will replace the default ResourceDictionary on each System.Windows.Control (or other ResourceDictionary-bearing component) in the class library that needs to see the shared resources:

ComponentResources.cs:

using System;
using System.Reflection;
using System.Windows;
using System.Windows.Markup;

namespace MyNamespace
{
    [UsableDuringInitialization(true), Ambient, DefaultMember("Item")]
    public class ComponentResources : ResourceDictionary
    {
        static ResourceDictionary _inst;

        public ComponentResources()
        {
            if (_inst == null)
            {
                var uri = new Uri("/my-class-lib;component/resources.xaml", UriKind.Relative);
                _inst = (ResourceDictionary)Application.LoadComponent(uri);
            }
            base.MergedDictionaries.Add(_inst);
        }
    };
}

Be sure to replace MyNamespace and my-class-lib in the previous code snippet with (respectively) the namespace and assembly filename (without the '.dll' extension) from your own project:

Add a new ResourceDictionary XAML file to your class library project. Unlike as for 'Application' assemblies, there is no option for this in the Visual Studio UI, so you'll have to do it manually. This will contain the resources you want to share across the whole class library:

$(ProjectDirectory)\Resources.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyNamespace">

    <!-- define the resources to be shared across the whole class library here -->

    <!-- example for demonstration -->
    <Style TargetType="Rectangle">
        <Setter Property="Fill" Value="Red" />
    </Style>

<ResourceDictionary>

Set the Build Action to "Page" and be sure the File Properties information looks like this:

enter image description here

Finally, go to the XAML files in the project that need to reference the shared resources, and replace the default ResouceDictionary (typically, on the Resources property of the XAML root element) with a ComponentResources instance. This will hold each component's private resources, and as you can see in the code above, the constructor attaches the module-wide shared singleton ResourceDictionary as a "merged dictionary." Do this for every control that should see the shared resources, even those that have no private resources of their own. Of course, you can also skip this step to exclude certain controls from sharing as needed. For example:

<UserControl x:Class="MyNamespace.UserControl1"
             xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MyNamespace">

    <UserControl.Resources>
        <local:ComponentResources>
            <!-- Keep any existing non-shared resources here from before -->
            <!-- Can be empty if this control has no private resources -->
        </local:ComponentResources>
    </UserControl.Resources>

    <!-- to demonstrate that the Style in the above example is effective... -->
    <Grid>
        <Rectangle Width="10" Height="10" />
    </Grid>

</UserControl>

As advertised, it works great, including in the XAML Designer...

enter image description here