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
Related
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.
I made a post about this a while back and got no solutions that worked.
No one said it wasn't possible but some people suggested that the error was coming from something else.
So I created a new solution, simple, just added a toolkit control which calls the DLL via the XAML (namespace import) - which I think is the problem.
I added some code to load the DLL's which I have embedded as resources.
my project can be downloaded from here:
https://onedrive.live.com/redir?resid=B293BA834310C42A%21108
for those who don't have time, here's the code:
MainWindow.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" x:Class="WpfAppTest1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<chartingToolkit:ScatterSeries HorizontalAlignment="Left" Margin="63,85,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace WpfAppTest1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
and my App.xaml.cs
namespace WpfAppTest1
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var execAssembly = Assembly.GetExecutingAssembly();
string resourceName = execAssembly.FullName.Split(',').First() + ".Resources." + new AssemblyName(args.Name).Name + ".dll";
Console.WriteLine(resourceName);
using (var stream = execAssembly.GetManifestResourceStream(resourceName))
{
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
}
}
}
The 2 DLL's are:
system.windows.controls.datavisualization.toolkit.dll
WPFToolkit.dll
Both are added as Resources and build action is set to Embedded Resource.
The 2 references are copy local to False.
Looking at the EXE, both files are well built into it.
I added a console writeline to print out the path of the DLL being loaded...
But I still get an error. If I do set the copy local to true, it works.
I am really stuck there and have to use this toolkit and I need the DLL to be part of the EXE. If it's not possible then I'd like to read it from one of you pro's :)
thanks
Steve
edit: Is there no solution to achieve what I need? :(
edit: I still haven't found a solution as of today :/
edit: I wonder if this is even possible.
After quite a bit of effort I seem to have this working.
As your resource for system.windows.controls.datavisualization.toolkit is all in lower case, ensure all of the XAML references to it are also, e.g.:
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=system.windows.controls.datavisualization.toolkit"
This ensures Assembly.GetManifestResourceStream is able to find the resource without any casing issues.
Then change your assembly resolve method to be the following:
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var requestedAssemblyName = new AssemblyName(args.Name).Name;
const string resourcesExtension = ".resources";
if (requestedAssemblyName.EndsWith(resourcesExtension, StringComparison.Ordinal))
{
requestedAssemblyName = requestedAssemblyName.Remove(requestedAssemblyName.Length - resourcesExtension.Length);
}
var resourceName = "WpfAppTest1.Resources." + requestedAssemblyName + ".dll";
var appAssembly = typeof(App).Assembly;
using (var stream = appAssembly.GetManifestResourceStream(resourceName))
{
// Check the resource was found
if (stream == null)
return null;
var assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
var loadedAssembly = Assembly.Load(assemblyData);
return loadedAssembly;
}
}
The key point to note is the removal of the .resources extension which sometimes comes through when resolving assemblies
This is enough to get the assembly loading from the resources, however there will be no implicit styling applied to the charting controls (I am assuming this is to do with how WPF discovers its themes at runtime).
To work around this problem, I put the following in App.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/system.windows.controls.datavisualization.toolkit;component/Themes/generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
This merges the default chart styles which should then get implicitly applied.
To test it, I used the following content in MainWindow.xaml:
<chartingToolkit:Chart Title="Some Chart" Background="Blue">
<chartingToolkit:Chart.Series>
<chartingToolkit:ScatterSeries Title="Series 1" />
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
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
if you worked on some larger wpf applications you might be familiar with this. Because ResourceDictionaries are always instantiated, everytime they are found in an XAML we might end up having one resource dictionary multiple times in memory. So the above mentioned solution seems like a very good alternative. In fact for our current project this trick did a lot ... Memory consumption from 800mb down to 44mb, which is a really huge impact. Unfortunately this solution comes at a cost, which i would like to show here, and hopefully find a way to avoid it while still use the SharedResourceDictionary.
I made a small example to visualize the problem with a shared resource dictionary.
Just create a simple WPF Application. Add one resource Xaml
Shared.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="myBrush" Color="Yellow"/>
</ResourceDictionary>
Now add a UserControl. The codebehind is just the default, so i just show the xaml
MyUserControl.xaml
<UserControl x:Class="Leak.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:SharedResourceDictionary="clr-namespace:Articy.SharedResourceDictionary" Height="128" Width="128">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Leak;component/Shared.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Rectangle Fill="{StaticResource myBrush}"/>
</Grid>
</UserControl>
The Window code behind looks something like this
Window1.xaml.cs
// [ ... ]
public Window1()
{
InitializeComponent();
myTabs.ItemsSource = mItems;
}
private ObservableCollection<string> mItems = new ObservableCollection<string>();
private void OnAdd(object aSender, RoutedEventArgs aE)
{
mItems.Add("Test");
}
private void OnRemove(object aSender, RoutedEventArgs aE)
{
mItems.RemoveAt(mItems.Count - 1);
}
And the window xaml like this
Window1.xaml
<Window.Resources>
<DataTemplate x:Key="myTemplate" DataType="{x:Type System:String}">
<Leak:MyUserControl/>
</DataTemplate>
</Window.Resources>
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Add" Click="OnAdd"/>
<Button Content="Remove" Click="OnRemove"/>
</StackPanel>
<TabControl x:Name="myTabs" ContentTemplate="{StaticResource myTemplate}">
</TabControl>
</DockPanel>
</Grid>
</Window>
I know the program is not perfect and propably could be made easier but while figuring out a way to show the problem this is what i came up with. Anyway:
Start this and you check the memory consumption, if you have a memory profiler this becomes much easier. Add (with showing it by clicking on the tab) and remove a page and you will see everything works fine. Nothing leaks.
Now in the UserControl.Resources section use the SharedResourceDictionary instead of the ResourceDictionary to include the Shared.xaml. You will see that the MyUserControl will be kept in memory after you removed a page, and the MyUserControl in it.
I figured this happens to everything that is instantiated via XAML like converters, user controls etc. Strangely this won't happen to Custom controls. My guess is, because nothing is really instantiated on custom controls, data templates and so on.
So first how we can avoid that? In our case using SharedResourceDictionary is a must, but the memory leaks makes it impossible to use it productively.
The Leak can be avoided using CustomControls instead of UserControls, which is not always practically. So why are UserControls strong referenced by a ResourceDictionary?
I wonder why nobody experienced this before, like i said in an older question, it seems like we use resource dictionaries and XAML absolutely wrong, otherwise i wonder why they are so inefficent.
I hope somebody can shed some light on this matter.
Thanks in advance
Nico
I'm running into the same issue of needing shared resource directories in a large-ish WPF project. Reading the source article and the comments, I incorporated a couple fixes to the SharedDirectory class as suggested in the comments, which seem to have removed the strong reference (stored in _sourceUri) and also make the designer work correctly. I tested your example and it works, both in the designer and MemProfiler successfully noting no held references. I'd love to know if anyone has improved it further, but this is what i'm going with for now:
public class SharedResourceDictionary : ResourceDictionary
{
/// <summary>
/// Internal cache of loaded dictionaries
/// </summary>
public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
new Dictionary<Uri, ResourceDictionary>();
/// <summary>
/// Local member of the source uri
/// </summary>
private Uri _sourceUri;
/// <summary>
/// Gets or sets the uniform resource identifier (URI) to load resources from.
/// </summary>
public new Uri Source
{
get {
if (IsInDesignMode)
return base.Source;
return _sourceUri;
}
set
{
if (IsInDesignMode)
{
try
{
_sourceUri = new Uri(value.OriginalString);
}
catch
{
// do nothing?
}
return;
}
try
{
_sourceUri = new Uri(value.OriginalString);
}
catch
{
// do nothing?
}
if (!_sharedDictionaries.ContainsKey(value))
{
// If the dictionary is not yet loaded, load it by setting
// the source of the base class
base.Source = value;
// add it to the cache
_sharedDictionaries.Add(value, this);
}
else
{
// If the dictionary is already loaded, get it from the cache
MergedDictionaries.Add(_sharedDictionaries[value]);
}
}
}
private static bool IsInDesignMode
{
get
{
return (bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty,
typeof(DependencyObject)).Metadata.DefaultValue;
}
}
}
I am not quite sure if this will solve your issue. But I had similar issues with ResourceDictionary referencing controls and its to do with lazy hydration. Here is a post on it. And this code resolved my issues:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
WalkDictionary(this.Resources);
base.OnStartup(e);
}
private static void WalkDictionary(ResourceDictionary resources)
{
foreach (DictionaryEntry entry in resources)
{
}
foreach (ResourceDictionary rd in resources.MergedDictionaries)
WalkDictionary(rd);
}
}
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.