9

I want to reuse some XAML fragment as image in some WPF application/library.

The background of the problem is following:

It's easy to reuse a bitmap image in a WPF application. The image can be added as a resource, I can use <Image Source="packURI"/> at many places in XAML so the image will be the same.

But I'd like to have a possibility to do the same for a vector image. The image itself can be represented as Path, but I cannot reuse the same Path as a resource, because simply using it at several different places (and possibly from several UI threads) is prohibited (UI element can have only one logical parent).

Moreover, the question gets more complicated if I'd like to build the "image" from several Paths, use a Canvas for it. Or some arbitrary XAML code.

I tried using a Style for the Path, so the image is represented in such a way:

<Path Style={StaticResource VectorImage1}/>

This seems to be a reusable way, but I am concerned with two problems:

  1. If the implementation of a vector image is changed from Path to a (for instance) Canvas, I'll need to replace it not only in the style, but everywhere in the source code which uses it.
  2. Definition of a path using a style seems to be too verbose.
  3. I see no way to generalize this approach for using Canvas or arbitrary XAML code.
  4. The syntax seems to be quite unnatural.

There is other way to have a reusable XAML fragment, through defining a UserControl, but defining a separate user control for each vector image seems to be an overkill.

Is there a better, nice, right way to define a reusable XAML fragment?

Vlad
  • 35,022
  • 6
  • 77
  • 199

3 Answers3

15

You can add the x:Shared attribute to the Path Resource and use it as a StaticResource. This will work if "MyVectorImage" changes to something else

Update
Probably better to use a ContentControl or similar to be able to add Properties, such as Margin etc.

<Window.Resources>
    <Path x:Key="MyVectorImage"
          x:Shared="False"
          Stroke="DarkGoldenRod"
          StrokeThickness="3"
          Data="M 10,20 C 10,25 40,35 40,17 H 28"
          Stretch="Fill"
          Width="100"
          Height="40"/>
</Window.Resources>
<StackPanel>
    <ContentControl Margin="10" Content="{StaticResource MyVectorImage}"/>
    <ContentControl Margin="10" Content="{StaticResource MyVectorImage}"/>
</StackPanel>

Example. You replace "MyVectorImage" with a StackPanel containing two Paths.

<Window.Resources>
    <StackPanel x:Key="MyVectorImage"
                x:Shared="False">
        <Path Stroke="DarkGoldenRod"
              StrokeThickness="3"
              Data="M 10,20 C 10,25 40,35 40,17 H 28"
              Stretch="Fill"
              Width="100"
              Height="40"/>
        <Path Stroke="DarkGoldenRod"
              StrokeThickness="3"
              Data="M 10,20 C 10,25 40,35 40,17 H 28"
              Stretch="Fill"
              Width="100"
              Height="40"/>
    </StackPanel>
</Window.Resources>
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • 1
    I tried to use your suggestion, but at runtime I get an exception: "Shared attribute in namespace 'http: //schemas.microsoft.com/winfx/2006/xaml' can be used only in compiled resource dictionaries." I moved `MyVectorImage` to a separate XAML resource dictionary, but I get the same error message when I include that dictionary into the Window's MergedDictionaries. – Vlad Dec 08 '10 at 13:09
  • @Vlad@ I updated my example. If you tried the example with the StackPanel I had forgot to move the x:Shared attribute to the StackPanel. – Fredrik Hedblad Dec 08 '10 at 13:15
  • I see, my bad, I should have tried to understand read what exactly the message say. With x:Shared only at the top level it works. – Vlad Dec 08 '10 at 13:19
  • @Vlad: Yes, I got the same exception when I tried to run it.. It showed up fine in the designer so I didn't bother to actually try and run it :) – Fredrik Hedblad Dec 08 '10 at 13:25
  • Cool works for me. Now I can keep all my vector graphic in separate resource dictionary! – EdwardM Dec 03 '15 at 21:04
5

After some research, there is one more option: using a DrawingImage as Source for an image. The customary image source is a BitmapSource, however it can be "vector graphics" as well.

Here is an example:

<Image>
  <Image.Source>
    <DrawingImage PresentationOptions:Freeze="True">
      <DrawingImage.Drawing>
        <GeometryDrawing>
          <GeometryDrawing.Geometry>
            <GeometryGroup>
              <EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />
              <EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />
            </GeometryGroup>
          </GeometryDrawing.Geometry>
          <GeometryDrawing.Brush>
            <LinearGradientBrush>
              <GradientStop Offset="0.0" Color="Blue" />
              <GradientStop Offset="1.0" Color="#CCCCFF" />
            </LinearGradientBrush>
          </GeometryDrawing.Brush>
          <GeometryDrawing.Pen>
            <Pen Thickness="10" Brush="Black" />
          </GeometryDrawing.Pen>
        </GeometryDrawing>
      </DrawingImage.Drawing>
    </DrawingImage>
  </Image.Source>
</Image>

produces such a nice vector image:

vector image from the source above

Yet another option might be using a DrawingBrush, like in this SO question: How to store and retrieve multiple shapes in XAML/WPF?.

Vlad
  • 35,022
  • 6
  • 77
  • 199
  • 1
    I prefer this approach, but I think the answer would benefit from some inline code examples. Using a DrawingImage allows you to combine multiple vector graphics (and multiple colors) into a single, cohesive icon, by using the element as the content property of the DrawingImage. It also allows you - in your UI code - to always use an element for your icons, regardless of whether you're referring to a vector image or a bitmap image; the "Source" property will either refer to the DrawingImage vector resource, or the BitmapSource image resource. – BTownTKD Oct 26 '14 at 12:47
  • @BTownTKD: you are right, I'll try to construct some examples – Vlad Nov 05 '14 at 23:50
  • Example link is broken and cannot be recovered. (The MSDN content for that member has no example.) Does anybody have content to share on this site? – ygoe Aug 04 '15 at 17:55
  • 1
    @LonelyPixel: I've corrected the link and inlined the example. – Vlad Aug 04 '15 at 18:26
3

You can store the path in a resource dictionary and set x:Shared to false:

<Path x:Key="CrossPath"
      x:Shared="false"
      ...
      />

This will tell WPF to create a new instance every time it is requested. http://msdn.microsoft.com/en-us/library/aa970778.aspx

Robert Jeppesen
  • 7,837
  • 3
  • 35
  • 50