Click here to Skip to main content
11,933,213 members (64,126 online)
Click here to Skip to main content
Add your own
alternative version


32 bookmarked

Handy Type Editors. Universal Dropdown Editor

, 5 May 2004
Rate this:
Please Sign up or sign in to vote.
Implementing a universal dropdown type editor.

Demo snapshot


In my previous article, Handy Type Editors. Filename Editor, I demonstrated the use of custom attributes for altering the way an editor works. Today, we'll go further and implement a type editor controlled by both attributes and members of an edited type. The purpose of the exercise is creating a dropdown editor suitable for most occasions. Instead of writing nearly the same code over and over again, breaking the main commandment of pragmatic programming (Thou shalt not duplicate thy code), it is sufficient to configure the class by adding one or two attributes.

Main Idea

As mentioned in the previous article, a dropdown editor is meant to display a control below the cell of the edited member. Our control will be a ListBox displaying contents of a collection in the class being edited. The target collection is specified by an attribute. We'll also provide a way to configure what member of an item is displayed, and how the value is determined.

Let's Code!


We need to provide a way to load our collection to a ListBox. ListControl has a built-in DataSource property which allows binding of an IList implementing object. However, there is a lot of collections out there that do not implement IList. We don't want to miss these either. This is why we'll write a small utility function which loads a collection to a ListBox. It will also act as a type validator (that is, an exception will be thrown if the user passes an invalid property).

Public Shared Sub FillListBoxFromCollection(ByVal lb As ListBox, _
            ByVal coll As ICollection)
    ' prevent flickers and slow downs by entering the mass update mode
    Dim item As Object
    For Each item in coll
End Sub

Essential Attributes

As mentioned earlier, custom attributes are used to configure the editor:

  • SourceCollectionAttribute. It holds the name and the reference to the type member (or property, to be exact) which serves as a source of ListBox data. Implementation:
    <Description("Service attribute to point to the source collection."), _
            AttributeUsage(AttributeTargets.All)> _
    Public Class SourceCollectionAttribute
        Inherits Attribute
        Private srcCollName As String
        Public ReadOnly Property CollectionName() As String
                Return Me.srcCollName
            End Get
        End Property
        Public ReadOnly Property Collection(ByVal instance As Object) _
                As ICollection
                Dim pdc As PropertyDescriptorCollection = _
                Dim pd As PropertyDescriptor
                For Each pd In pdc
                    If pd.Name = Me.srcCollName Then
                        Return pd.GetValue(instance)
                    End If
                Return Nothing
            End Get
        End Property
        Public Sub New(ByVal sourceCollectionPropertyName As String)
            Me.srcCollName = sourceCollectionPropertyName
        End Sub
    End Class
  • ValueMemberAttribute. It holds the name of the listed items' type member which is assigned to the edited value. For the sake of simplicity, if the attribute is not used, the entire selected object is returned. Pay attention to GetValue and SelectByValue functions: we cannot use ListControl.SelectedValue since we're not binding the data through ListControl.DataSource. Implementation:
    <AttributeUsage(AttributeTargets.All)> _
    Public Class ValueMemberAttribute
        Inherits Attribute
        Private valMemb As String
        Public ReadOnly Property ValuePropertyName() As String
                Return Me.valMemb
            End Get
        End Property
        Public Sub SelectByValue(ByVal lb As ListBox, ByVal val As Object)
            lb.SelectedItem = Nothing
            Dim item As Object
            For Each item In lb.Items
                If Me.GetValue(item) = val Then
                    lb.SelectedItem = item
                    Exit Sub
                End If
        End Sub
        Public Function GetValue(ByVal obj As Object) As Object
            If Me.valMemb = String.Empty Then Return obj
            Dim pi As System.Reflection.PropertyInfo = _
            If pi Is Nothing Then Return Nothing
            Return pi.GetValue(obj, Nothing)
        End Function
        Public Sub New(ByVal valueMemberPropertyName As String)
            Me.valMemb = valueMemberPropertyName
        End Sub
    End Class
  • DisplayMemberAttribute. It holds the name of the listed items' type member which is used to display items in the list. If omitted, the item's ToString method is used. All we must do is assign the string to the DisplayMember property of the ListBox we created. Implementation:
    <AttributeUsage(AttributeTargets.All)> _
    Public Class DisplayMemberAttribute
        Inherits Attribute
        Private dispMemb As String
        Public ReadOnly Property DisplayPropertyName() As String
                Return Me.dispMemb
            End Get
        End Property
        Public Sub New(ByVal displayMemberPropertyName As String)
            Me.dispMemb = displayMemberPropertyName
        End Sub
    End Class

Implementing the Editor

Now, we're ready to implement the editor itself. We'll set the style to DropDown:

Public Overloads Overrides Function GetEditStyle(ByVal context As _
                ITypeDescriptorContext) As UITypeEditorEditStyle
    If Not context Is Nothing AndAlso Not context.Instance Is Nothing Then
        Return UITypeEditorEditStyle.DropDown
    End If
    Return UITypeEditorEditStyle.None
End Function

EditValue and small service functions:

Private edSvc As IWindowsFormsEditorService
Private valMemb As ValueMemberAttribute

<RefreshProperties(RefreshProperties.All)> _
Public Overloads Overrides Function EditValue( _
            ByVal context As ITypeDescriptorContext, _
            ByVal provider As System.IServiceProvider, _
            ByVal value As [Object]) As [Object]
    If context Is Nothing OrElse provider Is Nothing _
            OrElse context.Instance Is Nothing Then
        Return MyBase.EditValue(provider, value)
    End If
    Dim att As SourceCollectionAttribute = _
            context.PropertyDescriptor.Attributes( _
    If att Is Nothing Then
        ' nothing we can do here. Let the default editor handle it
        Return MyBase.EditValue(provider, value)
    End If
    Me.edSvc = provider.GetService(GetType(IWindowsFormsEditorService))
    If Me.edSvc Is Nothing Then
        ' nothing we can do here either
        Return MyBase.EditValue(provider, value)
    End If

    ' prepare the listbox
    Dim lst As New ListBox
    Me.PrepareListBox(lst, att, context)
    If Me.valMemb Is Nothing Then
        lst.SelectedItem = value
        Me.valMemb.SelectByValue(lst, value)
    End If

    ' we're back
    If lst.SelectedItem Is Nothing Then
        value = Nothing ' nothing selected - nothing to return
    ElseIf Me.valMemb Is Nothing Then
        value = lst.SelectedItem ' return the item itself
    Else ' return a field from the selected item
        value = Me.valMemb.GetValue(lst.SelectedItem)
    End If
    Return value
End Function

Private Sub PrepareListBox(ByVal lst As ListBox, _
            ByVal att As SourceCollectionAttribute, _
            ByVal context As ITypeDescriptorContext)
    lst.IntegralHeight = True ' resize to avoid partial items
    Dim coll As ICollection = att.Collection(context.Instance)
    If lst.ItemHeight > 0 Then
        If Not coll Is Nothing AndAlso _
            lst.Height / lst.ItemHeight < coll.Count Then
            ' try to keep the listbox small but sufficient
            Dim adjHei As Integer = coll.Count * lst.ItemHeight
            If adjHei > 200 Then adjHei = 200
            lst.Height = adjHei
        End If
    Else ' safeguard, although it shouldn't happen
        lst.Height = 200
    End If
    lst.Sorted = True ' present in alphabetical order
    FillListBoxFromCollection(lst, coll)
    Me.AssignValueMember(lst, context.PropertyDescriptor)
    Me.AssignDisplayMember(lst, context.PropertyDescriptor)
    ' attach event handler
    AddHandler lst.SelectedIndexChanged, AddressOf Me.handleSelection
End Sub

Private Sub AssignValueMember(ByVal lc As ListControl, _
            ByVal pd As PropertyDescriptor)
    Me.valMemb = pd.Attributes(GetType(ValueMemberAttribute))
    If Me.valMemb Is Nothing Then Return
    ' maybe one day it'll work by itself...
    lc.ValueMember = Me.valMemb.ValuePropertyName
End Sub

Private Sub AssignDisplayMember(ByVal lc As ListControl, _
            ByVal pd As PropertyDescriptor)
    Dim att As DisplayMemberAttribute = pd.Attributes( _
    If att Is Nothing Then Return
    lc.DisplayMember = att.DisplayPropertyName
End Sub

Private Sub handleSelection(ByVal sender As Object, ByVal e As EventArgs)
    If Me.edSvc Is Nothing Then Return
    Me.edSvc.CloseDropDown() ' simply return to the EditValue procedure
End Sub

Ready to go.

Using the code

To use the class, bind it to your property like this:

<Editor(GetType(UniversalDropdownEditor), GetType(UITypeEditor)), _
    SourceCollection("MyMethods"), _
    ValueMember("Name"), DisplayMember("Name")> _
Property SelectedMethodName() As String
' ...
<Browsable(False)> _
Property MyMethods() As System.Reflection.MethodInfo()

Note that if you have a special property created to serve as a source collection, you might want to conceal it by setting the Browsable attribute to False.

That's it. Enjoy!


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

You may also be interested in...

Comments and Discussions

GeneralVS crashes: DLL version issue Pin
Jim Carnicelli4-Apr-06 12:29
memberJim Carnicelli4-Apr-06 12:29 
AnswerRe: VS crashes: DLL version issue Pin
VadimBerman4-Apr-06 13:49
memberVadimBerman4-Apr-06 13:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.151126.1 | Last Updated 6 May 2004
Article Copyright 2004 by VadimBerman
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid