How to get a second ValueMember for a databound custom combobox - vb.net

I have a simple custom combobox that just has an added property "ValueMember2".
I have the combobox datasource added to a dataset
I can set the property in code just as I would the normal property "ValueMember"
cboRadioType.ValueMember = "ID"
cboRadioType.ValueMember2 = "FREQMIN"
cboRadioType.DisplayMember = "MODEL"
The normal ValueMember property will return the ID from the datasource
The new ValueMember2 property just returns the string "FREQMIN"
My custom combobox code is as followed:
Imports System.ComponentModel
Public Class NewCBO
Inherits ComboBox
Dim vm2 As String
<Description("Gets/Sets the ValueMember2 of Control")>
Property ValueMember2() As String
Get
ValueMember2 = vm2
End Get
Set(ByVal Value As String)
vm2 = Value
End Set
End Property
End Class
What do I need to change my custom property to make it return the value from the connected datasource? All searches have turned up is how to "display a ValueMember"

Here is a custom ComboBox class that includes a fairly rigorous implementation of ValueMember2 and SelectedValue2 based on the existing implementations of ValueMember and SelectedValue:
Imports System.ComponentModel
Public Class ComboBoxEx
Inherits ComboBox
Private _valueMember2 As BindingMemberInfo
Public Property ValueMember2 As String
Get
Return _valueMember2.BindingMember
End Get
Set(value As String)
If value Is Nothing Then
value = String.Empty
End If
Dim bindingMemberInfo As New BindingMemberInfo(value)
If bindingMemberInfo.Equals(_valueMember2) Then
Return
End If
'This part is implemented in ValueMember but cannot be here because SetDataConnection is Private.
'It may be possible to provide our own implement of SetDataConnection but that is not done here.
'If DisplayMember.Length = 0 Then
' SetDataConnection(DataSource, bindingMemberInfo, False)
'End If
'This part is implemented in ValueMember but cannot be here because BindingMemberInfoInDataManager is Private.
'It may be possible to provide our own implement of BindingMemberInfoInDataManager but that is not done here.
'If DataManager IsNot Nothing AndAlso
' value <> String.Empty AndAlso
' Not BindingMemberInfoInDataManager(bindingMemberInfo) Then
' Throw New ArgumentException("...", NameOf(value))
'End If
_valueMember2 = bindingMemberInfo
'A rigorous implementation should also include implementation of corresponding events.
'OnValueMember2Changed(EventArgs.Empty)
'OnSelectedValue2Changed(EventArgs.Empty)
End Set
End Property
Public Property SelectedValue2 As Object
Get
Return If(SelectedIndex <> -1 AndAlso DataManager IsNot Nothing,
FilterItemOnProperty(DataManager.List(SelectedIndex), _valueMember2.BindingField),
Nothing)
End Get
Set(value As Object)
If DataManager Is Nothing Then
Return
End If
Dim bindingField = _valueMember2.BindingField
If String.IsNullOrEmpty(bindingField) Then
Throw New InvalidOperationException("...")
End If
'This part is implemented in ValueMember but cannot be here because DataManager.Find is Private.
'SelectedIndex = DataManager.Find(DataManager.GetItemProperties().Find(bindingField, True), value, True)
'The following replaces the call to DataManager.Find above.
If value Is Nothing Then
Throw New ArgumentNullException(NameOf(value))
End If
Dim newSelectedIndex = -1
Dim [property] = DataManager.GetItemProperties().Find(bindingField, True)
If [property] IsNot Nothing Then
Dim list=DataManager.List
Dim bindingList = TryCast(list, IBindingList)
If bindingList?.SupportsSearching Then
newSelectedIndex = bindingList.Find([property], value)
Else
For i = 0 To list.Count - 1
Dim obj = list(i)
If value.Equals(obj) Then
newSelectedIndex = i
Exit For
End If
Next
End If
End If
SelectedIndex = newSelectedIndex
End Set
End Property
End Class
There are some notes in there that you ought to read. Also note that ValueMember has design-time support that ValueMember2 lacks. It may not be difficult to use the same editor but I didn't look into that. It's also worth noting that ValueMember and SelectedValue are implemented in ListControl and thus are inherited by ListBox too. You'd have to implement this code separately in a custom ListBox.
I tested that control with the following code:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim table As New DataTable
With table.Columns
.Add("Id", GetType(Integer))
.Add("Name", GetType(String))
.Add("DateOfBirth", GetType(Date))
End With
With table.Rows
.Add(1, "Peter", #1/1/2001#)
.Add(2, "Paul", #2/2/2002#)
.Add(3, "Mary", #3/3/2003#)
End With
BindingSource1.DataSource = table
With ComboBoxEx1
.DisplayMember = "Name"
.ValueMember = "Id"
.ValueMember2 = "DateOfBirth"
.DataSource = BindingSource1
End With
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
MessageBox.Show($"ID: {ComboBoxEx1.SelectedValue}; Date of Birth: {ComboBoxEx1.SelectedValue2}", ComboBoxEx1.Text)
End Sub
End Class
It worked exactly as expected. The same code should also work for other data sources, e.g. a List(Of T), although I didn't do any additional testing.

Just to post what I have found out. Not sure if it the correct way but it works.
I added the following to my custom control:
Dim mds As New DataTable
then in the property I changed to this:
<Description("Gets/Sets the ValueMember2 of Control")>
Property ValueMember2() As String
Get
mds = Me.DataSource
ValueMember2 = mds.Rows(Me.SelectedValue - 1).Item(vm2)
End Get
Set(ByVal Value As String)
vm2 = Value
End Set
End Property
I'm not sure why I have to do the -1 but works like a charm.
In the normal code, instead of combobox.selectedvalue I just use combobox.ValueMember2 and it returns the proper item.

Related

DataGridView column of arbitrary controls per row (or replicate that appearance)

I have a DataGridView which contains, among other things, a "Feature" column and a "Value" column. The "Feature" column is a DataGridViewComboBoxColumn. What I would like to do, is manipulate the "Value" column, such that it can be one of a number of controls, depending on the item selected in the "Feature" column on any given row. So, for example :
Feature
Value Cell Behaviour
A
Combobox with a pre-determined set of options, specific to Feature A
B
Combobox with a different set of pre-determined options, specific to Feature B
C
Checkbox
D
Textbox (free-format)
X
etc. etc.
My initial naive approach to this (which I never really expected to work but figured I'd try anyway...) was to programmatically manipulate the specific value cell in the grid whenever the Feature combobox on the same row was changed :
Private Sub dgv_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
Dim changedCell As DataGridViewCell = CType(dgv.Rows(e.RowIndex).Cells(e.ColumnIndex), DataGridViewCell)
If changedCell.ColumnIndex = cboFeatureColumn.Index Then
Dim cboChangedFeature = CType(changedCell, DataGridViewComboBoxCell)
If cboChangedFeature.Value IsNot Nothing Then ConfigureValueField(cboChangedFeature)
End If
End Sub
Private Sub ConfigureValueField(cboFeature As DataGridViewComboBoxCell)
Dim cllValueField As DataGridViewCell = dgv.Rows(cboFeature.RowIndex).Cells(valueColumn.Index)
Dim featureID As Integer = dgv.Rows(cboFeature.RowIndex).Cells(featureIDColumn.Index).Value
Dim matches = From row In dtbFeatureList Let lookupID = row.Field(Of Integer)("ID") Where lookupID = featureID
Dim strFieldControl As String = ""
If matches.Any Then strFeatureControl = matches.First().row.Field(Of String)("FieldControl")
Select Case strFieldControl
Case "Checkbox"
' Do something
Case "Textbox"
' Do something
Case "Combobox"
Dim cboConfigured As New DataGridViewComboBoxCell
Dim dvOptions As New DataView(dtbFeatureValueList)
dvOptions.RowFilter = "[FeatureID] = " & featureID
Dim dtbOptions As DataTable
dtbOptions = dvOptions.ToTable
With cboConfigured
.DataSource = dtbOptions
.ValueMember = "Value"
.DisplayMember = "ValueText"
.DisplayStyle = DataGridViewComboBoxDisplayStyle.ComboBox
.ReadOnly = False
End With
cllValueField = cboConfigured
End Select
End Sub
But this (probably, obviously to many) doesn't work; for starters it throws a DataError Default Error Dialog :
The following exception occurred in the DataGridView:
System.FormatException: DataGridViewComboBoxCell value is not valid To
replace this default dialog please handle the DataError event.
...which I can trap and handle (i.e. ignore!) easily enough :
Private Sub dgv_DataError(sender As Object, e As DataGridViewDataErrorEventArgs) Handles dgv.DataError
e.Cancel = True
End Sub
...and the resulting cell does have the appearance of a combobox but nothing happens when I click the dropdown (no list options appear) I suspect there are a whole host of DataErrors being thrown; to be quite honest, I'm not entirely comfortable with ignoring exceptions like this without handling them properly...
The only alternative option I can think of is to add separate columns for each possible value type (so add a DataGridViewComboBoxColumn for combos, a DataGridViewCheckBoxColumn for checkboxes, a DataGridViewTextBoxColumn for textboxes etc.) but then all of my values are scattered across multiple columns instead of all under a single heading which will be really confusing to look at.
And I really want to retain the appearance of comboboxes (set list of options) versus checkboxes (boolean values) versus textboxes (free-format text), as it makes it a lot easier for users to differentiate the values.
I read somewhere that it may be possible to derive my own custom column class, inheriting the native DataGridViewColumn class, but I couldn't find any examples of how this was done. I've done something similar with a customised DataGridViewButtonColumn but literally just to change the appearance of the buttons across the entire column, not the functionality of the individual cells within it.
Would anybody have any suggestions as to how it might be possible to have a mix of controls in the same column, configured specifically to the row in which they reside?
EDIT
So, I followed the walkthrough at : https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-host-controls-in-windows-forms-datagridview-cells?view=netframeworkdesktop-4.8&redirectedfrom=MSDN
And I've added four new classes to my project as follows :
UserControl class : a basic control which contains a textbox, a combobox and a checkbox, and basic methods for showing/hiding each one as appropriate, configuring the combobox if necessary etc. By default, the UserControl should display as an empty textbox.
CustomConfigurableCellEditingControl : derived from the UserControl in #1
DataGridViewCustomConfigurableCell : container for the EditingControl in #2 and derived from DataGridViewTextBoxCell
DataGridViewCustomConfigurableColumn : container for the Cell in #3 and derived from DataGridViewColumn
Public Class UserControl
Private displayControl As String
Private displayValue As String
Private myValue As String
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
displayControl = "Textbox"
displayValue = ""
refreshDisplay()
End Sub
Public Property ControlToDisplay As String
Get
Return displayControl
End Get
Set(value As String)
displayControl = value
End Set
End Property
Public Property ValueToDisplay As String
Get
Return displayValue
End Get
Set(value As String)
displayValue = value
End Set
End Property
Public Property Value As String
Get
Return myValue
End Get
Set(value As String)
myValue = value
End Set
End Property
Public Sub refreshDisplay()
Select Case displayControl
Case "Textbox"
With ucTextBox
.Text = displayValue
.Visible = True
End With
ucComboBox.Visible = False
ucCheckbox.Visible = False
Case "Combobox"
With ucComboBox
.Visible = True
End With
ucTextBox.Visible = False
ucCheckbox.Visible = False
Case "Checkbox"
With ucCheckbox
.Checked = myValue
.Visible = True
End With
ucTextBox.Visible = False
ucComboBox.Visible = False
End Select
End Sub
Public Sub configureCombobox(dtb As DataTable, valueMember As String, displayMember As String, style As ComboBoxStyle)
With ucComboBox
.DataSource = dtb
.ValueMember = "ID"
.DisplayMember = "FriendlyName"
.DropDownStyle = style
End With
End Sub
End Class
Class CustomConfigurableCellEditingControl
Inherits UserControl
Implements IDataGridViewEditingControl
Private dataGridViewControl As DataGridView
Private valueIsChanged As Boolean = False
Private rowIndexNum As Integer
Public Sub New()
End Sub
Public Property EditingControlFormattedValue() As Object _
Implements IDataGridViewEditingControl.EditingControlFormattedValue
Get
'Return Me.Value.ToShortDateString()
Return Me.valueIsChanged.ToString
End Get
Set(ByVal value As Object)
Me.Value = value
End Set
End Property
Public Function GetEditingControlFormattedValue(ByVal context As DataGridViewDataErrorContexts) As Object _
Implements IDataGridViewEditingControl.GetEditingControlFormattedValue
Return Me.valueIsChanged.ToString
End Function
Public Sub ApplyCellStyleToEditingControl(ByVal dataGridViewCellStyle As DataGridViewCellStyle) _
Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl
Me.Font = dataGridViewCellStyle.Font
End Sub
Public Property EditingControlRowIndex() As Integer _
Implements IDataGridViewEditingControl.EditingControlRowIndex
Get
Return rowIndexNum
End Get
Set(ByVal value As Integer)
rowIndexNum = value
End Set
End Property
Public Function EditingControlWantsInputKey(ByVal key As Keys,
ByVal dataGridViewWantsInputKey As Boolean) As Boolean _
Implements IDataGridViewEditingControl.EditingControlWantsInputKey
End Function
Public Sub PrepareEditingControlForEdit(ByVal selectAll As Boolean) _
Implements IDataGridViewEditingControl.PrepareEditingControlForEdit
' No preparation needs to be done.
End Sub
Public ReadOnly Property RepositionEditingControlOnValueChange() As Boolean _
Implements IDataGridViewEditingControl.RepositionEditingControlOnValueChange
Get
Return False
End Get
End Property
Public Property EditingControlDataGridView() As DataGridView _
Implements IDataGridViewEditingControl.EditingControlDataGridView
Get
Return dataGridViewControl
End Get
Set(ByVal value As DataGridView)
dataGridViewControl = value
End Set
End Property
Public Property EditingControlValueChanged() As Boolean _
Implements IDataGridViewEditingControl.EditingControlValueChanged
Get
Return valueIsChanged
End Get
Set(ByVal value As Boolean)
valueIsChanged = value
End Set
End Property
Public ReadOnly Property EditingControlCursor() As Cursor _
Implements IDataGridViewEditingControl.EditingPanelCursor
Get
Return MyBase.Cursor
End Get
End Property
Protected Overrides Sub OnValueChanged(ByVal eventargs As EventArgs)
' Notify the DataGridView that the contents of the cell have changed.
valueIsChanged = True
Me.EditingControlDataGridView.NotifyCurrentCellDirty(True)
MyBase.OnValueChanged(eventargs)
End Sub
End Class
Public Class DataGridViewCustomConfigurableCell
Inherits DataGridViewTextBoxCell
Public Sub New()
End Sub
Public Overrides Sub InitializeEditingControl(ByVal rowIndex As Integer,
ByVal initialFormattedValue As Object,
ByVal dataGridViewCellStyle As DataGridViewCellStyle)
' Set the value of the editing control to the current cell value.
MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
Dim ctl As CustomConfigurableCellEditingControl = CType(DataGridView.EditingControl, CustomConfigurableCellEditingControl)
' Use the default row value when Value property is null.
If (Me.Value Is Nothing) Then
ctl.Value = CType(Me.DefaultNewRowValue, String)
Else
ctl.Value = CType(Me.Value, String)
End If
End Sub
Public Overrides ReadOnly Property EditType() As Type
Get
' Return the type of the editing control that Cell uses.
Return GetType(CustomConfigurableCellEditingControl)
End Get
End Property
Public Overrides ReadOnly Property ValueType() As Type
Get
' Return the type of the value that Cell contains.
Return GetType(String)
End Get
End Property
Public Overrides ReadOnly Property DefaultNewRowValue() As Object
Get
Return ""
End Get
End Property
End Class
Imports System.Windows.Forms
Public Class DataGridViewCustomConfigurableColumn
Inherits DataGridViewColumn
Public Sub New()
MyBase.New(New DataGridViewCustomConfigurableCell())
End Sub
Public Overrides Property CellTemplate() As DataGridViewCell
Get
Return MyBase.CellTemplate
End Get
Set(ByVal value As DataGridViewCell)
' Ensure that the cell used for the template is a Custom Configurable Cell.
If (value IsNot Nothing) AndAlso Not value.GetType().IsAssignableFrom(GetType(DataGridViewCustomConfigurableCell)) Then
Throw New InvalidCastException("Must be a Custom Configurable Cell")
End If
MyBase.CellTemplate = value
End Set
End Property
End Class
But... I'm still none the wiser as to how I populate, display, manipulate etc. The code compiles fine, but I just get a blank column. I can't see any controls, I can't see any values and I can't seem to "trap" any of the events that should manipulate them?
With dgv
cfgValueColumn = New DataGridViewCustomConfigurableColumn With {.DisplayIndex = valueColumn.Index + 1}
With cfgValueColumn
.HeaderText = "Custom Value"
.Width = 300
End With
.Columns.Add(cfgValueColumn)
Dim cfgCell As DataGridViewCustomConfigurableCell
For Each row As DataGridViewRow In .Rows
cfgCell = CType(row.Cells(cfgValueColumn.Index), DataGridViewCustomConfigurableCell)
With cfgCell
.Value = row.Cells(valueColumn.Index).Value
End With
Next
End With
Your main approach is correct if you just need to change the type of the given cell based on the ComboBox selection.
When the value changes of the ComboBox cell:
Dispose of the current cell of the target column.
Create a new cell whose type is defined by the ComboBox selection.
Assign the new cell to the grid place of the old one.
Here's a working example.
Private Sub dgv_CellValueChanged(
sender As Object,
e As DataGridViewCellEventArgs) Handles dgv.CellValueChanged
If e.RowIndex < 0 Then Return
If e.ColumnIndex = dgvcTypeSelector.Index Then
Dim cmb = DirectCast(dgv(e.ColumnIndex, e.RowIndex), DataGridViewComboBoxCell)
Dim selItem = cmb.FormattedValue.ToString()
Dim valSelCell = dgv(dgvcValueSelector.Index, e.RowIndex)
valSelCell.Dispose()
Select Case selItem
Case "Text"
valSelCell = New DataGridViewTextBoxCell With {
.Value = "Initial value If any."
}
Case "Combo"
valSelCell = New DataGridViewComboBoxCell With {
.ValueMember = "Value",
.DisplayMember = "ValueText",
.DataSource = dt,
.Value = 2 ' Optional...
}
Case "Check"
valSelCell = New DataGridViewCheckBoxCell With {
.ValueType = GetType(String),
.Value = True
}
valSelCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
Case "Button"
valSelCell = New DataGridViewButtonCell With {
.Value = "Click Me!"
}
valSelCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
End Select
dgv(dgvcValueSelector.Index, e.RowIndex) = valSelCell
ElseIf e.ColumnIndex = dgvcValueSelector.Index Then
Dim cell = dgv(e.ColumnIndex, e.RowIndex)
Console.WriteLine($"{cell.Value} - {cell.FormattedValue}")
End If
End Sub
Private Sub dgv_CurrentCellDirtyStateChanged(
sender As Object,
e As EventArgs) Handles dgv.CurrentCellDirtyStateChanged
If dgv.IsCurrentCellDirty Then
dgv.CommitEdit(DataGridViewDataErrorContexts.Commit)
End If
End Sub
' If you need to handle the Button cells.
Private Sub dgv_CellContentClick(
sender As Object,
e As DataGridViewCellEventArgs) Handles dgv.CellContentClick
If e.RowIndex >= 0 AndAlso
e.ColumnIndex = dgvcValueSelector.Index Then
Dim btn = TryCast(dgv(e.ColumnIndex, e.RowIndex), DataGridViewButtonCell)
If btn IsNot Nothing Then
Console.WriteLine(btn.FormattedValue)
End If
End If
End Sub
Note, I've changed the value type of the check box cell by ValueType = GetType(String) to avoid throwing exceptions caused by the different value types of the check box and main columns. I'd assume the main column here is of type DataGridViewTextBoxColumn. So you have String vs. Boolean types. If you face problems in the data binding scenarios, then just swallow the exception.
Private Sub dgv_DataError(
sender As Object,
e As DataGridViewDataErrorEventArgs) Handles dgv.DataError
If e.RowIndex >= 0 AndAlso e.ColumnIndex = dgvcValueSelector.Index Then
e.ThrowException = False
End If
End Sub

How to check elements from an array in a CheckListBox

i have a checklisbox with some value, let's say
"Apple"
"Peach"
"Lemon"
These values came from a dataset.
I have an array with Apple and Lemon: {"Apple", "Lemon"}.
How to check in the checklistbox each value read in this array?
EDIT: In my case, the checklistbox was populate using a dataset provided by a SQL query
In the following code sample, data from SQL-Server (database doesn't matter but this is what I used, what is important is the container the data is loaded into is loaded into a list.
Container to hold data
Public Class Category
Public Property Id() As Integer
Public Property Name() As String
Public Overrides Function ToString() As String
Return Name
End Function
End Class
Class to read data
Imports System.Data.SqlClient
Public Class SqlOperations
Private Shared ConnectionString As String =
"Data Source=.\SQLEXPRESS;Initial Catalog=NorthWind2020;Integrated Security=True"
Public Shared Function Categories() As List(Of Category)
Dim categoriesList = New List(Of Category)
Dim selectStatement = "SELECT CategoryID, CategoryName FROM Categories;"
Using cn As New SqlConnection With {.ConnectionString = ConnectionString}
Using cmd As New SqlCommand With {.Connection = cn}
cmd.CommandText = selectStatement
cn.Open()
Dim reader = cmd.ExecuteReader()
While reader.Read()
categoriesList.Add(New Category() With {.Id = reader.GetInt32(0), .Name = reader.GetString(1)})
End While
End Using
End Using
Return categoriesList
End Function
End Class
Extension method
Which can check or uncheck a value if found in the CheckedListBox and is case insensitive.
Public Module Extensions
<Runtime.CompilerServices.Extension>
Public Function SetCategory(sender As CheckedListBox, text As String, Optional checkedValue As Boolean = True) As Boolean
If String.IsNullOrWhiteSpace(text) Then
Return False
End If
Dim result = CType(sender.DataSource, List(Of Category)).
Select(Function(item, index) New With
{
Key .Column = item,
Key .Index = index
}).FirstOrDefault(Function(this) _
String.Equals(this.Column.Name, text, StringComparison.OrdinalIgnoreCase))
If result IsNot Nothing Then
sender.SetItemChecked(result.Index, checkedValue)
Return True
Else
Return False
End If
End Function
End Module
Form code
Public Class ExampleForm
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CheckedListBox1.DataSource = SqlOperations.Categories
End Sub
Private Sub CheckCategoryButton_Click(sender As Object, e As EventArgs) Handles CheckCategoryButton.Click
CheckedListBox1.SetCategory(CategoryToCheckTextBox.Text, StateCheckBox.Checked)
End Sub
End Class
To check all at once
Private Sub CheckAllButton_Click(sender As Object, e As EventArgs) Handles CheckAllButton.Click
CType(CheckedListBox1.DataSource, List(Of Category)).
ForEach(Sub(cat) CheckedListBox1.SetCategory(cat.Name, True))
End Sub
You haven't mentioned exactly how the CheckedListBox gets populated by the DataSet, I've made the assumption that you add Strings directly to the Items collection.
This code will simply loop through the CheckedListBox and compare the values with the array, whatever the match result, the Checkbox is either ticked or cleared.
Dim theArray() As String = {"Apple", "Lemon"}
For counter As Integer = 0 To CheckedListBox1.Items.Count - 1
Dim currentItem As String = CheckedListBox1.Items(counter).ToString
Dim match As Boolean = theArray.Contains(currentItem.ToString)
CheckedListBox1.SetItemChecked(counter, match)
Next
Use the SetItemChecked method like this:
CheckedListBox1.SetItemChecked(CheckedListBox1.Items.IndexOf("your item goes here"), True)
Note that if the item does not exist, an exception will be thrown, so be sure to check for the item before calling the SetItemChecked() method. To do that, you can check for the return value of IndexOf(). It will be -1 if the item does not exist.

How to return specific properties of a custom vb.net object

I'm trying to generate a list of all of the TableName and FieldName properties for a custom object type called LxTextBox. I've gotten as far as generating a list of all of the LxTextBox names on my form, but I can't figure out a way to call the properties of the custom object... I've been looking into System.Reflection, but I haven't ever used it. Additionally, I'm returning the list to a RichTextBox while I'm testing this out, but ultimately, I need to return each objects properties as a data row. Example:
ObjectName Table Field
---------------------------------------
LxTextBox23 SomeTbl SomeFld
Here's my code to return the list - updated based on #OneFineDay...
Imports System.Collections.Generic
Imports Application.UDF.Controls
Public Class MeasurementsControl
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim textBoxList As New List(Of Control)
Dim customTbs = GetAllControls(Me)
Dim sb As New System.Text.StringBuilder
For index As Integer = 0 To customTbs.Count - 1
sb.Append(customTbs.Item(index).TableName & "." & customTbs.Item(Index).FieldName & System.Environment.NewLine)
Next
RichTextBox1.Text = sb.ToString
End Sub
Private Function GetAllControls(ByVal searchWithin As Control) As List(Of LxTextbox)
Dim returnList As List(Of LxTextbox) = Nothing
If searchWithin.HasChildren Then
returnList = searchWithin.Controls.OfType(Of LxTextbox).ToList
For Each ctrl As Control In searchWithin.Controls
returnList.AddRange(GetAllControls(ctrl))
Next
End If
Return returnList
End Function
End Class
I made the changes suggested and I'm throwing an error: OfType is not a member of System.Windows.Forms.Control.ControlCollection
FYI - Adding Imports System.Linq did not fix the error.
You are boxing it into a Control the object from where it derives, where you're custom properties cannot be found. You can cast it right from the control collection.
Dim customTbs = GetAllControls(Me)
'recursive function
Private Function GetAllControls(ByVal searchWithin As Control) As List(Of LxTextbox)
Dim returnList As List(Of LxTextbox) = Nothing
returnList = searchWithin.Controls.OfType(Of LxTextbox).ToList
If searchWithin.HasChildren Then
For Each ctrl As Control In searchWithin.Controls
Dim ctrls = GetAllControls(ctrl)
If Not ctrls Is Nothing Then returnList.AddRange(ctrls)
Next
End If
Return returnList
End Function

Binding a custom property to a datatable field ,the source table is changing when navigating through items

I've got custom control inherited from a textbox I added a custom property as follows
Dim _MyVal As Object
Public Property MyVal As Object
Get
Return _MyVal
End Get
Set(value As Object)
_MyVal = value
If IsNothing(value) OrElse IsDBNull(value) Then
Me.Text = "Null"
Else
Select Case _MyVal
Case 1
Me.Text = "NewYork"
Case 2
Me.Text = "London"
Case 3
Me.Text = "Zwara"
Case Else
Me.Text = "Unknown"
End Select
End If
End Set
End Property
So I bound this property to a DataTable field in simple WinForm with a DataGridView and the custom textbox as follows
Public Class Form10
Dim dtMain As New DataTable
Dim bsMain As New BindingSource
Private Sub Form10_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
dtMain.Columns.Add("Name", GetType(String))
dtMain.Columns.Add("CityID", GetType(Integer))
dtMain.Rows.Add("John", 1)
dtMain.Rows.Add("Steve", 2)
dtMain.Rows.Add("Sara", 3)
dtMain.Rows.Add("Joe", DBNull.Value)
bsMain.DataSource = dtMain
dgv.DataSource = bsMain
txtCity.DataBindings.Add(New System.Windows.Forms.Binding("MyVal", bsMain, "CityID", False, DataSourceUpdateMode.OnPropertyChanged))
dtMain.AcceptChanges()
End Sub
My problems is, when I navigate through items, the DataTable is changed. Why does that happen?
While when I bind the field to a ComboBox SelectedValue property it works perfectly. It doesn't change the source through navigating; it changes when I change the SelectedValue property

Binding data to a combobox and add a select option

I'm using VB.NET and Visual Studio 2010
I've got a windows form with a combobox.
I fill the combobox using the following
Dim objSizes As List(Of ASME_Hub_Sizes) = ASME_Hub_Sizes.GetAllHubSizes()
If Not objSizes Is Nothing Then
With Me.cboSize
.DisplayMember = "Size"
.ValueMember = "ID"
.DataSource = objSizes
End With
End If
This works fine, but i would like to add a "Select Size..." option but i'm unsure how to do this.
It seems so much easier to do this in asp.net, but this has me baffled
Thanks
Mick
You could try adding a custom objSize object to the objSizes collection that has ID = 0 and value = "Select size..." as it's ID 0 it should be at the top (I think) and won't clash with any values in your database, upon saving the record you can validate the combobox to avoid writing the "Select size..." object to your database. I'll have a bit of a code and see if this will work...
Ok, I've had another look. You can do as I suggested but you must sort the list before passing it to the combobox. Here is my example:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
With Me.ComboBox1
.DisplayMember = "Description"
.ValueMember = "ID"
.DataSource = GetListOfObjects.returnListOFObjects
End With
End Sub
End Class
Public Class dummyObjectForCombo
Public Property ID As Integer
Public Property Description As String
Public Sub New(ByVal id As Integer,
ByVal description As String)
_ID = id
_Description = description
End Sub
End Class
Public Class GetListOfObjects
Public Shared Function returnListOFObjects() As List(Of dummyObjectForCombo)
Dim col As New List(Of dummyObjectForCombo)
Dim obj0 As New dummyObjectForCombo(-1, "Herp")
Dim obj1 As New dummyObjectForCombo(1, "Jamie")
Dim obj2 As New dummyObjectForCombo(2, "Bob")
col.Add(obj1)
col.Add(obj2)
col.Add(obj0)
'using a lambda to sort by ID as per http://stackoverflow.com/questions/3309188/c-net-how-to-sort-a-list-t-by-a-property-in-the-object
Return col.OrderBy(Function(x) x.ID).ToList
End Function
End Class
I've used -1 as the topmost record instead of 0.
So you'd get your list as usual, add in the extra dummy record, then sort it as per the above code before assigning it as your combo boxes datasource.
simply add the item before setting the datasource property
C# : cboSize.items.add(...);