Reputation: 1978
I have a resource dictionary in a Resources.xaml
file containing multiple vector icons (XAML format, Canvas
in a Viewbox
):
<ResourceDictionary>
<Viewbox x:Key="Icon1" x:Shared="False">
...
</Viewbox>
<Viewbox x:Key="Icon2" x:Shared="False">
...
</Viewbox>
</ResourceDictionary>
These icons can be displayed in a WPF window multiple times because I have used the x:Shared="False
setting. For example, ...
<ContentControl Content="{StaticResource Icon1}" />
<ContentControl Content="{StaticResource Icon1}" />
... displays the Icon1
icon twice as expected.
Now I'd like to convert an enum
to the icon object so that an icon can be displayed based on an enum
value (for nodes in a tree view). You would usually declare an EnumToObjectConverter
in the Resources.xaml
:
<local:EnumToObjectConverter x:Key="TreeIcons">
<ResourceDictionary>
<Viewbox x:Key="Icon1" x:Shared="False">
...
</Viewbox>
<Viewbox x:Key="Icon2" x:Shared="False">
...
</Viewbox>
<ResourceDictionary>
</local:EnumToObjectConverter>
But since this is an embedded resource dictionary the x:Shared
setting does not have any effect (https://learn.microsoft.com/en-us/dotnet/framework/xaml-services/x-shared-attribute) and referencing the image through the converter results in the icon being displayed only once in the Window or tree view, even when referenced in multiple places (the other places remain blank).
How can I do a mapping from an enum
to the vector icon object so that icons are still properly displayed in multiple places?
Update: This example demonstrates the effect of the x:Shared
setting (this is a NET Core 3.0 WPF application in case it makes any difference).
MainWindow.xaml
<Window x:Class="XamlIconTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:XamlIconTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<Label Content="Icon1 (1st)" />
<ContentControl Content="{StaticResource Icon1}" Margin="8"/>
<Separator />
<Label Content="Icon1 (2nd)" />
<ContentControl Content="{StaticResource Icon1}" Margin="8"/>
<Separator />
<Label Content="Icon2 (1st)" />
<ContentControl Content="{StaticResource Icon2}" Margin="8"/>
<Separator />
<Label Content="Icon2 (2nd)" />
<ContentControl Content="{StaticResource Icon2}" Margin="8"/>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace XamlIconTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
Resources.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:XamlIconTest">
<!-- Icon1 without x:Shared -->
<Path x:Key="Icon1"
Width="37.9858" Height="46.6386" Canvas.Left="19.186" Canvas.Top="14.2229" Stretch="Fill" Fill="#FF000000" Data="F1 M 38.1789,60.8614L 19.186,37.7428L 38.1686,14.2229L 57.1718,37.7531L 38.1789,60.8614 Z "/>
<!-- Icon2 with x:Shared -->
<Path x:Key="Icon2" x:Shared="False"
Width="37.9858" Height="46.6386" Canvas.Left="19.186" Canvas.Top="14.2229" Stretch="Fill" Fill="#FF000000" Data="F1 M 38.1789,60.8614L 19.186,37.7428L 38.1686,14.2229L 57.1718,37.7531L 38.1789,60.8614 Z "/>
</ResourceDictionary>
Displayed main window (note the missing Icon1 in the first row):
Upvotes: 2
Views: 302
Reputation: 70671
Your question seems to boil down to two separate topics:
x:Shared
has no effect (i.e. in a resource dictionary that's defined as a child of your converter).enum
value).First I will note: as a general rule it is my preference to use templates instead of x:Shared=false
with explicit resources. It winds up doing basically the same thing — instantiating new visual objects for each value displayed — but IMHO is more idiomatic for WPF, which is designed entirely around the concept of templating and binding.
As far as addressing your issues goes…
Your MCVE does not involve code that uses a converter, but the basic principle would be the same, so I will provide an example based on the MCVE, not using a converter. The approach involves doing as I suggested in the comments, which is to declare a resource containing the path's data (i.e. the geometry), and then reuse that resource as needed. The data itself isn't a visual, and so can be shared arbitrarily.
First, the resource:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestSO58533019ShareVectorData"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<PathGeometry x:Key="IconGeometry1">F1 M 38.1789,60.8614L 19.186,37.7428L 38.1686,14.2229L 57.1718,37.7531L 38.1789,60.8614 Z</PathGeometry>
</ResourceDictionary>
Then to use that, you can just define a DataTemplate
that maps a Geometry
object to the visual you want (in this case, a Path
object):
<Window x:Class="TestSO58533019ShareVectorData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestSO58533019ShareVectorData"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources.xaml" />
<ResourceDictionary>
<DataTemplate DataType="{x:Type Geometry}">
<Path Width="37.9858" Height="46.6386" Canvas.Left="19.186" Canvas.Top="14.2229"
Stretch="Fill" Fill="#FF000000"
Data="{Binding}"/>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<Label Content="IconGeometry1 (1st)" />
<ContentControl Content="{StaticResource IconGeometry1}" Margin="8"/>
<Separator />
<Label Content="IconGeometry1 (2nd)" />
<ContentControl Content="{StaticResource IconGeometry1}" Margin="8"/>
</StackPanel>
</Grid>
</Window>
This results in the display of the icon twice:
Now, the above approach could still be used with your converter technique. Your converter could return different Geometry
objects depending on the enum
value, which in turn could be bound to the Data
property of a Path
object as above. With some contortions, you could even have a Path
resource item that does this, using x:Shared=false
to reuse that resource item.
But IMHO that would be harder than necessary and not the right way to go. To me, conceptually what is going on is that you have an enum
value, and you want to represent that very value with some graphic, depending on the value. That's exactly what WPF's templating features are for! They map one data type to another (i.e. your enum
type to a Path
object), and with styles you can conditionally configure the templated object as needed.
For the sake of simplicity I will use int
rather than an actual enum
value. But the basic idea is exactly the same. Note that a key benefit of doing it this way is to minimize the amount of code-behind. You declare for WPF what it is you want to happen, instead of having to write procedural code to do something yourself that WPF could instead do for you.
First, let's define a couple of different icons:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestSO58533019ShareVectorData"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<PathGeometry x:Key="IconGeometry1">F1 M 38.1789,60.8614L 19.186,37.7428L 38.1686,14.2229L 57.1718,37.7531L 38.1789,60.8614 Z</PathGeometry>
<PathGeometry x:Key="IconGeometry2">F1 M 38.1789,60.8614L 19.186,37.7428L 57.1718,37.7531L 38.1789,60.8614 Z</PathGeometry>
</ResourceDictionary>
Now, let's define a template for int
, where that template uses style triggers to use the appropriate geometry data, and the bound value is simply that int
value:
<Window x:Class="TestSO58533019ShareVectorData.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestSO58533019ShareVectorData"
xmlns:s="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources.xaml" />
<ResourceDictionary>
<DataTemplate DataType="{x:Type s:Int32}">
<Path Width="37.9858" Height="46.6386" Canvas.Left="19.186" Canvas.Top="14.2229"
Stretch="Fill" Fill="#FF000000">
<Path.Style>
<p:Style TargetType="Path">
<p:Style.Triggers>
<DataTrigger Binding="{Binding}" Value="1">
<DataTrigger.Setters>
<Setter Property="Data" Value="{StaticResource IconGeometry1}"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="2">
<DataTrigger.Setters>
<Setter Property="Data" Value="{StaticResource IconGeometry2}"/>
</DataTrigger.Setters>
</DataTrigger>
</p:Style.Triggers>
</p:Style>
</Path.Style>
</Path>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel>
<Label Content="1st int" />
<ContentControl Margin="8">
<ContentControl.Content>
<s:Int32>1</s:Int32>
</ContentControl.Content>
</ContentControl>
<Separator />
<Label Content="2nd int" />
<ContentControl Margin="8">
<ContentControl.Content>
<s:Int32>2</s:Int32>
</ContentControl.Content>
</ContentControl>
</StackPanel>
</Grid>
</Window>
With that code, you'll get this:
Upvotes: 4