目录
介绍
提供了什么
本地定义的枚举器
代码
EnumItemList集合和EnumItem集合项
附加属性
使用代码
结束语
- 下载控件-141.8 KB
几天前,我发布了这篇文章,其中描述了一种创建可观察的枚举器值集合的方法,以供在WPF应用程序中使用。在本文中,我将把这个想法带入一个更高的逻辑层次——创建专用于允许选择枚举器值的列表控件。
最初的想法是为控件提供对C#中任何System枚举器的支持。这当然足够了,但是正如我所看到的那样,对于实际有用只是一个“半步骤”。因此,我还添加了对本地定义的枚举器的支持。
在我的最后几篇文章中,我同时提供了.Net Framework和.Net Core版本的代码,但是坦率地说,我认为你们真的不值得这样做(至少,不是我本人) 。转换为.Net Core并非易事,特别是如果您不使用.Net框架代码中的任何System.Drawing或ADO内容,那么如果您愿意/需要,可以随时进行转换。
提供了什么由于枚举器本质上是一种简单的方法,有用属性的数量恰好是1(枚举器的名称),因此创建一个ListBox和一个ComboBox并忽略ListView是有意义的。除非另有说明,否则以下功能集适用于两个控件。
- 对于系统枚举器,只需指定枚举类型名称。该控件将创建结果枚举器集合并将其绑定到该控件,而无需在XAML中进行操作。以下代码段是显示包含星期几的ListBox(或ComboBox)代码所需的最少代码量。(我知道!太棒了,对!!)
- 如果枚举器表示标志(用[Flags]属性装饰),则ListBox将会自动成为多选的ListBox,除非您通过将AutoSelectionMode属性设置为false(默认值为true)来指定不应这样做:
- 您可以选择将ShowOrdinalWithName属性设置为true(默认值为false),以显示带有名称的枚举数的序数值。
- 所有基础类型都被支持(但是开发者仍要确保他所做的事情有意义)。
我敢肯定你们中的大多数人都实现了自己的枚举器,并且知道这一点,如果不提供某种方式来与自己的枚举器一起使用这些自定义控件,那就太可笑了。将自己的枚举数与可全局访问的自定义控件一起使用的主要问题是,该控件无法知道为特定应用程序定义的内容。
由于这些控件使用的实际集合是在控件的命名空间中定义的,因此您要做的就是在窗口/用户控件中实例化它,如下所示:
给出以下枚举声明:
public enum EnumTest1 { One=1, Two, Three, Four, Fifty=50, FiftyOne, FiftyTwo }
您可以像这样实例化集合:
public EnumItemList Enum1 { get; set; }
...
this.Enum1 = new EnumItemList(typeof(EnumTest1), true);
然后,您可以将其手动绑定到控件:
代码
一般来说,EnumComboBox和EnumListBox的底层是相同的。如果我能找到一种只在一个类中编写代码的方法,我一定会找到。但是,C#的本质要求我本质上复制两个类中的所有代码。唯一真正的区别是组合框不支持多选。因为代码本质上是相同的,所以我将仅详细讨论EnumListBox。我不会讲在WPF中创建自定义控件的细节,因为互联网上有无数其他产品可以比我更好地描述该过程。相反,如果我认为原因很重要,我只想告诉你我做了什么,甚至是我为什么做。
EnumItemList集合和EnumItem集合项为了使实际项目对使用控件的表单尽可能有用,枚举器将许多最有用的信息分解为易于访问的属性。这减轻了开发人员在其窗口/用户控件中对所选项目进行后处理的负担。
public class EnumItem
{
// The actual value of the enumerator (i.e., DayOfWeek.Monday)
public object Value { get; set; }
// The name of the enumerator value (i.e., "Monday")
public string Name { get; set; }
// The enumerator type (i.e., DayOfWeek)
public Type EnumType { get; set; }
// The underlying enumerator type (i.e., Int32)
public Type UnderlyingType { get; set; }
// A helper property that determines how the enumartor value is
// displayed in the control
public bool ShowOrdinalWithName { get; set; }
public EnumItem()
{
this.ShowOrdinalWithName = false;
}
public override string ToString()
{
return (this.ShowOrdinalWithName) ? string.Format("({0}) {1}",
Convert.ChangeType(this.Value, this.UnderlyingType),
Name)
: this.Name;
}
}
实际上绑定到控件的EnumItemList ObservableCollection负责创建自己的项。它还可以自确定控件是否可以进行多重选择。请记住,如果要在控件中显示一个本地枚举数,则必须自己实例化此集合(已经提供了这样做的示例)。
public class EnumItemList : ObservableCollection
{
public bool CanMultiSelect { get; set; }
public EnumItemList(Type enumType, bool showOrd)
{
// if the enumerator is decorated with the "Flags" attribute,
// more than one item can be selected at a time.
this.CanMultiSelect = enumType.GetCustomAttributes().Any();
// find all of the enumerator's members
this.AsObservableEnum(enumType, showOrd);
}
public void AsObservableEnum(Type enumType, bool showOrd)
{
// if the specified type is not null AND it is actually an
// enum type, we can create the collection
if (enumType != null && enumType.IsEnum)
{
// discover the underlying type (int, long, byte, etc)
Type underlyingType = Enum.GetUnderlyingType(enumType);
// get each enum item and add it to the list
foreach (Enum item in enumType.GetEnumValues())
{
this.Add(new EnumItem()
{
// the name that will probably be displayed in the
// UI component
Name = item.ToString(),
// the actual enum value (DayofWeek.Monday)
Value = item,
// the enum type
EnumType = enumType,
// the underlying type (int, long, byte, etc)
UnderlyingType = underlyingType,
ShowOrdinalWithName = showOrd,
});
}
}
}
}
附加属性
对于您将要编写的几乎每个自定义控件,您都将添加一些基类中不可用的属性,这仅是启用自定义功能的目的,并且EnumListBox肯定没有什么不同。
//---------------------------------------------------------
// This property allows you to specify the type name for system
// enumerators (it's pointless to try using local enumerators
// here because the control won't be able to discover its
// members.)
public static DependencyProperty EnumTypeNameProperty =
DependencyProperty.Register("EnumTypeName",
typeof(string),
typeof(EnumListBox),
new PropertyMetadata(null));
public string EnumTypeName
{
get { return (string)GetValue(EnumTypeNameProperty); }
set { SetValue(EnumTypeNameProperty, value); }
}
//---------------------------------------------------------
// This property allows you to turn off the automatic
// determination of whether or not to use multiple selection.
// This only affects list boxes because combo boxes do not
// support multiple-selection. The default value is true.
public static DependencyProperty AutoSelectionModeProperty =
DependencyProperty.Register("AutoSelectionMode",
typeof(bool),
typeof(EnumListBox),
new PropertyMetadata(true));
public bool AutoSelectionMode
{
get { return (bool)GetValue(AutoSelectionModeProperty); }
set { SetValue(AutoSelectionModeProperty, value); }
}
//---------------------------------------------------------
// This property causes the displayed enumerator name to be
// pre-pended with the ordnial value of the enumerator. The
// default value is false.
public static DependencyProperty ShowOrdinalWithNameProperty =
DependencyProperty.Register("ShowOrdinalWithName",
typeof(bool),
typeof(EnumListBox),
new PropertyMetadata(false));
public bool ShowOrdinalWithName
{
get { return (bool)GetValue(ShowOrdinalWithNameProperty); }
set { SetValue(ShowOrdinalWithNameProperty, value); }
}
也有几个辅助属性:
// This property represents the auto-created collection (for
// system enums only).
public EnumItemList EnumList { get; set; }
// This property provides the actual Type based on the enum
// type name
public Type EnumType
{
get
{
Type value = (string.IsNullOrEmpty(this.EnumTypeName))
? null : Type.GetType(this.EnumTypeName);
return value;
}
}
剩下的唯一事情就是控件对加载的反应。我使用Loaded事件来确定绑定该集合要做什么。当我向ItemsSource添加对XAML内支持的支持时,我觉得有必要验证绑定集合的EnumItemList类型,并发现我必须获取父窗口的DataContext类型。
private void EnumListBox_Loaded(object sender, RoutedEventArgs e)
{
// avoid errors being displayed in designer
if (!DesignerProperties.GetIsInDesignMode(this))
{
// if the enum type is not null, the enum must be a system enum, so we can
// populate/bind automatically
if (this.EnumType != null)
{
// create the list of enums
this.EnumList = new EnumItemList(this.EnumType, this.ShowOrdinalWithName);
// create and set the binding
Binding binding = new Binding() { Source=this.EnumList };
this.SetBinding(ListBox.ItemsSourceProperty, binding);
}
else
{
// otherwise, the developer specifically set the binding, so we have
// to get the datacontext from the parent content control (window or
// usercontrol) so we can use the specified collection
this.DataContext = EnumGlobal.FindParent(this).DataContext;
// before we use it, make sure it's the correct type (it must be a
// EnumItemList object)
if (!(this.ItemsSource is EnumItemList))
{
throw new InvalidCastException("The bound collection must be of type EnumItemList.");
}
}
// no matter what happens, see if we can set the list to mult5iple selection
if (this.ItemsSource != null)
{
if (this.AutoSelectionMode)
{
this.SelectionMode = (((EnumItemList)(this.ItemsSource)).CanMultiSelect)
? SelectionMode.Multiple : SelectionMode.Single;
}
}
}
}
使用代码
使用控件非常容易,特别是考虑到您不必实现它们使用的集合时,尤其如此。您在窗口中创建的属性甚至不必使用INotifyPropertyChanged,因为一旦实例化,集合就永远不会改变。在cfact中,如果您要绑定到系统枚举器,则无需实例化集合。
public partial class MainWindow : Window
{
public EnumItemList Enum1 { get; set; }
public MainWindow()
{
this.InitializeComponent();
this.DataContext = this;
// we only have to do this for locally implemented enumerators.
this.Enum1 = new EnumItemList(typeof(EnumTest1), true);
}
...
}
XAML同样简单,因为缺少样式元素和处理控件中事件的愿望,因此您所要做的就是应用适当的绑定
>ctrls:EnumListBox x:Name="lbLocalEnum1" ItemsSource="{Binding Path=Enum1}" />
结束语
像我在这里一样,很少有机会创建专门与数据相关的控件。大多数时候,您必须写更大的目标。我本来只是允许System枚举器使用的,但是考虑了一段时间之后,我决定增加对本地枚举器的支持。为此,我只添加了几行代码,实际上将控件的用途增加了一倍。
许多人可能会说“面向未来”是在浪费时间,因为很有可能您永远不需要支持该范例的代码。诚实地讲?确实如此。但是,我认为要适应未来的发展比以后再添加代码要容易得多,因为通常,您几乎没有时间回头来改进代码。
https://www.codeproject.com/Articles/5295569/WPF-Dedicated-Enumerator-ListBox-and-ComboBox