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.