WPF 中的数据绑定枚举






4.84/5 (8投票s)
将枚举绑定到 WPF 中的 UI 元素,

引言
.NET 提供了 enum
(枚举)结构,用于在一个对象中创建一组常量,例如一周中的几天或一年中的月份。我发现它们在各种情况下都非常方便,尤其是在涉及选择列表(从列表中选择一个值)的场景中。因此,enum
很自然地可以绑定到应用程序视图中的项目控件,例如列表框或组合框。
不幸的是,enum
并不容易在视图中以用户友好的方式显示。问题在于 enum
成员不能包含空格,这在使用 enum
绑定到 WPF 项目控件时可能导致可用性问题——用户不喜欢在组合框中看到“CustomerWith10PctDisc
”这样的名称。本文提出了一种解决该问题的简单方法。
有几种不同的方法可以为 enum
成员提供用户友好的对应项,以便在视图中显示。Sacha Barber 在 CodeProject 上有一篇关于这个主题的精彩文章,我推荐大家阅读,特别是如果您需要本地化要在 UI 中显示的友好 string
。Sacha 的文章使用反射读取修饰 enum
每个成员的可本地化描述属性,这效果很好,除非您有非常长的 enum
或大量 enum
需要显示为 string
。在这些情况下,反射可能会减慢组合框的速度,导致下拉列表出现明显的延迟。
在我开发的应用程序中,我不需要本地化,并且在阅读 Sacha 的文章时,我想知道是否可以在不诉诸反射的情况下完成工作。字典(dictionary)的想法立刻浮现在我脑海中,其结果就是本文附带的演示应用程序。与我遇到的一些其他方法相比,我对其简洁性感到惊讶。我认为 Sacha 的方法更优雅一些,但下面描述的字典方法很简单,并且能够完成任务。
我对这种特定方法的反馈和建议很感兴趣。在 CodeProject 上发布的好处之一是其高质量的同行评审。我将定期更新文章以反映反馈和建议,当然,也会注明原作者的贡献。
演示应用程序
演示应用是一个非常简单的应用,围绕 MVVM 模式构建。视图模型有一个命令属性和一个数据属性。命令属性实现为独立的 ICommand
对象,而不是视图模型中的内联代码。
主窗口显示一个组合框,它绑定到视图模型。
<!-- Note that the ItemsSource binding is a 'fake' binding. The items
are generated by the value converter referenced in the binding. -->
<ComboBox ItemsSource="{Binding Converter={StaticResource FontStylesListProvider}}"
SelectedItem="{Binding Path=FontStyle, Converter={StaticResource
FontStylesValueConverter}, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Width="200" Height ="26" />
ItemsSource
属性通常绑定到视图模型——它不绑定到任何特定属性。这是因为它实际上并非从视图模型导入其值列表。相反,列表是由绑定中引用的 FontStylesListProvider
生成的。
我花了一些时间犹豫是否要使用这种方法。老实说,它对我来说有点“代码异味”。值转换器应该转换值,而不是生成它们。但这种方法大大简化了事情,所以在经过一番纠结和喝了几杯啤酒后,我决定采用它。XAML 中的一个注释指出了数据绑定是“伪造”的,我强烈建议任何其他人采用此方法的人添加类似的注释。
组合框的 SelectedItem
属性绑定到视图模型中的 FontStyle
属性。它使用第二个值转换器 FontStylesValueConverter
,在 enum
成员和用户友好的对应项之间进行转换。转换是双向的;它会将 enum
值转换为友好名称,反之亦然。我将在下面更详细地讨论这两个转换器。
除了组合框之外,主窗口还有两个文本块,显示视图模型 FontStyle
属性当前保存的原始、未转换的 enum
值。这两个文本块提供了验证 enum
到友好 string
的转换是否正确地在两个方向上执行。最后,主窗口包含一个按钮,用于将视图模型属性设置为“BoldItalic
”。可以单击此按钮来验证当视图模型 FontStyle
属性值由代码更改而不是通过组合框选择更改时,视图是否正确更新。
ViewModel
视图模型包含一个数据属性 FontStyle
。该属性的类型为 FontStyles
,即我们将绑定到视图中组合框的 enum
。此外,视图模型包含一个命令属性 SetFontStyle
,它在视图模型构造函数中实例化为一个自定义 ICommand
对象 SetFontStyleCommand
。属性设置代码位于 ICommand 的 Execute()
方法中。
枚举
演示类中的 enum
是一个非常简单的字体样式列表。
public enum FontStyles { Normal, Bold, Italic, BoldItalic }
enum
的前三个元素在组合框中看起来都很好,但第四个成员看起来会相当丑陋。一个用户友好的名称,如“Bold + Italic”,会好得多。帮助类和转换器为 enum
的所有四个元素提供了友好的名称。
Helper 类
enum
元素和友好名称之间的转换围绕一个字典对象构建。FontStylesHelper
类创建并维护这个字典。实际上,有两个字典;一个用于正向(enum
到友好名称)转换,一个镜像字典用于反向(友好名称到 enum
)转换。这两个转换器类调用帮助类来执行转换查找。为了方便起见,这三个类都可以与 enum
捆绑在一个代码文件中。在演示应用中,代码文件名为 FontStyles.cs。
帮助类是一个 internal static
类——它不打算被代码文件以外的任何其他东西访问。它有一个构造函数和两个属性。
internal static class FontStylesHelper
{
#region Constructor
static FontStylesHelper()
{
// Create enum-to friendly name dictionary
FontStyleFriendlyNames = new Dictionary<FontStyles, string>
{
{FontStyles.Normal, "Normal Style"},
{FontStyles.Bold, "Bold Style"},
{FontStyles.Italic, "Italic Style"},
{FontStyles.BoldItalic, "Bold + Italic Style"},
};
// Create friendly name-to-enum dictionary
FontStyleEnumValues = FontStyleFriendlyNames.ToDictionary(x => x.Value, x => x.Key);
}
#endregion
#region Properties
public static Dictionary<FontStyles, String> FontStyleFriendlyNames { get; set; }
public static Dictionary<String, FontStyles> FontStyleEnumValues { get; set; }
#endregion
}
构造函数创建一个 Dictionary<FontStyles, String>
对象,该对象将 enum
成员转换为其用户友好的对应项。然后它创建一个反向查找字典,用于将用户友好的名称转换回相应的 enum
成员。
最后,帮助类包含两个属性,每个属性对应一个字典。转换器通过这些属性执行它们的转换。
字体样式列表提供程序
第一个转换器是 FontStylesListProvider
。
public class FontStylesListProvider : IValueConverter
{
#region Implementation of IValueConverter
public object Convert
(object value, Type targetType, object parameter, CultureInfo culture)
{
/* Note that this converter does not convert a value passed in. Instead, it generates
* a list of user-friendly counterparts for each member of the target enum and
* returns that list to the caller. */
var fontStyleList = FontStylesHelper.FontStyleFriendlyNames.Values.ToList();
return fontStyleList;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
这个转换器实际上并不进行转换,这就是为什么它被命名为“提供程序”而不是“转换器”。它查询友好名称字典以获取其值列表,并将该列表返回给调用者。当组合框的 ItemsSource
属性被解析时,就会调用这个转换器。
请注意,我们只需要一个从 enum
到视图的正向“转换”——这个转换器需要执行的唯一任务是提供将在组合框下拉列表中显示的友好名称列表。它不需要将任何内容传回视图模型。由于我们不需要反向转换(从视图模型到视图),因此我们不实现 ConvertBack()
方法。
字体样式值转换器
第二个转换器是 FontStylesValueConverter
。
public class FontStylesValueConverter: IValueConverter
{
#region Implementation of IValueConverter
public object Convert
(object value, Type targetType, object parameter, CultureInfo culture)
{
var enumValue = (FontStyles)value;
var friendlyName = FontStylesHelper.FontStyleFriendlyNames[enumValue];
return friendlyName;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
var friendlyName = (String)value;
var enumValue = FontStylesHelper.FontStyleEnumValues[friendlyName];
return enumValue;
}
#endregion
}
这个转换器在 enum
成员及其用户友好的对应项之间提供完整的双向转换。从视图模型传递到视图的 Enum
值被转换为友好名称,从视图传递到视图模型的友好名称被转换回 enum
值。
与第一个转换器不同,这个转换器是常规使用的。代码非常简单——在每种情况下,传递的值都被强制转换为适当的类型;然后,在其相应的字典中查找其对应项,并将结果返回。
将枚举绑定到组合框
演示应用在 XAML 中执行所有数据绑定。绑定标记非常简单——与其他解决方案不同,我们不需要 ObjectDataProvider
,也不需要组合框的 DataTemplate
。
<Window x:Class="WpfEnumBindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfEnumBindingDemo" Title="WPF Enum Binding Demo"
Height="350" Width="525">
<Window.Resources>
<!-- Value Converter to provide friendly name list for FontStyles enum -->
<local:FontStylesListProvider x:Key="FontStylesListProvider"/>
<!-- Value Converter to convert between
FontStyles enum and user-friendly names -->
<local:FontStylesValueConverter x:Key="FontStylesValueConverter"/>
</Window.Resources>
<StackPanel Margin="0,100,0,0">
<!-- Note that the ItemsSource binding is a 'fake' binding. The items
are generated by the value converter referenced in the binding. -->
<ComboBox ItemsSource="{Binding Converter=
{StaticResource FontStylesListConverter}}"
SelectedItem="{Binding Path=FontStyle,
Converter={StaticResource FontStylesValueConverter},
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="200" Height ="26" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Enum value in view model:"
FontStyle="Italic" Margin="155,10,0,0" />
<TextBlock Text="{Binding Path=FontStyle, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Width="50" Height ="26" Margin="5,10,0,0" />
</StackPanel>
</StackPanel>
</Window>
请注意,我们提供了本地命名空间声明,并且像往常一样将值转换器声明为窗口的资源。我们绑定了两个组合框属性。
ComboBox.ItemsSource
属性绑定到第一个转换器,该转换器提供填充下拉列表的用户友好名称列表。- 我们将
ComboBox.SelectedItem
属性绑定到第二个转换器,以执行enum
值和用户友好名称之间的双向转换。
组合框下方的两个文本块显示视图模型 FontStyle
属性中保存的原始、未转换的 enum
值。第一个文本块充当标签,第二个文本块显示 enum
值。它们用于验证转换是否按预期在两个方向上执行。
Using the Code
实现此处描述的 enum
绑定方法很简单。步骤如下:
步骤 1: 将 FontStyle.cs 代码文件复制到您的应用程序中,并将其命名为您要绑定的 enum
的名称。
步骤 2: 在代码文件中,将 FontStyle
enum
替换为您要绑定的 enum
。
步骤 3: 将 Dictionary<TKey, TValue>
声明中的键类型更改为您 enum
的名称。例如,Dictionary<FontStyles, String>
将变为 Dictionary<MyEnum, String>
。
步骤 4: 在 FontStylesHelper
构造函数中,用适用于您枚举的列表替换 enum
值和相应值名称(键值对)的列表。
可以通过在帮助类构造函数中使用反射来读取 enum
的描述属性并构建字典列表来进一步增强代码文件。我选择不使用该方法,因为我不想要与反射相关的开销。
结论
我非常喜欢这种方法的简洁性。值转换器都只有几行代码,我认为这是良好转换器的特征。这两个转换器都由相同的字典驱动,并且这两个字典都由相同的键值列表生成。因此,根本没有代码重复。
如上所述,我不太喜欢使用转换器来生成值列表的想法,而不是从一个值转换为另一个值。我乐于接受其他以更常规方式实现相同结果的方法,并欢迎您的评论。
希望本文提出的解决方案能简化您在 WPF 应用程序中将 enum
绑定到项目控件的过程。如果您对总体方法有任何疑问,请在下方提出。
历史
- 2011/08/23: 完成初始版本