Imports System.Web.UI Imports System.Collections.Specialized Imports System.Reflection Public Class DropDownList Inherits Control ' ** IPostBackDataHandler is used, as this Control must both process data ' ** And handle client-side events Implements IPostBackDataHandler ' ** This is our custom Event Public Event SelectedIndexChange As DropDownListEventHandler ' ** This is a Sub which raises the custom "SelectedIndexChange" Event, ' ** and can be overridden by implementations that inherit this class Protected Overridable Sub OnSelectedIndexChange(ByVal sender As Object, ByVal e As DropDownListEventArgs) RaiseEvent SelectedIndexChange(sender, e) End Sub ' ** This property can be any object which implements IEnumerable ' ** It is used to populate the ListBox Private _DataSource As Object Public Property DataSource() As Object Get Return _DataSource End Get Set(ByVal Value As Object) Dim objType As System.Type objType = Value.GetType() If objType.IsArray Then _DataSource = Value Else objType = Value.GetType().GetInterface("IEnumerable", True) If Not IsNothing(objType) Then _DataSource = Value Else Throw (New Exception("Data Source must implement IEnumerable")) End If End If End Set End Property ' ** This is the name of the property or field of the DataSource ' ** used to populate the "Text" attribute of the options Private _DisplayMember As String = "" Public Property DisplayMember() As String Get Return _DisplayMember End Get Set(ByVal Value As String) _DisplayMember = Value End Set End Property ' ** This is the name of the property or field of the DataSource ' ** used to populate the "Value" attribute of the options Private _ValueMember As String = "" Public Property ValueMember() As String Get Return _ValueMember End Get Set(ByVal Value As String) _ValueMember = Value End Set End Property ' ** Returns the "Name" attribute of the ListBox Public ReadOnly Property Name() As String Get Return UniqueID End Get End Property ' ** The CSS Class Name for this ListBox, if any ' ** Initialized to "" Private _CssClass As String = "" Public Property CssClass() As String Get Return _CssClass End Get Set(ByVal Value As String) _CssClass = Value End Set End Property ' ** Gets/Sets the "disabled" attribute of the ListBox Private _Disabled As Boolean = False Public Property Disabled() As Boolean Get Return _Disabled End Get Set(ByVal Value As Boolean) _Disabled = Value End Set End Property ' ** Number of options to display (number of rows visible when ListBox is closed) Private _Size As Integer = 1 Public Property Size() As Integer Get Return _Size End Get Set(ByVal Value As Integer) If Value > 0 Then _Size = Value End Set End Property ' ** Used to programmatically access the SelectedIndex property of the ListBox ' ** The Private property is used to persist the SelectedIndex in ViewState ' ** The Public Readonly property returns the value of the Private property Private Property _SelectedIndex() As Integer Get If Not IsNothing(ViewState("SelectedIndex")) Then Return CType(ViewState("SelectedIndex"), Integer) End If Return -1 End Get Set(ByVal Value As Integer) ViewState("SelectedIndex") = Value End Set End Property Public ReadOnly Property SelectedIndex() As Integer Get Return _SelectedIndex End Get End Property ' ** Used to programmatically access the "value" attribute of the Selected option ' ** The Private property is used to persist the value in ViewState ' ** The Public Readonly property returns the value of the Private property Private Property _Value() As String Get If Not IsNothing(ViewState("Value")) Then Return CType(ViewState("Value"), String) Return "" End Get Set(ByVal Value As String) ViewState("Value") = Value End Set End Property Public ReadOnly Property Value() As String Get Return _Value End Get End Property ' ** Used to store Display Property Info Private _DisplayIsProperty As Boolean = False ' ** Is DisplayMember a property? Private _ValueIsProperty As Boolean = False ' ** Is ValueMember a property? Private _DisplayFound As Boolean = False ' ** Does the DisplayMember exist? Private _ValueFound As Boolean = False ' ** does the ValueMember exist? Private _MemberIsObject As Boolean = True ' ** If an array, are its members primitives? ' ** Identify DisplayMember and ValueMember of DataSource ' ** Since this is an Object, we must use Reflection ' ** to find the fields or properties ' ** which will be used in our options list Private Function ConfirmProperties() As Type Dim objEnumerator As IEnumerator Dim objType As Type If Not IsNothing(_DataSource) Then objType = _DataSource.GetType() If objType.IsArray Then objEnumerator = CType(_DataSource, Array).GetEnumerator() ' ** If it is an Array, we need to determine whether it is an Array of ' ** Value Types (String, Integer, etc) If (CType(DataSource, Array).GetValue(0).GetType.IsValueType) Or (CType(DataSource, Array).GetValue(0).GetType.FullName.ToLower = "system.string") Then _MemberIsObject = False End If ElseIf IsNothing(_DataSource.GetType.GetInterface("ienumerable", True)) Then Throw New Exception("DataSource must be an Array or a Collection") Else objEnumerator = CType(_DataSource, CollectionBase).GetEnumerator End If ' ** Get the Type of the objects in the Array/Collection If Not objEnumerator.MoveNext() Then Throw New Exception("No Objects in DataSource") End If objType = objEnumerator.Current.GetType() ' ** If _DisplayFound is True, this Function has already run. ' ** There is therefore no need to reset these properties If Not _DisplayFound Then ' ** If it is not a primitive Data Type in an Array, check ' ** for the existence of properties or fields matching ' DisplayMember and ValueMember If _MemberIsObject Then ' ** Check _DisplayMember and _ValueMember to see if they are fields If Not IsNothing(objType.GetField(_DisplayMember)) Then _DisplayFound = True End If If Not IsNothing(objType.GetField(_ValueMember)) Then _ValueFound = True End If ' ** Check _DisplayMember and _ValueMember to see if they are properties If Not _DisplayFound Then If Not IsNothing(objType.GetProperty(_DisplayMember)) Then _DisplayFound = True _DisplayIsProperty = True End If End If If Not _ValueFound Then If Not IsNothing(objType.GetProperty(_ValueMember)) Then _ValueFound = True _ValueIsProperty = True End If End If Else _DisplayFound = True _ValueFound = True End If ' ** If _DisplayMember or _ValueMember are not fields OR properties, ' ** Throw Exception If Not _ValueFound Then Throw New Exception("ValueMember not found in DataSource") End If If Not _DisplayFound Then Throw New Exception("DisplayMember not found in DataSource") End If End If Else Throw New Exception("DataSource Not Defined") End If Return objType End Function ' ** Renders the HTML to the Page Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Dim intCt As Integer Dim objType As Type ' ** Used to hold Type Info Dim objEnumerator As IEnumerator ' ** Used to loop through DataSource ' ** Render "" tag writer.RenderEndTag() End Sub #Region "IPostBackDataHandler" Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData ' ** If the current Value is different from the posted value, a change has occurred If _Value <> postCollection(postDataKey) Then _Value = postCollection(postDataKey) ' ** Raise Event Return True End If ' ** Don't raise event Return False End Function Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent Dim objEnumerator As IEnumerator Dim objType As Type Dim strValue As String Dim intCt As Integer ' ** We need to get the Properties of the DataSource to find ' ** the SelectedIndex of the ListBox, which corresponds to the ' ** ordinal position in the DataSource of the Value If _DataSource.GetType().IsArray Then objEnumerator = CType(_DataSource, Array).GetEnumerator() Else objEnumerator = CType(_DataSource, CollectionBase).GetEnumerator End If objType = ConfirmProperties() ' ** Loop through DataSource to find the SelectedIndex intCt = 0 Do While objEnumerator.MoveNext() If Not _MemberIsObject Then strValue = objEnumerator.Current.ToString ElseIf _ValueIsProperty Then strValue = objType.GetProperty(_ValueMember).GetValue(objEnumerator.Current, Nothing).ToString Else strValue = objType.GetField(_ValueMember).GetValue(objEnumerator.Current).ToString End If If strValue = _Value Then _SelectedIndex = intCt Exit Do End If intCt += 1 Loop ' Raise OnSelectedIndexChange Event OnSelectedIndexChange(Me, New DropDownListEventArgs(_SelectedIndex, _Value)) End Sub #End Region ' Methods required by IPostBackDataHandler End Class ' ** Custom Event Class for dropDownList Public Class DropDownListEventArgs Inherits EventArgs Private _SelectedIndex As Integer Public ReadOnly Property SelectedIndex() As Integer Get Return _SelectedIndex End Get End Property Private _Value As String Public ReadOnly Property Value() As String Get Return _Value End Get End Property ' ** All public Properties of an Event Class should be read only. ' ** These properties are set by the New Constructor Public Sub New(ByVal p_intSelectedIndex As Integer, ByVal p_strValue As String) _SelectedIndex = p_intSelectedIndex _Value = p_strValue End Sub End Class ' ** Event Delegate for DropDownListEventArgs Public Delegate Sub DropDownListEventHandler(ByVal sender As Object, ByVal e As DropDownListEventArgs)