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.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListBox
Me.Button1 = New System.Windows.Forms.Button
Me.TextBox1 = New System.Windows.Forms.TextBox
Me.TextBox2 = New System.Windows.Forms.TextBox
Me.ComboBox2 = New System.Windows.Forms.ComboBox
Me.Label1 = New System.Windows.Forms.Label
Me.Label3 = New System.Windows.Forms.Label
Me.Label4 = New System.Windows.Forms.Label
Me.Label6 = New System.Windows.Forms.Label
Me.SuspendLayout()
'
'ListBox1
'
Me.ListBox1.FormattingEnabled = True
Me.ListBox1.Location = New System.Drawing.Point(32, 26)
Me.ListBox1.Name = "ListBox1"
Me.ListBox1.Size = New System.Drawing.Size(175, 95)
Me.ListBox1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(514, 120)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(75, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Save Part"
Me.Button1.UseVisualStyleBackColor = True
'
'TextBox1
'
Me.TextBox1.Location = New System.Drawing.Point(479, 42)
Me.TextBox1.Name = "TextBox1"
Me.TextBox1.Size = New System.Drawing.Size(144, 20)
Me.TextBox1.TabIndex = 2
Me.TextBox1.Text = "Enter a name"
'
'TextBox2
'
Me.TextBox2.Location = New System.Drawing.Point(479, 82)
Me.TextBox2.Name = "TextBox2"
Me.TextBox2.Size = New System.Drawing.Size(144, 20)
Me.TextBox2.TabIndex = 3
Me.TextBox2.Text = "8434"
'
'ComboBox2
'
Me.ComboBox2.FormattingEnabled = True
Me.ComboBox2.Location = New System.Drawing.Point(32, 177)
Me.ComboBox2.Name = "ComboBox2"
Me.ComboBox2.Size = New System.Drawing.Size(234, 21)
Me.ComboBox2.TabIndex = 5
'
'Label1
'
Me.Label1.AutoSize = True
Me.Label1.Location = New System.Drawing.Point(215, 225)
Me.Label1.Name = "Label1"
Me.Label1.Size = New System.Drawing.Size(39, 13)
Me.Label1.TabIndex = 6
Me.Label1.Text = "Label1"
'
'Label3
'
Me.Label3.AutoSize = True
Me.Label3.Location = New System.Drawing.Point(215, 257)
Me.Label3.Name = "Label3"
Me.Label3.Size = New System.Drawing.Size(39, 13)
Me.Label3.TabIndex = 8
Me.Label3.Text = "Label3"
'
'Label4
'
Me.Label4.AutoSize = True
Me.Label4.Font = New System.Drawing.Font("Microsoft Sans Serif",
8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
CType(0, Byte))
Me.Label4.Location = New System.Drawing.Point(56, 225)
Me.Label4.Name = "Label4"
Me.Label4.Size = New System.Drawing.Size(138, 13)
Me.Label4.TabIndex = 9
Me.Label4.Text = "BindingSource Position"
'
'Label6
'
Me.Label6.AutoSize = True
Me.Label6.Font = New System.Drawing.Font("Microsoft Sans Serif",
8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point,
CType(0, Byte))
Me.Label6.Location = New System.Drawing.Point(56, 257)
Me.Label6.Name = "Label6"
Me.Label6.Size = New System.Drawing.Size(96, 13)
Me.Label6.TabIndex = 11
Me.Label6.Text = "Get Text Result"
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(781, 351)
Me.Controls.Add(Me.Label6)
Me.Controls.Add(Me.Label4)
Me.Controls.Add(Me.Label3)
Me.Controls.Add(Me.Label1)
Me.Controls.Add(Me.ComboBox2)
Me.Controls.Add(Me.TextBox2)
Me.Controls.Add(Me.TextBox1)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ListBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
Friend WithEvents TextBox2 As System.Windows.Forms.TextBox
Friend WithEvents ComboBox2 As System.Windows.Forms.ComboBox
Friend WithEvents Label1 As System.Windows.Forms.Label
Friend WithEvents Label3 As System.Windows.Forms.Label
Friend WithEvents Label4 As System.Windows.Forms.Label
Friend WithEvents Label6 As System.Windows.Forms.Label
End Class
John
JT - 31 May 2006 05:28 GMT
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.

Signature
John
> 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).
[quoted text clipped - 296 lines]
> Me.Label3.Text = "Label3"
> '
Linda Liu [MSFT] - 31 May 2006 06:48 GMT
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.
====================================================
JT - 31 May 2006 07:36 GMT
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!

Signature
John
> Hi John,
>
[quoted text clipped - 62 lines]
> from your issue.
> ====================================================
Linda Liu [MSFT] - 31 May 2006 09:11 GMT
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.
====================================================