首页 > 解决方案 > 如何将“枚举”转换为 XAML 矢量图标对象并在同一窗口中多次显示?

问题描述

Resources.xaml我在一个包含多个矢量图标(XAML 格式,Canvas在 a 中)的文件中有一个资源字典Viewbox

<ResourceDictionary>
    <Viewbox x:Key="Icon1" x:Shared="False">
        ...
    </Viewbox>
    <Viewbox x:Key="Icon2" x:Shared="False">
        ...
    </Viewbox>
</ResourceDictionary>

这些图标可以在 WPF 窗口中多次显示,因为我已经使用了该x:Shared="False设置。例如, ...

<ContentControl Content="{StaticResource Icon1}" />
<ContentControl Content="{StaticResource Icon1}" />

...Icon1按预期显示图标两次。

现在我想将 an 转换enum为图标对象,以便可以根据enum值显示图标(对于树视图中的节点)。您通常会EnumToObjectConverterResources.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>

但由于这是一个嵌入式资源字典,因此该x:Shared设置没有任何效果(https://docs.microsoft.com/en-us/dotnet/framework/xaml-services/x-shared-attribute)并通过转换器导致图标在窗口或树视图中仅显示一次,即使在多个位置引用(其他位置保持空白)也是如此。

如何进行从一个enum到矢量图标对象的映射,以便图标仍然正确显示在多个位置?

更新:此示例演示了x:Shared设置的效果(这是一个 NET Core 3.0 WPF 应用程序,以防它产生任何影响)。

主窗口.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>

主窗口.xaml.cs

using System.Windows;

namespace XamlIconTest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

资源.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>

显示的主窗口(注意第一行中缺少的 Icon1):

在此处输入图像描述

标签: c#wpfxaml

解决方案


您的问题似乎可以归结为两个不同的主题:

  • 第一个是如何在x:Shared没有影响的上下文中共享矢量图形(即在定义为转换器子级的资源字典中)。
  • 隐含的次要属性,即如何在给定输入值(例如值)的情况下选择特定矢量图形enum

首先我要注意:作为一般规则,我更喜欢使用模板而不是x:Shared=false显式资源。它最终做了基本相同的事情——为每个显示的值实例化新的视觉对象——但恕我直言,对于 WPF 来说更惯用,它完全围绕模板和绑定的概念设计。

就解决您的问题而言……

您的 MCVE 不涉及使用转换器的代码,但基本原理是相同的,所以我将提供一个基于 MCVE 的示例,而不是使用转换器。该方法涉及按照我在评论中的建议进行操作,即声明包含路径数据(即几何图形)的资源,然后根据需要重用该资源。数据本身不是视觉对象,因此可以任意共享。

一、资源:

<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>

然后要使用它,您可以定义一个将对象DataTemplate映射Geometry到您想要的视觉对象(在本例中为Path对象):

<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>

这会导致图标显示两次:

在此处输入图像描述

现在,上述方法仍然可以与您的转换器技术一起使用。Geometry您的转换器可以根据值返回不同的对象enum,而这些对象又可以绑定到上述对象的Data属性Path。通过一些扭曲,您甚至可以拥有Path执行此操作的资源项,x:Shared=false用于重用该资源项。

但是恕我直言,这将比必要的更难,而且不是正确的方法。对我来说,从概念上讲,你有一个enum价值,你想用一些图形来表示这个价值,具体取决于价值。这正是 WPF 的模板功能的用途!它们将一种数据类型映射到另一种数据类型(即您的enum类型到Path对象),并且您可以根据需要使用样式有条件地配置模板化对象。

为了简单起见,我将使用int而不是实际enum值。但基本思想是完全一样的。请注意,这样做的一个关键好处是最大限度地减少代码隐藏的数量。您为 WPF声明您想要发生的事情,而不必编写程序代码来自己做 WPF 可以为您做的事情。

首先,让我们定义几个不同的图标:

<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>

现在,让我们为 定义一个模板int,该模板使用样式触发器来使用适当的几何数据,而绑定值就是该int值:

<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>

使用该代码,您将获得以下信息:

在此处输入图像描述


推荐阅读