Automatically apply default styles defined in separate assembly - wpf

I have the following requirements:
My WPF application consists of several modules (assemblies), some of them UI-related.
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.
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.).
I do not want to write my own, extended Button class, or something like that.
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.

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.

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.

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.

Related

How can I access a resource from a ValueConverter?

I have a UserControl in a custom DLL assembly where I've defined two static BitmapImage resources that represent the state of data in our ItemsControl. I want to use a converter to set the Source property of an Image to one of the BitmapImage resources depending on some condition. However, I'm not sure how to access the resources from inside the Convert method since I don't have an instance of the control that I'm using the converter on.
I've tried loading the resources into static variables in a static constructor for the converter, which is also in the same DLL, but I haven't been successful.
This fails...
public class MyConverter : IValueConverter
{
static BitmapImage myFirstResource;
static MyConverter()
{
// This can't seem to find the resource...
myFirstResource = (BitmapImage)Application.Current.FindResource("MyResourceKey");
}
}
...but in the XAML, this succeeds, so I know the resource key is valid.
<Image Source="{StaticResource MyResourceKey}" />
I don't know if this makes any difference, but this is in a DLL, not in the EXE. Still, I thought all resources were flattened down to the application depending on where you were executing from.
Found perfect solution here Accessing a resource via codebehind in WPF
(better than using Application.Current)
#itsho
You can simply add x:Class to it:
<ResourceDictionary x:Class="Namespace.NewClassName"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<ds:MyCollection x:Key="myKey" x:Name="myName" />
</ResourceDictionary>
And then use it in code behind:
var res = new Namespace.NewClassName();
var col = res["myKey"];
Then a little fix should be applied:
#Stephen Ross
But to be able to find resources using it's key I had to call res.InitializeComponent() before attempting to access the key otherwise the object would show no keys and the call to res["myKey"] would return null

How to create a StaticResource for a class without public constructors

WPF bindings uses CultureInfo.CurrentUICulture rather than CultureInfo.CurrentCulture which means they do not respect the preferences specified in Control Panel's Region and Language dialog.
To properly implement localisation in a WPF application it is therefore necessary to somehow assign CurrentCulture to the ConverterCulture of every binding.
This is best done with a StaticResource declared in App.xaml but there is a problem: the CultureInfo class has no public constructors. As a result, markup like this
<Application x:Class="ScriptedRoutePlayback.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"
StartupUri="MainWindow.xaml">
<Application.Resources>
<glob:CultureInfo x:Key="CurrentCulture" />
</Application.Resources>
</Application>
generates a warning about the fact that CultureInfo has no public constructors. Despite this, the markup is sufficient to register an appropriately typed static resource in the namespace used by the designer, which stops markup references to {StaticResource CurrentCulture} from complaining in the rest of the app.
At run-time the failure of this markup to create an instance of CultureInfo is irrelevant because the accompanying Startup code assigns it from CultureInfo.CurrentCulture:
using System.Globalization;
using System.Windows;
namespace ScriptedRoutePlayback
{
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Resources["CurrentCulture"] = CultureInfo.CurrentCulture;
}
}
}
Finally, the question:
What is the preferred way to mark up a StaticResource that refers to an existing object such as a singleton obtained from a static property of a class, especially when said class lacks public constructors?
instead of defining a StaticResource, have you tried the x:Static markup extension when referring to CultureInfo.CurrentCulture in your bindings?
http://msdn.microsoft.com/en-us/library/ms742135.aspx
something like:
... {Binding ... ConverterCulture={x:Static glob:CultureInfo.CurrentCulture}}
alternatively, this answer or this answer may offer better alternatives for your situation.
May be there is no point to define resource in XAML.
base.OnStartup(e);
Resources.Add("CurrentCulture", CultureInfo.CurrentCulture);

Silverlight Shared MergedDictionaries

I am using Silverlight 4 and trying to share some common styles (colors, brushes).
My take was to put them into a "Common.xaml" Resource Dictionary and then use it in all other Resource Dictionaries.
Referencing everything like so:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SampleApp.App"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Assets/Styles/Common.xaml"/>
<ResourceDictionary Source="Assets/Styles/TextBoxStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
The problem is, that I get an exception on InitializeComponent stating that the common styles cannot be found (Cannot find a Resource with the Name/Key....)
I have to explicitly Reference the "Common.xaml" in every Resource Dictionary where I use it.... And this basically result in multiple Instances of every color, brush, template and whatnot that resides in "Common.xaml".
Isn't there any way to share Resources so the only get instanziated once in Silverlight?
The problem is that silverlight appears to streamline loading of resource dictionaries such that multiple dictionaries can be loading in parallel. As a result when one dictionary has a dependency on another that dependency may not be ready in time.
Since ResourceDictionary doesn't have builtin means to describe inter-dependencies nor an event to indicate when it has loaded the only solution I've been able to come to is to manage the loading of the dictionaries myself.
Here is a function you can add to your App.xaml.cs file to "manually" load a resource dictionary:-
private void LoadResource(Uri uri)
{
var info = Application.GetResourceStream(uri);
string xaml;
using (var reader = new StreamReader(info.Stream))
{
xaml = reader.ReadToEnd();
}
ResourceDictionary result = XamlReader.Load(xaml) as ResourceDictionary;
if (result != null)
{
Resources.MergedDictionaries.Add(result);
}
}
Now in the Application_Startup before assigning RootVisual you would use code like:-
LoadResource(new Uri"Assets/Styles/Common.xaml", UriKind.Relative));
LoadResource(new Uri("Assets/Styles/TextBoxStyle.xaml", UriKind.Relative));
It isn't going to be as efficient as using the Source property but it will work. If you have many such dictionaries and only few "common" dictionaries that contain shared resources then you could use this technique to load only the "common" dictionaries then use:-
Resource.MergedDictionaries.Add(new ResourceDictionary() {Source = new Uri("Assets/Styles/TextBoxStyle.xaml", UriKind.Relative)});
For the other dictionaries that don't have interdependencies on each other.
I was able to tweak the solution proposed at http://www.wpftutorial.net/MergedDictionaryPerformance.html
to make it work with Silverlight and the VS designer (haven't tried Blend). I have a blog post on it here (http://softnotes.wordpress.com/2011/04/05/shared-resourcedictionary-for-silverlight/)
public class SharedResourceDictionary : ResourceDictionary
{
public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
new Dictionary<Uri, ResourceDictionary>();
private Uri _sourceUri;
public new Uri Source
{
get { return _sourceUri; }
set
{
_sourceUri = value;
if (!_sharedDictionaries.ContainsKey(value))
{
Application.LoadComponent(this, value);
_sharedDictionaries.Add(value, this);
}
else
{
CopyInto(this, _sharedDictionaries[value]);
}
}
}
private static void CopyInto(ResourceDictionary copy, ResourceDictionary original)
{
foreach (var dictionary in original.MergedDictionaries)
{
var mergedCopy = new ResourceDictionary();
CopyInto(mergedCopy, dictionary);
copy.MergedDictionaries.Add(mergedCopy);
}
foreach (DictionaryEntry pair in original)
{
copy.Add(pair.Key, pair.Value);
}
}
}
XAML usage:
<ResourceDictionary.MergedDictionaries>
<ui:SharedResourceDictionary Source="/my_assembly_name;component/Resources/Shared.xaml"/>
</ResourceDictionary.MergedDictionaries>
If you get an error loading, ensure the Build Action is set to one of the following:
//In the dll, which is in the xap, marked as Build Action: Resource or Page
LoadResource(new Uri("SilverlightApplication48;component/GlobalAssets.xaml", UriKind.Relative));
//In the xap at the same level as the dll, (not in the dll) marked as Build Action: Content.
LoadResource(new Uri("Dictionary1.xaml", UriKind.Relative));
//In a separate library, marked as Build Action: Resource or Page.
LoadResource(new Uri("StylesLibrary;component/Dictionary2.xaml", UriKind.Relative));
Greg
Another interesting note on this thread is that SL only keeps ONE copy of a style if it is found in two different dictionaries. The last one wins. In other words, if you have two different styles both with the same key, the first one is discarded when the second one loads.

Changing ResourceDictionary doesn't visably affect Window in WPF

I have a simple WPF application I'm using for experimenting.
I have two themes defined in seperate xaml files, changing the xaml to point to them worked fine. By the way, in the xaml I'm using a straight ResourceDictionary element, not a ResourceDictionary.MergedDictionaries one.
I want to let the user select which theme to use, so I'm reseting the source property in code behind - but whilst the debugger tells me I've successfully set the value the applications appearance doesn't change.
So, how do you successfully apply a theme at runtime?
EDIT: This is how I'm declaring my "style" in the xaml:
<Window x:Class="WpfUI.winMain">
<Window.Resources>
<ResourceDictionary Source="Themes\Blah.xaml"></ResourceDictionary>
</Window.Resources>
// The windows grid and other controls...
</Window>
The simple answer is, you need to clear the applications merged resource dictionaries. Here is some code to get you started
ResourceDictionary dictionary = GetThemeResourceDictionary(yourTheme)
if (dictionary != null)
{
App.Current.Resources.MergedDictionaries.Clear();
App.Current.Resources.MergedDictionaries.Add(dictionary);
}
public ResourceDictionary GetThemeResourceDictionary(string theme)
{
if (theme != null)
{
Assembly assembly = Assembly.LoadFrom("WPF.Themes.dll");
string packUri = String.Format(YourThemeFolder/{0}.xaml", theme);
return Application.LoadComponent(new Uri(packUri, UriKind.Relative)) as ResourceDictionary;
}
return null;
}
If you want a really nice packaged solution, i would reccommend WPF themes. It introduces a ThemeManager class and a set of built in themes which are really incredible. If you have any difficulty installing it or adding a new theme, contact me :)

How to get at ResourceDictionary style when it is loaded from external xap and assemblies are MEF-fed?

I've got the following setup:
The main application loads a XAP with an IPlugin implementation. The Plugin contains a 'DisplayPanel' that contains a referenced Control with other controls. The DisplayPanel here is simply a container control to show referenced Control.
This referenced Control, from an assembly, uses a Style from a ResourceDictionary xaml in this assembly. At least that's what I want to have. The problem is that the referenced Control throws an error:
Cannot find a Resource with the Name/Key PlayerPanelGrad [Line: 1500
Position: 127]
I've tried to get at the style by referencing theResourceDictionary through a Merged Resource dictionary reference:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="TableControls;component/ControlsStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
But that doesn't work.
How would you approch this?
the only way i got it to work is by loading the Resource dictionary into the control (in a Class Library) programmatically before the InitializeComponent call:
public ActionPanel()
{
StreamResourceInfo sr = Application.GetResourceStream(
new Uri("TableControls;component/ControlsStyle.xaml", UriKind.Relative));
Application.Current.Resources.Add("plop",sr.Stream);
// Required to initialize variables
InitializeComponent();
}
This question may be of help, although, honestly, I'm still trying to figure it out myself:
Using MEF to import a WPF DataTemplate?
//load other.dll dynamically first,and then use the following code:
StreamResourceInfo srf = Application.GetResourceStream(new Uri("otherdll;component/Resources/Brush.xaml", UriKind.Relative));
StreamReader sr = new StreamReader(srf.Stream);
string stt = sr.ReadToEnd();
ResourceDictionary dict = XamlReader.Load(stt) as ResourceDictionary;
Application.Current.Resources.MergedDictionaries.Add(dict);
For future reference, my XAML file was found in a subdirectory of the solution which needed the / character but also file was further in a subdirectory named Assets within it.
<ResourceDictionary
Source="/MyAssemblyName;component/Assets/RadResources.xaml" />
Also the .XAML file was built as Page in the solution.

Resources