简体   繁体   中英

How can I convert an `enum` to a XAML vector icon object and display it multiple times within the same window?

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://docs.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 (ie 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 (eg 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 (ie 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 (ie 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:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM