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):
Your question seems to boil down to two separate topics:
The primary one, which is how to share vector graphics in a context where x:Shared has no effect (i.e. in a resource dictionary that's defined as a child of your converter).
An implied secondary one, which is how to property select a specific vector graphic given an input value (e.g. an 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:
I am using the DropDownButton from the Xceed Wpf Toolkit.
In that popup I just want to display a manually-filled ListView.
A ListViewItem is just a vertical StackPanel with an Image and a TextBlock.
<xceed:DropDownButton Grid.Column="4" x:Name="BurgerButton">
<xceed:DropDownButton.Content>
<Image Source="/Resources/BurgerMenu_128x128.png"/>
</xceed:DropDownButton.Content>
<xceed:DropDownButton.DropDownContent>
<ListView>
<ListViewItem cal:Message.Attach="[Event MouseUp] = [Action NewProject]">
<StackPanel Orientation="Vertical">
<Image Source="/Resources/FileNew_32x32.png"/>
<TextBlock Text="{DynamicResource ProjectSelectorView_NewProject}"/>
</StackPanel>
</ListViewItem>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="DataContext" Value="{Binding DataContext, ElementName=BurgerButton}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</xceed:DropDownButton.DropDownContent>
</xceed:DropDownButton>
I am using Caliburn.Micro to trigger an Action when the MouseUp event of the ListViewItem fires. This works fine. The problem ist the <TextBlock Text="{DynamicResource ProjectSelectorView_NewProject}"/> It is just empty, despite the resource being there. Everywhere else in the project the DynamicResource syntax works fine. It just doesn't work inside the Popup. I know that a Popup doesn't reside in the same VisualTree as the main application - so DataContext sharingis a bit harder.
This isn't about the DataContext though, it is about the ResourceDictionary defined in the App.xaml. my App.xaml looks like this:
<Application x:Class="Projectname"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CVBRecorder">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Localization/English.xaml"/>
<ResourceDictionary Source="/Localization/German.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<ResourceDictionary>
<local:AppBootstrapper x:Key="bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Anyone know why the DynamicResource is not displayed inside a WPF popup?
Apparently this is a bug in the Xceed WPF Toolkit. I used version 3.2.0 which had the bug. Updating to 3.4.0 solved the problem.
I am trying to make a plugin WPF application that can be referenced on other applications and the latter can change the styles of the former.
My example is based on Xceed's BusyIndicator. I have a style for the BusyIndicator in my plugin WPF app and want the style of that BusyIndicator to be changed on other applications.
Example:
WPF Plugin Application: Let's call it OverrideBusyIndicator. The solution looks like the image below where a MainWindow containing the BusyIndicator exists and the BusyIndicator style is in BusyContextResourceDictionary.xaml
The content of BusyContextResourceDictionary.xaml is this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:xceed="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type xceed:BusyIndicator}">
<Setter Property="BusyContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="Downloading Email" FontWeight="Bold" HorizontalAlignment="Center"/>
<StackPanel Margin="4">
<TextBlock Text="Downloading message 4/10..."/>
<ProgressBar Value="40" Height="15"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Content="Pause" HorizontalAlignment="Right" Margin="0 0 2 0"/>
<Button Grid.Column="1" Content="Cancel" HorizontalAlignment="Left" Margin="2 0 0 0"/>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
I created another solution that will reference my assembly above. Let's call it OverrideBusyIndicator2. This one has no main window and its App.xaml just calls the other OverrideBusyIndicator.MainWindow. I then added a BusyContextResourceDictionary2.xaml that I EXPECT to override the style of the BusyIndicator but it does not. Any way I could achieve this behavior?
<Application x:Class="OverrideBusyIndicator2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xceed="http://schemas.xceed.com/wpf/xaml/toolkit"
StartupUri="pack://application:,,,/OverrideBusyIndicator;component/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="BusyContextResourceDictionary2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
For sample purposes BusyContextResourceDictionary2.xaml will just change the textblock text from "Downloading email" to "Not Downloading email".
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:xceed="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/OverrideBusyIndicator;component/BusyContextResourceDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!--I am using BasedOn to override the BusyContextResourceDictionary.xaml-->
<Style TargetType="{x:Type xceed:BusyIndicator}" BasedOn="{StaticResource {x:Type xceed:BusyIndicator}}">
<Setter Property="BusyContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Margin="4">
<TextBlock Text="NOT Downloading Email" FontWeight="Bold" HorizontalAlignment="Center"/>**
It would be helpful to see the style for the xceed:BusyIndicator control. You might need to define the default style key property for your control.
I feel like this is a common sense and trivial, but i don't understand what i'm doing to begin with. I don't have any other resource I can use for help either. Sometimes i wonder if I'm even googling the question right.
I have some custom styles & templates I've made, but now the file is rather large and difficult to work with. I want to put each style or template in there own XAML files (sorta like headers/implementation files) so that a friend could work on one and then we add it to the project. (Such as Dictionary1.xaml ... ). I started a blank project to keep it simple.
Dictionary1.XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary x:Key="SomeKey">
<Color x:Key="detailMark">Black</Color>
<SolidColorBrush x:Key="detailMarkBrush" Color="{StaticResource ResourceKey=detailMark}" />
<Style x:Key="flatTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Rectangle Stroke="{StaticResource ResourceKey=detailMarkBrush}" StrokeThickness="1"/>
<TextBox Margin="1" Text="{TemplateBinding Text}" BorderThickness="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
App.XAML
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<local:MainWindow x:Key="SomeKey"/>
</Application.Resources>
</Application>
And MainWindow.XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow"
Height="350" Width="525">
<Grid>
<TextBox Style="{DynamicResource flatTextBox}"> <!-- doesn't autocomplete/work -->
</TextBox>
</Grid>
</Window>
Edit:
<TextBox Style="{Binding Mode=OneWay, Source={StaticResource SomeKey}}">
<!-- Throws System.Windows.Markup.XamlParseException -->
</TextBox>
As Alex mentioned, the right way to do this is using Merged Dictionaries.
Therefore, you should structure your project correctly, otherwise it will end up in a mess.
Keeping your "blank project", it should look like this:
YourProject
App.xaml (.cs)
MainWindow.xaml (.cs)
SomeOtherWindow.xaml (.cs)
Resources folder
Dictionary1.xaml
Dictionary2.xaml
...
Then you have to decide:
Do you want the resources to be available application wide?
Or do you want the resources to vary between certain windows / user controls?
If you want #1, you have to merge the dictionaries in the App.xaml file:
<Application x:Class=...
...>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Dictionary1.xaml"/>
<ResourceDictionary Source="Resources/Dictionary2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
If you want #2, you have to merge the dictionaries in the specific window / user control file:
<Window x:Class=...
...>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Dictionary1.xaml"/>
<ResourceDictionary>
<!-- Window specific resources -->
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<!-- Content -->
</Window>
I have external library libccc with its own resources defined like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ccc="clr-namespace:libccc"
>
<ccc:BooleanToHiddenVisibilityConverter x:Key="BooleanToHiddenVisibilityConverter" />
</ResourceDictionary>
In my main project I include it using ResourceDictionary.MergedDictionaries in main app.xaml:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/libccc;component/Resources/Converters.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Now my problem is that when I reference that resource from Control template only like this:
<window style="{StaticResource MyDialog}">
</window>
where style is defined in application like this:
<ResourceDictionary>
<Style x:Key="MyDialog" TargetType="{x:Type Window}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<Label HorizontalAlignment="Center" Content="{Binding UpdateResultDescription}" Visibility="{Binding UpdateEnded, Converter={StaticResource BooleanToHiddenVisibilityConverter}, FallbackValue=Hidden}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary
VisualStudio Designer throws exception:
Exception: Cannot find resource named 'BooleanToHiddenVisibilityConverter'. Resource names are case sensitive.
My application works as expected, but designer throws exception. When I reference my libccc in other windows that do not use control template then designer works fine.
Anyone can give me a hint what can I change to make designer work?