views:

3010

answers:

4

I am currently building an application that consists of several components, each of which is essentially a WPF user control with a little C# code around it for the plugin system to work (using MEF).

The problem I am having is that each component should include an icon and for niceness purposes I defined that as a System.Windows.Media.Brush so I can just use the DrawingBrush exported from Design there. Now I need to access that piece of XAML from non-WPF C# where I currently have the horrible workaround of instantiating the user control and asking it for the resource:

private Brush CachedIcon = null;

public override Brush Icon
{
    get
    {
        if (CachedIcon == null)
        {
            CachedIcon = (Brush)(new BlahControl().TryFindResource("Icon"));
        }
        return CachedIcon;
    }
}

I couldn't find a way to read that resource (which is a .xaml file, and referenced in a ResourceDictionary in the custom control) from a "normal" C# class. Anything belonging to WPF has that nice TryFindResource method but how to do that otherwise? I don't want to have the XAML file with the icon lying around un-embedded.

A: 

You can read resources from your assembly as stream.

Example code here: http://www.wpftutorial.net/ReadWPFResourcesFromWinForms.html

boj
Hmm, couldn't get it to work right now, will try agani later. But this doesn't exactly strike me as a sensible solution to the problem, either. However, apparently XAML files get embedded as XAML when setting the Build action to Resource instead of Page. Might be a better option that way. I'll try.
Joey
A: 

Define the icons at the app level instead of in the control, either in the app.xaml or a master resource dictionary xaml file. Then you can use the same TryFindResource method, but without creating an instance of the control.

Lee Roth
As the project is not a WPF application but only user control there is no App.xaml. And where woulld I call TryFindResource when having only the ResourceDict? The icon already resides in one.
Joey
+2  A: 

In your XAML code make sure the icon resource has the build option set to "Resource", and then reference the resource to make it a xaml static resource

<UserControl.Resources>
    <BitmapImage x:Key="icon1" UriSource="Resources/Icon1.ico" />
</UserControl.Resources>

Then in your .Net 2.0 code you will find the resource in the "{xamlName}.g.resource" stream

Example code that loads all icons from a xaml dll into a dictionary:

using System.IO;
using System.Reflection;
using System.Collections;
using System.Resources;

...

var icons = new Dictionary<String, Bitmap>();
var externalBaml = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, "MyXaml.dll"));
Stream resourceStream = externalBaml.GetManifestResourceStream(externalBaml.GetName().Name + ".g.resources");
using (ResourceReader resourceReader = new ResourceReader(resourceStream)) {
    foreach (DictionaryEntry resourceEntry in resourceReader) {
        if (resourceEntry.Key.ToString().ToUpper().EndsWith(".ICO")) {
            icons.Add(resourceEntry.Key.ToString(), Image.FromStream(resourceEntry.Value as Stream) as Bitmap);
        }
    }
}
TFD
Seems to work, but looks veeeery hackish. Relying on internal names of automatically-generated resources doesn't seem very advisable to me.
Joey
Ehh? Resources have string names, there is nothing you can do about that! What are you looking for?
TFD
I was just wondering on how reliable the ".g.resources" thing will prove to be. I've read enough of Raymond Chen's blog that I don't really want to use anything that can break because it's just an artifact of the implementation, not documented behaviour.
Joey
It's part of the current WPF standard. It could change in WPF 4, but so could everything else! You could use ?externalBaml.GetManifestResourceNames() to parse through all the resource dictionaries
TFD
A: 

My suggestions are:

  • Provide metadata on your control about where the icon can be found. You can do this with your own custom attribute (see example 1 below). This metadata will allow you to load the icon without creating an instance of the control.

  • Since you're using MEF, you can use metadata in your export to achieve the same as above. Details here. See example 2 below.

  • Treat your icon as an ImageSource rather than a Brush. You can use WPF's Image control to show your ImageSource, or you can paint it with an ImageBrush.

  • Use the technique provided by TFD to read the resource with the name specified in the metadata. Unfortunately, WPF does not appear to provide anything like a BamlReader, which would make it much cleaner to load the WPF resource from a non-WPF context.

Example 1:

[Icon("MyIconResourceName")]
public class BlahControl : Control
{
    ...
}

Example 2:

[Export(typeof(IApplicationComponent))]
[ExportMetadata("IconResource", "MyIconResourceName")]
public class BlahControl : Control
{
    ...
}

HTH, Kent

Kent Boogaart