Предоставить коллекцию перечислений (флагов) в конструкторе Visual Studio - PullRequest
3 голосов
/ 07 мая 2009

У меня есть перечисление типов данных, которые могут отображаться в элементе управления .NET Forms, и я хочу предоставить потребителям элемента управления интерфейс для фильтрации некоторых типов (установить некоторые флаги). Битовое поле кажется логичным способом сделать это, к сожалению, enum начинается с 0, а не 1 (0, 1, 2, 4, 8, ...) и не может быть изменен.

Как я могу предоставить этот набор флагов, чтобы его можно было легко настроить программно или с помощью дизайнера Visual Studio?

Ответы [ 2 ]

4 голосов
/ 07 мая 2009

Для выполнения работы вам нужно написать UITypeEditor и связать его со свойством через [EditorAttribute].

edit теперь с примером - я боюсь, довольно длинным - но большая часть кода, к счастью, может быть разделена между типами.

Вы не можете использовать одно составное значение перечисления из-за нуля - поэтому здесь я использую 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;
    }
}
0 голосов
/ 24 апреля 2015

У меня была та же проблема: элемент управления редактора работал, но значения не сохранялись. Основываясь на ответе Марка и некоторых дальнейших исследованиях, я его запустил. Хотя мне нужна была эта функция для редактирования моих собственных элементов управления в DesignTime, я протестировал ее с помощью демонстрационного проекта с элементом управления PropertyGrid после прочтения следующей цитаты из Microsoft :

При разработке пользовательского UITypeEditor рекомендуется установите номер сборки, чтобы увеличивать с каждой сборкой. Это мешает старые кэшированные версии вашего UITypeEditor, созданные в среда проектирования.

Я думаю, что это была проблема, которая вызывала проблемы при попытке реализовать решение Марка. Тестирование в отдельном проекте также полезно, потому что вы можете использовать пошаговую отладку, чтобы увидеть, что происходит в элементе управления, редакторе и конвертере.

Основные шаги:

  • Создайте тип (здесь: 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
    

У этого свойства позже будут добавлены атрибуты, как только мы создадим другие необходимые нам компоненты (см. # 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
    

Выше приведен код класса без designer.vb частей. Важными частями являются: конструктор с параметрами value и editorService, заполнение элемента управления текущими выбранными значениями (в конструкторе) и реализация «действия коммита», когда свойство элемента управления 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
    

Основной частью здесь является переопределение EditValue, когда экземпляр нашего элемента управления редактора (см. # 3) создается и отображается с использованием svc.DropDownControl(c). И, наконец, получение выбранного значения из свойства нашего элемента управления и его возврат.

  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
    

Примечание. Ссылаясь на упомянутое в вопросе «нулевое значение»: это решение не учитывает это, но его можно легко изменить для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...