Silverlight Shared MergedDictionaries - silverlight

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.

Related

Is it possible to load an external hosted XAML file into a WPF project?

Is it possible to load an external hosted XAML file into a WPF project?
I am trying to pull in a ResourceDictionary from an external XAML file. I am doing this so I can style the application external from the application.
I am doing this because I want to see if this is possible because the application is going to be running on multiple computers and I don't want to have to reload upload or load a new XAML file everytime I need to make a simple change to a button color or text color.
Below are 2 options I have tried but I keep getting that the resource can not be an absolute URI. Any pointers on how to make my XAML file load from an external hosted source?
Try one
namespace FunWithXaml
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
EnsureApplicationResources();
InitializeComponent();
}
public static void EnsureApplicationResources()
{
if (Current == null)
{
// create the Application object
// new Application();
// merge in your application resources
Current.Resources.MergedDictionaries.Add(
LoadComponent(
new Uri("http://example.com/scripts/XAMLTest.xml", //This can be .xaml or .xml from my understanding if it does not have the x:Class declared in the file.
UriKind.RelativeOrAbsolute)) as ResourceDictionary);
}
else
{
MessageBox.Show("Welp that didn't work.");
}
}
}
Try Two. Similar to above but I try to do it with using the WebClient and XamlReader. Not sure if I am doing this part correctly though.
var Blob = new Uri("http://example.com/scripts/XAMLTest.xml");
var wc = new WebClient();
var sourceStream = wc.OpenRead(Blob);
StreamReader mysr = new StreamReader(sourceStream);
FrameworkElement rootObject = XamlReader.Load(mysr.BaseStream) as FrameworkElement;
Current.Resources.MergedDictionaries.Add(LoadComponent(rootObject)); //rootobject is giving me red lined error.
And Try Three. Similar to Try One but completely did away with the "If" and get an error stating I can't have an absolute URI.
public static void EnsureApplicationResources()
{
Current.Resources.MergedDictionaries.Add(
LoadComponent(
new Uri("http://example.com/scripts/XAMLTest.xml",
UriKind.RelativeOrAbsolute)) as ResourceDictionary);
}
EDIT
Try Four based on the suggestion below by #Leon Zhou. But I am getting an exception error:
"An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll
Additional information: ResourceDictionary LoadFrom operation failed with URI 'http://example.com/scripts/XAMLTest.xml' ".
public App()
{
AFunction();
InitializeComponent();
}
public void AFunction()
{
var foo = new Uri("http://example.com/scripts/XAMLTest.xml", UriKind.Absolute);
Current.Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = foo });
}
This what's in my XML file I am trying to pull.
<ResourceDictionary>
<Style x:Key="HeaderStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green"/>
</Style>
</ResourceDictionary>
This is my App.xaml file
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="FunWithXaml.App"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--Trying to get dynamic XML/XAML resource to populate here-->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
How about:
Application.Current.Resources.MergedDictionaries.Add(
new ResourceDictionary { Source = uri }
);
var uri = new Uri("PathToMyXamlFile");
var stream = File.OpenRead(uri.ToString());
var resources = (ResourceDictionary)XamlReader.Load(stream);
I would get the Xaml as a String into a memory stream. And then give it to the XamlReader.Load.

Custom resource dictionary inside ControlTemplate or DataTemplate

EDIT: This problem occurs when using the standard .NET ResourceDictionary as well and appears to be an issue with using resource dictionaries inside control or data templates.
I have a custom resource dictionary that follows a common approach to sharing resource instances.
http://softnotes.wordpress.com/2011/04/05/shared-resourcedictionary-for-silverlight/
http://www.wpftutorial.net/MergedDictionaryPerformance.html
public class SharedResourceDictionary : ResourceDictionary
{
static readonly Dictionary<Uri, WeakReference<ResourceDictionary>> SharedDictionaries = new Dictionary<Uri, WeakReference<ResourceDictionary>>();
Uri _sourceUri;
public new Uri Source
{
get
{
// Behave like standard resource dictionary for IDE...
if (VisualStudio.IsInDesignMode)
return base.Source;
return this._sourceUri;
}
set
{
// Behave like standard resource dictionary for IDE...
if (VisualStudio.IsInDesignMode)
{
base.Source = value;
return;
}
this._sourceUri = value;
WeakReference<ResourceDictionary> cached;
if (SharedDictionaries.TryGetValue(value, out cached))
{
ResourceDictionary rd;
if (cached.TryGetTarget(out rd))
{
this.MergedDictionaries.Add(rd);
return;
}
}
base.Source = value;
SharedDictionaries[value] = new WeakReference<ResourceDictionary>(this);
}
}
}
It works fine, but whenever it is referenced inside a Resources element within a ControlTemplate or DataTemplate, there are spurious errors shown (these do not affect the build, which still succeeds).
This one gets shown for the standard ResourceDictionary which contains SharedResourceDictionary in its merged dictionaries:
Unable to cast object of type 'Microsoft.Expression.Markup.DocumentModel.DocumentCompositeNode' to type 'System.Windows.ResourceDictionary'
Sample XAML:
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<DockPanel Style="{DynamicResource MainDockPanel}">
<DockPanel.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<p:SharedResourceDictionary Source="/MyAssembly;component/MyResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</DockPanel.Resources>
</DockPanel>
</DataTemplate>
Does anyone have any ideas how I can eliminate this nuisance error?
Thanks
This issue has been reported to Microsoft. You can vote on it, so maybe it will get fixed in some future release.
https://connect.microsoft.com/VisualStudio/feedback/details/772730/xaml-designer-broken-when-adding-resource-dictionaries-to-data-or-control-templates

Automatically apply default styles defined in separate assembly

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.

Is it possible to add wpf ResourceDictionary in the Application from a UserControl xaml file?

Is it possible to add a ResourceDictionary in the application level from a UserControl xaml.
i.e. do the same thing from UserControl xaml than the following in C#:
if (Application.Current == null) new Application();
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary() {...});
You can write an ApplicationDictionaryMerger class that accepts dictionaries as its content and adds them to the MergedDictionaries of the application, for example:
[ContentProperty("Dictionaries")]
public class ApplicationDictionaryMerger
{
private readonly ObservableCollection<ResourceDictionary> dictionaries =
new ObservableCollection<ResourceDictionary>();
public ApplicationDictionaryMerger()
{
this.dictionaries.CollectionChanged += this.DictionariesChanged;
}
private void DictionariesChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
// Do whatever you deem appropriate here to get the MergedDictionaries
var applicationDictionaries =
Application.Current.Resources.MergedDictionaries;
// Enhance this switch statement if you require more functionality
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var dict in e.NewItems)
{
applicationDictionaries.Add((ResourceDictionary)dict);
}
break;
}
}
public IList Dictionaries
{
get { return this.dictionaries; }
}
}
The only catch then is that you need to instantiate an object like the above from XAML.
Initially I thought that adding it to the Resources section of any control in your XAML would be fine, but then it turns out that the XAML loader does not instantiate resources that are unused. So I came up with another workaround: setting the object as the value of any control's Tag property.
I 'd be very interested to know if there's a better way of ensuring that the ApplicationDictionaryMerger is instantiated.
Here's how to use it:
<Grid> <!-- can also be any other control -->
<Grid.Tag>
<sandbox:ApplicationDictionaryMerger>
<ResourceDictionary>
<!-- add all resources you need here -->
</ResourceDictionary>
<!-- you can also add more dictionaries here -->
</sandbox:ApplicationDictionaryMerger>
</Grid.Tag>
</Grid>

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