Groups | Blog | Home
all groups > dotnet windows forms databinding > may 2006 >

dotnet windows forms databinding : Reflecting on a Generic Type


JT
5/30/2006 2:36:02 PM
I am trying to set up a datagrid combobox column to get the display member
given the value member when the datasource is a BindingList(Of T).
I expanded on a MS example for the BindingList, and am attempting to use
reflection to get a property value from the instance that represents the type
'T' - in this case, an instance of the Part class. My complete code is
below. It fails with a TargetException in the GetText method when I actually
call 'propertyInfo.GetValue'.

Any help on what I am doing wrong would be awesome.

Thanks, John

Option Explicit On
Option Strict On
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Reflection
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Public Class Form1

Private randomNumber As New Random()
Private bs2 As BindingSource
Private strDisplay As String = "DisplayMember"
Private strValueMember As String = "ValueMember"
' Declare a new BindingListOfT with the Part business object.
'Private WithEvents listOfParts As GenericBindingList(Of
Generic2ValBusinessObject(Of Integer, String))
Private WithEvents listOfParts As BindingList(Of Part)
Dim properties As PropertyDescriptorCollection =
TypeDescriptor.GetProperties(listOfParts)

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load

InitializeListOfParts()
ListBox1.DataSource = listOfParts
ListBox1.DisplayMember = "PartName"
'
bs2 = New BindingSource(listOfParts, "")

With Me.ComboBox2
.DisplayMember = "PartName"
.ValueMember = "PartNumber"
.DataSource = bs2
End With

Me.Label1.Text = bs2.Position.ToString
'Me.Label3.Text = Me.GetText(bs2,
listOfParts(bs2.Position).ValueMember)
End Sub

Private Sub InitializeListOfParts()

listOfParts = New BindingList(Of Part)

With listOfParts
' Allow new parts to be added, but not removed once committed.

.AllowNew = True
.AllowRemove = False

' Raise ListChanged events when new parts are added.
.RaiseListChangedEvents = True

' Do not allow parts to be edited.
.AllowEdit = False

' Add a couple of parts to the list.

listOfParts.Add(New Part(1234, "Widget"))
listOfParts.Add(New Part(5647, "Gadget"))

End With

End Sub

' Create a new part from the text in the two text boxes.
Private Sub listOfParts_AddingNew(ByVal sender As Object, _
ByVal e As AddingNewEventArgs) Handles listOfParts.AddingNew
e.NewObject = New Part(Integer.Parse(TextBox2.Text), TextBox1.Text)

End Sub

' Add the new part unless the part number contains
' spaces. In that case cancel the add.
Private Sub button1_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles Button1.Click

Dim newPart As Part = listOfParts.AddNew()

If newPart.PartName.Contains(" ") Then
MessageBox.Show("Part names cannot contain spaces.")
listOfParts.CancelNew(listOfParts.IndexOf(newPart))
Else
TextBox2.Text = randomNumber.Next(9999).ToString()
TextBox1.Text = "Enter part name"
End If

End Sub

<STAThread()> _
Shared Sub Main()
Application.EnableVisualStyles()
Application.Run(New Form1())

End Sub

Private Sub ComboBox2_SelectedIndexChanged(ByVal sender As Object, ByVal
e As System.EventArgs) Handles ComboBox2.SelectedIndexChanged
Me.Label1.Text = bs2.Position.ToString
Me.Label3.Text = Me.GetText(bs2, listOfParts(bs2.Position).PartNumber)
End Sub

Private Function GetText(ByVal bs As BindingSource, ByVal ValueMember As
Object) As String

Dim strOut As String = String.Empty
Try

Dim genericType As Type = bs.DataSource.GetType 'BindingList(Of
Part)

Dim openGenericType As Type = genericType.GetGenericTypeDefinition

Dim typeParams() As Type = genericType.GetGenericArguments

'BindingList(Of T)
'Has only one parameter
Dim struc As Type = typeParams(0) 'Part type

Dim valProp As PropertyInfo = struc.GetProperty("DisplayText")

Dim args() As Object = {ValueMember, bs.DataSource}

'THE OFFENDING CODE
'THROWS TARGETEXCEPTION
Dim o As Object = valProp.GetValue(bs.DataSource, args)

If Not o Is Nothing Then
strOut = o.ToString
End If

Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try

Return strOut

End Function

End Class

' A simple business object for example purposes.
Public Class Part
Private name As String
Private number As Integer

Public Sub New()
End Sub

Public Sub New(ByVal numberForPart As Integer, ByVal nameForPart As
String)
PartName = nameForPart
PartNumber = numberForPart

End Sub

Public Property PartName() As String
Get
Return name
End Get
Set(ByVal value As String)
name = Value
End Set
End Property

Public Property PartNumber() As Integer
Get
Return number
End Get
Set(ByVal value As Integer)
number = Value
End Set
End Property

Public ReadOnly Property DisplayText(ByVal _PartNumber As Integer, ByVal
BL As BindingList(Of Part)) As String
Get

If BL IsNot Nothing Then

For n As Integer = 0 To BL.Count - 1

Dim Inst As Part = DirectCast(BL(n), Part)
If Inst.PartNumber = _PartNumber Then
Return Inst.PartName
End If
Next n
End If

Return String.Empty
End Get
End Property

End Class

-- <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form

'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub

'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer

'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
JT
5/30/2006 9:28:02 PM
Never mind - on further "reflection", I got it.

I changed

Dim o As Object = valProp.GetValue(bs.DataSource, args)
to
Dim o As Object = valProp.GetValue(Activator.CreateInstance(struc), args)

Works fine now.

--
John


[quoted text, click to view]
JT
5/30/2006 11:36:02 PM
Thanks Linda - once again. If you saw my second post, I did manage to
finally realize that the object I was placing as the first parameter of the
GetValue function was incorrect. I was using BindingSource.DataSource, which
translates to BindingList(Of Part). I changed this to an instance of Part by

1. Retrieving the type of T with
BindingSource.DataSource.GetType.GetGenericArguments
to retrieve the array of the generic type's parameters.
2. Knowing that BindingList(Of T) has only one parameter, I retrieve it's
type (T) with
Dim struc as Type = typeParams(0)
This gets me the Part type.
3. I then create an instance of the Part type with
Activator.CreateInstance(struc, args). This equates to your BusinessObj, and
is what I use as the Object parameter in the GetValue function.

I missed the error in the code for getting the label.Text. Thanks.

Thanks for your help. The support in the dotnet.framework newsgroup is
superb!


--
John


[quoted text, click to view]
v-lliu NO[at]SPAM online.microsoft.com
5/31/2006 12:00:00 AM
Hi John,

Yes, I saw your second post after I sent my first reply to you. I think
your solution is a good alternative and will benefit many other users.

If you have any other questions or concerns, please do not hesitate to
contact us. It is always our pleasure to be of assistance.

Have a nice day!


Sincerely,
Linda Liu
Microsoft Online Community Support

====================================================
When responding to posts,please "Reply to Group" via
your newsreader so that others may learn and benefit
from your issue.
====================================================
v-lliu NO[at]SPAM online.microsoft.com
5/31/2006 12:00:00 AM
Hi John,

Thank you for posting.

Since the DisplayText property is defined in the Part class, you should
access this property with an instance of Part class instead of with an
instance of BindingList.
Thus, a probable modification of the GetText() function in your program may
be like the following.

' add a parameter "businessObj" which refers to an instance of the Part
class
Private Function GetText(ByVal bs As BindingSource, ByVal businessObj As
Object, ByVal ValueMember As Object) As String

Dim strOut As String = String.Empty
Try

Dim genericType As Type = bs.DataSource.GetType 'BindingList(Of
Part)

Dim openGenericType As Type =
genericType.GetGenericTypeDefinition

Dim typeParams() As Type = genericType.GetGenericArguments

'BindingList(Of T) Has only one parameter
Dim struc As Type = typeParams(0) 'Part type
'Dim struc As Type = businessObj.GetType()
Dim valProp As PropertyInfo = struc.GetProperty("DisplayText")

Dim args() As Object = {ValueMember, bs.DataSource}

'this is the main modification. replace bs.DataSource with
businessObj
Dim o As Object = valProp.GetValue(businessObj, args)

If Not o Is Nothing Then
strOut = o.ToString
End If

Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try

Return strOut

End Function

And you should modify the sentence calling this method as the following.
Me.Label3.Text = Me.GetText(bs2, listOfParts(i),
listOfParts(bs2.Position).PartNumber)

Hope this is helpful to you.
If you have any concerns or need anything else, please don't hesitate to
let me know.


Sincerely,
Linda Liu
Microsoft Online Community Support

====================================================
When responding to posts,please "Reply to Group" via
your newsreader so that others may learn and benefit
from your issue.
====================================================
AddThis Social Bookmark Button