Examining Property Attributes in Code


Before getting into examining property attributes in code, you need to be aware that you cannot set Option Strict to On.

Caution

To elaborate, you cannot set Option Strict to On in the code module where you process the property attributes. This is because the type of reflection you are performing requires late binding. In general, this is not a good practice, but it is acceptable in this situation.

Now, having said that, let's start coding. Go into the form code module and import the System.Reflection namespace. Next, add a module-level variable for the ComputerListMgr as follows:

 Private mobjCLMgr As ComputerListMgr 

Add the code in Listing 10-4 to the frmList class.

Listing 10-4: The btnComputers_Click Method

start example
 Private Sub btnComputers_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnComputers.Click      Dim t As Type = GetType(ComputerList)      mobjCLMgr = New ComputerListMgr()      mobjCLMgr.Add(New ComputerList("Lightning", "P4", 1.4, 699.0, "Dell"))      mobjCLMgr.Add(New ComputerList("Thunder", "P4", 1.7, 799.0, "Dell"))      LoadList(t, CType(mobjCLMgr, CollectionBase)) End Sub 
end example

The first line of this code gets the type of the ComputerList class and stores it in a type variable. Next you instantiate the computer list manager and add two items to the collection. Finally you call the LoadList method (which you will code next) and pass in your object type and your collection. Notice, however, that you are passing your manager in as a CollectionBase object. In a real application, you would probably want to overload this method to accept virtually any type of collection. The reason why you are passing in generic values is so that the LoadList method remains as flexible as possible. Listing 10-5 contains the code for the LoadList method. Do not panic! You will get an explanation of everything line by line after the listing.

Listing 10-5: The LoadList Method

start example
 Private Sub LoadList(ByVal t As Type, ByRef col As CollectionBase)      Dim p As PropertyInfo()      Dim i, j As Integer      Dim SortedL As New Collections.SortedList()      lvwList.Clear      p = t.GetProperties(BindingFlags.Public Or BindingFlags.Instance)      For i = 0 To p.Length - 1           Dim a As Object()           a = p(i).GetCustomAttributes(False)           If a.Length > 0 Then                For j = 0 To a.Length - 1                     If a(j).GetType Is GetType(ListAttribute) Then                          Dim la As ListAttribute = CType(a(j), ListAttribute)                          SortedL.Add(la.Column, p(i))                     End If                Next           End If      Next      For i = 0 To SortedL.Count - 1           Dim pi As PropertyInfo = CType(SortedL.Item(i), PropertyInfo)           Dim a As Object = pi.GetCustomAttributes(False)           For j = 0 To a.Length - 1                If a(j).GetType Is GetType(ListAttribute) Then                     Dim la As ListAttribute = CType(a(j), ListAttribute)                     lvwList.Columns.Add(la.Heading, _                     lvwList.Width / SortedL.Count - 2, _                     HorizontalAlignment.Left)                     Exit For                End If           Next      Next      Dim obj As Object      Dim myObject() As Object      For Each obj In col           Dim cl As Object = Convert.ChangeType(obj, t)           Dim k As Integer           Dim lst As New ListViewItem()           For i = 0 To SortedL.Count - 1                Dim pr As PropertyInfo = CType(SortedL.Item(i), PropertyInfo)                Dim strValue As String = ""                strValue = Convert.ToString(t.InvokeMember(pr.Name, _                BindingFlags.GetProperty, Nothing, cl, myObject))                If i = 0 Then                     lst.SubItems(i).Text = strValue                Else                     lst.SubItems.Add(strValue)                End If           Next           lvwList.Items.Add(lst)      Next End Sub 
end example

So now that you think you may be lost, let's try to straighten everything out and explain what is going on here. The first line declares an array of PropertyInfo variables. The PropertyInfo type holds information about—you guessed it—properties. The i and j variables are just counter variables. The SortedL variable stores the properties in the order you have specified they be displayed in (by way of your attribute settings in the ComputerList class). You will see this in action in a minute. Then you clear the listview of all of its current contents—headers and all:

 Dim p As PropertyInfo() Dim i, j As Integer Dim SortedL As New Collections.SortedList() lvwList.Clear 

This next line calls the GetProperties method on your type variable. So this line reads, "Get all of the properties of the type (in this case, the ComputerList class) that are public or instance properties." This method returns an array of PropertyInfo types:

 p = t.GetProperties(BindingFlags.Public Or BindingFlags.Instance) 

The next block of code continues your process of discovering information about the properties. First, you start by looping through the array of PropertyInfo values:

 For i = 0 To p.Length - 1 

The variable a is an object array to hold all of the custom attributes on the specific property:

 Dim a As Object() 

The GetCustomAttributes returns an object array because there may be several types of attributes associated with the property you are examining. The False parameter indicates that you do not want to look at any other property values in the inheritance chain for this class:

 a = p(i).GetCustomAttributes(False) 

Now you check the length of the array to see if there were any custom attributes associated with the property. Remember, for your class there are only three: the Cname, Proc, and Speed properties:

 If a.Length > 0 Then 

If it does find at least one custom attribute, you loop through the array of custom attributes:

 For j = 0 To a.Length - 1 

Here you check the type of custom attribute. This is the only known type in the entire method:

 If a(j).GetType Is GetType(ListAttribute) Then 

If the custom attribute is of type ListAttribute, then you convert that custom attribute into a value that you can manipulate easily by performing a ctype on it:

 Dim la As ListAttribute = CType(a(j), ListAttribute) 

Finally, you add the column number as the key in the sorted list, and you add the PropertyInfo variable as the object in the sorted list so you can reference it later:

 SortedL.Add(la.Column, p(i)) 

The next block of code adds the column headers to the listview. You start by looping through the sorted list collection and retrieving the PropertyInfo objects. Then you get the custom attributes of the property. Next, you again loop through the custom attributes looking for the ListAttribute. When you find it, you convert it into a ListAttribute variable and extract the heading name. When this block of code finishes executing, the column headers will have been added to the listview:

 For i = 0 To SortedL.Count - 1      Dim pi As PropertyInfo = CType(SortedL.Item(i), PropertyInfo)      Dim a As Object = pi.GetCustomAttributes(False)      For j = 0 To a.Length - 1           If a(j).GetType Is GetType(ListAttribute) Then                Dim la As ListAttribute = CType(a(j), ListAttribute)                lvwList.Columns.Add(la.Heading, _                lvwList.Width / SortedL.Count - 2, _                HorizontalAlignment.Left)                Exit For           End If      Next Next 

The obj variable helps you iterate through the ComputerListMgr collection. Because you only know that this is a collection, you cannot use a For Next loop to iterate through the collection. You can only use the For Each enumeration. And because you do not know what type of object is returned to you by the collection (remember, this is a wholly generic routine, so you cannot declare a variable of type ComputerList anywhere), you need to use an object variable. The myObject object array is used as a parameter to the InvokeMethod call. It is a throwaway variable:

 Dim obj As Object Dim myObject() As Object 

Finally, you get to the block of code that adds the values from the collection into the listview. Before you start examining this block of code, think about what it is doing. You are taking a collection that you know nothing about, that stores objects you know nothing about, and that has properties you know nothing about and extracting that data and placing it in a listview! This block of code, in a nut-shell, shows exactly how powerful the .NET Framework can be when used to its fullest potential.

Let's now look at what is happening here. The For Each statement, as mentioned earlier, is the only way to iterate through your collection object:

 For Each obj In col 

The cl variable is an object that you are converting to the type you have passed in to the method—in this case, the ComputerList type. You do this using the ChangeType method of the Convert class. This is an example of late binding and the chief reason you cannot use Option Strict On in this code module:

 Dim cl As Object = Convert.ChangeType(obj, t) 

The k variable is just a counter variable, and lst is the listviewitem you will be adding to the listview:

 Dim k As Integer Dim lst As New ListViewItem() 

Next you loop through the sorted list collection (yet again) to get the properties for which you need to retrieve the values:

 For i = 0 To SortedL.Count - 1 

This line retrieves the PropertyInfo from the sorted list collection:

 Dim pr As PropertyInfo = CType(SortedL.Item(i), PropertyInfo) 

StrValue holds the value you retrieve from whatever property you are calling. It is initialized to an empty string because you may have a property that was not set and this would leave strValue with a value of nothing, which you absolutely do not want:

 Dim strValue As String = "" 

This next line is the workhorse of this method. This line says the following: "Call the method whose name is returned by PropertyInfo variable (pr). Look for this method in the class's collection of properties using the default binder (do not worry about what this is right now, for more information check the MSDN documentation). Call this method on the given object (cl) with the parameters given in myObject and store the return value in the string variable strValue." That was a handful to say the least. The myObject array would, if you were calling a method that required parameters to be passed to it, contain a list of values to pass in to the method:

 strValue = Convert.ToString(t.InvokeMember(pr.Name, _ BindingFlags.GetProperty, Nothing, cl, myObject)) 

If this is the first time through the loop, assign the value to item 0 of the subitems collection (because you already instantiated the lst variable previously); otherwise, add a new subitem to the listviewitem. Finally, add the listviewitem to the listview:

     If i = 0 Then          lst.SubItems(i).Text = strValue     Else          lst.SubItems.Add(strValue)     End If Next lvwList.Items.Add(lst) 

Now, if you have not done so yet, run the application and click the Computer List button. The result should look something like Figure 10-2.

click to expand
Figure 10-2: List of computers displayed by the LoadList method

Now, as a test, edit the ComputerList class and change the order you would like things to display on the screen (by changing the numeric value in the List Attribute tag) and run the application again. Pretty neat, huh?

Listing 10-6 contains the code for the BookList class and the BookListMgr class. They are the same as what you have just done, but they have different properties.

Listing 10-6: The BookList and BookListMgr Classes

start example
 Public Class BookList     Private mstrTitle As String     Private mstrAuthor As String     Private mdblPrice As Double     Private mstrPublisher As String     Public ReadOnly Property Price() As Double         Get             Return mdblPrice         End Get     End Property     Public ReadOnly Property Publisher() As String         Get             Return mstrPublisher         End Get     End Property     <List("Author", 1)> Public ReadOnly Property Author() As String         Get             Return mstrAuthor         End Get     End Property     <List("Book Title", 0)> Public ReadOnly Property Title() As String         Get             Return mstrTitle         End Get     End Property     Public Sub New(ByVal sTitle As String, ByVal sAuthor As String, _     ByVal dPrice As Double, ByVal sPub As String)         mstrTitle = sTitle         mstrAuthor = sAuthor         mdblPrice = dPrice         mstrPublisher = sPub     End Sub End Class Public Class BookListMgr     Inherits System.Collections.CollectionBase     Public Sub Add(ByVal obj As BookList)         list.Add(obj)     End Sub     Public Sub Remove(ByVal Index As Integer)         list.RemoveAt(Index)     End Sub     Public Function Item(ByVal Index As Integer) As BookList         Return CType(list.Item(Index), BookList)     End Function End Class 
end example

Next, add the following module-level declaration in frmList:

 Private mobjBKMgr As BookListMgr 

Finally, Listing 10-7 shows the code for the btnBooks_Click method.

Listing 10-7: The btnBooks_Click Method

start example
 Private Sub btnBooks_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles btnBooks.Click      Dim t As Type = GetType(BookList)      mobjBKMgr = New BookListMgr()      mobjBKMgr.Add(New BookList("Life With .NET", "Anonymous", 49.95, _      "Apress"))      mobjBKMgr.Add(New BookList("Life With Java", "Unknown", 19.95, _      "ABC Publishing"))      LoadList(t, CType(mobjBKMgr, CollectionBase)) End Sub 
end example

Now try running the application and clicking either button. Try changing the methods with which the custom attributes are associated. No matter what you do, this code will work.




Building Client/Server Applications with VB. NET(c) An Example-Driven Approach
Building Client/Server Applications Under VB .NET: An Example-Driven Approach
ISBN: 1590590708
EAN: 2147483647
Year: 2005
Pages: 148
Authors: Jeff Levinson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net