您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

WPF——专用枚举器ListBox和ComboBox

寒冰屋 发布时间:2021-04-08 22:20:04 ,浏览量:0

目录

介绍

提供了什么

本地定义的枚举器

代码

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

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0645s