在Visual Studio设计器中公开一组枚举(标志)

时间:2009-05-06 20:36:58

标签: .net visual-studio winforms

我有一个可能在.NET Forms控件中显示的数据类型的枚举,我想为控件的使用者提供一个接口来过滤某些类型(设置一些标志)。一个位字段似乎是这样做的合理方式,不幸的是,枚举从0开始而不是1(0,1,2,4,8,...)并且无法更改。

如何公开这组标志,以便可以通过编程方式或通过Visual Studio设计器轻松配置?

2 个答案:

答案 0 :(得分:4)

您需要写一个UITypeEditor来完成工作,并通过[EditorAttribute]将其与该属性相关联。

编辑现在举例 - 相当长的一个,我很害怕 - 但幸运的是,大多数代码都可以在不同类型之间共享。

由于零,你不能使用单个复合枚举值 - 所以我在这里使用HashSet<T>来保存所选的枚举 - 如果你使用List<T>相当容易但是有.NET 2.0 / 3.0。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Design;

public class MyControl : UserControl
{
    public MyControl()
    {
        Values = new HashSet<MyEnum>();
    }
    [Editor(typeof(MyEnumSetEditor), typeof(UITypeEditor))]
    [TypeConverter(typeof(MyEnumSetConverter))]
    public HashSet<MyEnum> Values { get; set; }
}

public enum MyEnum
{  // numbers as per the question...
    A = 0, B = 1, C = 2, D = 4, E = 8
}
class MyEnumSetEditor : EnumSetEditor<MyEnum> { }
class MyEnumSetConverter : EnumSetConverter<MyEnum> { }

// from here down is shared between types
abstract class EnumSetConverter<T> : TypeConverter where T : struct
{
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
    }
    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if(destinationType == typeof(string))
        {
            HashSet<T> set = (HashSet<T>)value;
            if (set == null) return "(null)";

            StringBuilder sb = new StringBuilder();
            foreach (T item in Enum.GetValues(typeof(T)))
            {
                if (set.Contains(item))
                {
                    if (sb.Length > 0) sb.Append(", ");
                    sb.Append(item);
                }
            }
            return sb.ToString();
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

public abstract class EnumSetEditor<T> : UITypeEditor where T : struct
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }
    public override bool IsDropDownResizable
    {
        get { return true; }
    }
    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        IWindowsFormsEditorService svc = (IWindowsFormsEditorService)
            provider.GetService(typeof(IWindowsFormsEditorService));
        HashSet<T> set = value as HashSet<T>;
        if (svc != null && set != null)
        {
            UserControl ctrl = new UserControl();
            CheckedListBox clb = new CheckedListBox();
            clb.Dock = DockStyle.Fill;
            Button btn = new Button();
            btn.Dock = DockStyle.Bottom;
            foreach (T item in Enum.GetValues(typeof(T)))
            {
                clb.Items.Add(item, set.Contains(item));
            }
            ctrl.Controls.Add(clb);
            ctrl.Controls.Add(btn);
            btn.Text = "OK";
            btn.Click += delegate
            {
                set.Clear();
                foreach (T item in clb.CheckedItems)
                {
                    set.Add(item);
                }
                svc.CloseDropDown();
            };
            svc.DropDownControl(ctrl);
        }

        return value;
    }
}

答案 1 :(得分:0)

我遇到了同样的问题:编辑器控件似乎有效,但值不会持续存在。基于Marc的答案和一些进一步的研究,我现在已经开始运行了。 虽然我在DesignTime上需要此功能来编辑我自己的控件,但在阅读Microsoft的以下引用后,我使用带有PropertyGrid控件的演示项目对其进行了测试:

  

开发自定义UITypeEditor时,建议您使用   设置构建编号以随每个构建增加。这可以防止   UITypeEditor的旧版本,缓存版本在...中创建   设计环境。

我认为这实际上是一个在尝试实施Marc解决方案时遇到问题的问题。在单独的项目中进行测试也很有帮助,因为您可以使用Step-By-Step-Debugging来查看控件,编辑器和转换器中发生的情况。

基本步骤是:

  • 创建类型(此处为: MyFlags )和属性(此处为: MyProperty ),为其添加一些额外属性 后者。
  • 实施用于编辑的控件(此处为: EnumEditorControl )。
  • 实施UITypeEditor课程(此处: EnumEditor );这是使用Editor属性连接到属性。它还创建和管理 EnumEditorControl 的实例。
  • 实施TypeConverter课程(此处: EnumConverter );这也使用属性(TypeConverter)连接到属性,并处理将值转换为字符串以在属性网格中显示。

现在进行相关代码片段的演练:

  1. 使用Enum属性定义Flags

    <Flags>
    Public Enum MyFlags
        Flag1 = 2 ^ 0
        Flag2 = 2 ^ 1
        Flag3 = 2 ^ 2
    End Enum
    
  2. 定义自定义控件的属性。

    Public Property MyProperty() As MyFlags
        ...
    End Property
    
  3. 一旦我们创建了我们需要的其他组件,该属性稍后将添加属性(参见#6)。

    1. 创建用于编辑的所需控件 属性。

      Imports System.Windows.Forms.Design
      Public Class EnumEditorControl(Of T As Structure)
          Public Sub New(value As Long, editorService As IWindowsFormsEditorService)
              'This call is required by the Windows.Forms Form Designer.
              InitializeComponent()
      
              _value = value
              _editorService = editorService
      
              For Each item As Long In [Enum].GetValues(GetType(T))
                  Me.CheckedListBox1.Items.Add([Enum].GetName(GetType(T), item), (_value And item) = item)
              Next
      
          End Sub
      
          Private _value As Long
          Public Property Value As Long
              Get
                  Return _value
              End Get
              Set(value As Long)
                  _value = value
              End Set
          End Property
          Private _editorService As IWindowsFormsEditorService
      
          Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
              Dim v As Long = 0
              For Each item As String In Me.CheckedListBox1.CheckedItems
                  v = (v Or [Enum].Parse(GetType(T), item))
              Next
              _value = v
              Me._editorService.CloseDropDown()
          End Sub
      End Class
      
    2. 以上是没有 designer.vb 部分的类代码。重要的部分是:具有valueeditorService参数的构造函数,使用当前选定的值(在构造函数中)填充控件并实现“commit action”,其中Value属性为控件更新(取决于所做的选择),最后调用Me._editorService.CloseDropDown()

      1. 定义一个通用的UITypeEditor。

        Imports System.ComponentModel
        Imports System.Drawing.Design
        Imports System.Windows.Forms.Design
        
        Public Class EnumEditor(Of T As Structure)
            Inherits UITypeEditor
        
            Public Overrides Function GetEditStyle(context As ITypeDescriptorContext) As UITypeEditorEditStyle
                Return UITypeEditorEditStyle.DropDown
            End Function
            Public Overrides ReadOnly Property IsDropDownResizable() As Boolean
                Get
                    Return True
                End Get
            End Property
            Public Overrides Function EditValue(context As ITypeDescriptorContext, provider As IServiceProvider, value As Object) As Object
                Dim svc As IWindowsFormsEditorService = DirectCast(provider.GetService(GetType(IWindowsFormsEditorService)), IWindowsFormsEditorService)
                Dim items As Long = CLng(value)
                If svc IsNot Nothing Then
                    Dim c As New EnumEditorControl(Of T)(value, svc)
        
                    svc.DropDownControl(c)
        
                    value = c.Value
                End If
        
                Return CType(value, T)
            End Function
        End Class
        
      2. 这里的核心部分是EditValue的覆盖,其中我们使用svc.DropDownControl(c)创建并显示了编辑器控件的实例(参见#3)。最后,从我们的控件属性中检索所选值并返回它。

        1. 定义一个通用 TypeConverter ,用于将实际值转换为要在属性资源管理器中使用的字符串表示形式。     导入System.ComponentModel     导入System.Drawing.Design     导入System.Text     导入System.Windows.Forms.Design

          Class EnumConverter(Of T As Structure)
              Inherits TypeConverter
          
              Public Overrides Function CanConvertTo(context As ITypeDescriptorContext, destinationType As Type) As Boolean
                  Return destinationType = GetType(String) OrElse MyBase.CanConvertTo(context, destinationType)
              End Function
              Public Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As System.Globalization.CultureInfo, value As Object, destinationType As Type) As Object
                  If destinationType = GetType(String) Then
                      Dim items As Integer = CLng(value)
          
                      If items = 0 Then
                          Return ""
                      End If
          
                      Dim values As New List(Of String)
          
                      For Each item As Integer In [Enum].GetValues(GetType(T))
                          If (items And item) = item Then
                              values.Add([Enum].GetName(GetType(T), item))
                          End If
                      Next
                      Return String.Join(", ", values)
                  End If
          
                  Return MyBase.ConvertTo(context, culture, value, destinationType)
              End Function
          End Class
          
        2. 通过将以下属性添加到已定义的属性(请参阅#2)将所有内容绑定在一起。

          <Browsable(True),
          DefaultValue(MyFlags.Flag1),
          Editor(GetType(EnumEditor(Of MyFlags)), 
          GetType(System.Drawing.Design.UITypeEditor)),
          TypeConverter(GetType(EnumConverter(Of MyFlags)))
          >
          Public Property MyProperty() As MyFlags
              ...
          End Property
          
        3. 注意:参考问题中提到的“零值”:此解决方案不考虑这一点,但可以轻松修改它。