我有一个可能在.NET Forms控件中显示的数据类型的枚举,我想为控件的使用者提供一个接口来过滤某些类型(设置一些标志)。一个位字段似乎是这样做的合理方式,不幸的是,枚举从0开始而不是1(0,1,2,4,8,...)并且无法更改。
如何公开这组标志,以便可以通过编程方式或通过Visual Studio设计器轻松配置?
答案 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来查看控件,编辑器和转换器中发生的情况。
基本步骤是:
UITypeEditor
课程(此处: EnumEditor );这是使用Editor
属性连接到属性。它还创建和管理 EnumEditorControl 的实例。TypeConverter
课程(此处: EnumConverter );这也使用属性(TypeConverter
)连接到属性,并处理将值转换为字符串以在属性网格中显示。现在进行相关代码片段的演练:
使用Enum
属性定义Flags
。
<Flags>
Public Enum MyFlags
Flag1 = 2 ^ 0
Flag2 = 2 ^ 1
Flag3 = 2 ^ 2
End Enum
定义自定义控件的属性。
Public Property MyProperty() As MyFlags
...
End Property
一旦我们创建了我们需要的其他组件,该属性稍后将添加属性(参见#6)。
创建用于编辑的所需控件 属性。
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
以上是没有 designer.vb 部分的类代码。重要的部分是:具有value
和editorService
参数的构造函数,使用当前选定的值(在构造函数中)填充控件并实现“commit action”,其中Value
属性为控件更新(取决于所做的选择),最后调用Me._editorService.CloseDropDown()
。
定义一个通用的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
这里的核心部分是EditValue
的覆盖,其中我们使用svc.DropDownControl(c)
创建并显示了编辑器控件的实例(参见#3)。最后,从我们的控件属性中检索所选值并返回它。
定义一个通用 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)将所有内容绑定在一起。
<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
注意:参考问题中提到的“零值”:此解决方案不考虑这一点,但可以轻松修改它。