c# - 如何将“枚举”转换为 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
值显示图标(对于树视图中的节点)。您通常会EnumToObjectConverter
在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>
但由于这是一个嵌入式资源字典,因此该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):
解决方案
您的问题似乎可以归结为两个不同的主题:
- 第一个是如何在
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>
使用该代码,您将获得以下信息: