ComboBoxColumn errors out when trying to load data - vb.net

I am trying to create a DataGridView with a DataGridViewComboBox
I am using the following code to generate it using 2 function that return DataTables for populating it:
'// Returns a DataTable with 2 Columns of Type Integer and System.String
'// Column 1 are IDs called ID, Column 2 is a String of numbers called CompanyCode
'// Example:
'// 1,'9999'
'// 2,'9998'
'// 3,'9997' etc.
Dim uTableSys As DataTable = GetCompanyCodeLinks()
'// Returns a DataTable with 1 Column of Type System.String
'// The Column is called CC
'// Example: '0001','0002','1234'
Dim CompanyCodes As DataTable = GetListCompanyCodes()
Dim uBindSys As New BindingSource
uBindSys.DataSource = uTableSys
Dim uCol1 As New DataGridViewComboBoxColumn()
uCol1.HeaderText = "Buchungskreis"
'// Points to uTableSys.CompanyCode
uCol1.DataPropertyName = "CompanyCode"
uCol1.DataSource = CompanyCodes
uCol1.ValueType = uTableSys.Columns(1).DataType
'// Points to CompanyCodes.CC
uCol1.ValueMember = "CC"
uCol1.DisplayMember = uCol1.ValueMember
Me.DGV.Columns.Add(uCol1)
Me.DGV.AutoGenerateColumns = False
Me.DGV.DataSource = uBindSys
But as soon as I add uCol1.DataPropertyName = "CompanyCode", which I assume would link the DGV Column to the BindingSource table "CompanyCode" column, it returns with the following error:
"The DataGridViewComboBoxCell value is not valid"
I have checked the types of the DataTable, which are System.String, which the Column is set to via the uCol1.ValueType in the code. For clarification, uTableSys.Columns(1).DataType returns System.String
So why does it return with a "value is not valid" error?

I have figured out the issue:
The value that is written in the cell from the GridView DataSource on loading has to exist ComboBox list as well. If the value does not exist, it will throw the error.

Related

DataGridView Cell Formatting Error

DataGridView caught me again, I cannot figure out, why I get cell formatting error with following code. None of the solutions I found on the net wouldn't work/fit either. On load I set:
Dim dtAlign As New DataTable ' creating and filling a DataTable
dtAlign.Columns.Add("ID", GetType(Int32))
dtAlign.Columns.Add("Text", GetType(String))
dtAlign.Rows.Add(1, "Left")
dtAlign.Rows.Add(2, "Right")
dtAlign.Rows.Add(3, "Center")
Dim cola As DataGridViewComboBoxColumn = CType(Me.dgList.Columns("ColAlign"), DataGridViewComboBoxColumn)
cola.DisplayMember = "Text"
cola.ValueMember = "ID"
cola.DataSource = dtAlign ' assign datatable as a combobox col. datasource
The columns are set via VS GUI (this column: Name = 'ColAlign', Width = 100, DataPropertyName = 'ColAlign', column type = DataGridViewComboBoxCell, etc.).
It, works, at a breakpoint just before filling data into DGV, I see a valid datatable is provided:
When I don't load any data into column, I can see a proper ComboBox selection:
However, if I add data in the datasource for this column, I get cell formatting error (saying the value is not valid) and the value is shown as display member text:
The database column is not nullable (at this point always 1), and is INT. I even tried to CAST it, to be sure it's not mixed up i.e. as string:
CAST(ColAlign as INT) as ColAlign
Still, I get the error and I have no more ideas what can be wrong, but ovbiously the 1 in the datatable is not the same as 1 in the database result set. In past I had a problem with Int16 not matching INT, but Int32 always worked agaist INT in database. And even:
CAST(1 as INT) as ColAlign
...doesn't work. By the way, I assign the data simply this way:
Me.dgList.DataSource = ds.Tables(0)
As usually, it has to be something really simple, what I'm missing. I would appreciate even hints how to furthermore debug such an issue.
EDIT:
I also tried creating a button and in onClick wrote a code:
Me.dgList.Rows(1).Cells("ColAlign").Value = 1
This works well.
EDIT 2:
I inserted a DataTable between DataSet and DataSource with fixed datatypes columns (int32 in case of the ComboBoxColumns), which should rule out that wrong datatypes are provided and it still doesn't work...
Dim dtGrid As New DataTable
dtGrid.Columns.Add("ID", GetType(Int32))
dtGrid.Columns.Add("FormID", GetType(Int32))
dtGrid.Columns.Add("ColName", GetType(String
dtGrid.Columns.Add("IsVisible", GetType(Boolean))
dtGrid.Columns.Add("ColWidth", GetType(String))
dtGrid.Columns.Add("ColAlign", GetType(Int32))
dtGrid = ds.Tables(0)
Me.dgList.AutoGenerateColumns = False
Me.dgList.DataSource = dtGrid
EDIT 3
Another debugging:
Dim dtTemp As DataTable
Dim dgwcb As DataGridViewComboBoxColumn = CType(Me.dgList.Columns("ColAlign"), DataGridViewComboBoxColumn)
dtTemp = CType(dgwcb.DataSource, DataTable)
MsgBox("ValueMember name = " & dgwcb.ValueMember.ToString & vbCrLf &
"DisplayMember name = " & dgwcb.DisplayMember.ToString & vbCrLf &
"DataPropertyName = " & dgwcb.DataPropertyName.ToString & vbCrLf &
"Value = " & Me.dgList.Rows(2).Cells("ColAlign").Value.ToString & vbCrLf &
"ToString = " & dgwcb.ToString)
dtTemp in DataSet Visualizer looks good and everything checked in MsgBox is es expected (ValueMember = "ID", DisplayMember = "Text", DataPropertyName = "ColAlign", Value = 2 /I set it to 2 for a change/).
WORKAROUND:
The only way it works is to leave columns with ComboBoxCell empty and fill them manually after the DGV datasource is set, narrowing them to Int32 along (note that they were narrowed both in SQL and dtGrid already):
For ir = 0 To dtGrid.Rows.Count - 1
Me.dgList.Rows(ir).Cells("ColAlign").Value = CInt(dtGrid.Rows(ir)("ColAlign"))
Next
Since I'm using DGV with ComboBoxCell extremely rarely, I can live with this "solution".
EDIT 4 / WORKAROUND 2
I created a 2nd, identical (structure) dtGrid2 and copied values row-by-row and cell-by-cell into the 2nd and voila - it works with the dtGrid2 and doesn't with the original dtGrid! So when I pull data from database, the original correct datatypes of columns are re-typed to something wrong. I suspect this is only happening with SQLiteDataAdapter, because I didn't experienced it with SqlDataAdapter in past. A simplified code to explain:
Dim dtGrid As New DataTable
Dim dtGrid3 As New DataTable
dtGrid.Columns.Add("ID", GetType(Int32))
dtGrid.Columns.Add("ColName", GetType(String))
dtGrid.Columns.Add("ColAlign", GetType(Int32))
' load data from DB. ...and corrupt the dtGrid...
dtGrid = ds.Tables(0).DefaultView.ToTable
dtGrid3.Columns.Add("ID", GetType(Int32)) ' identical columns
dtGrid3.Columns.Add("ColName", GetType(String)) ' identical columns
dtGrid3.Columns.Add("ColAlign", GetType(Int32)) ' identical columns
For ir = 0 To dtGrid.Rows.Count - 1 ' copy all values to new identical table
dtGrid3.Rows.Add({dtGrid(ir)("ID"), dtGrid(ir)("ColName"), dtGrid(ir)("ColAlign")})
Next
Me.dgList.DataSource = dtGrid3 ' works!!! Doesn't with dtGrid...
Try this:
Dim cola As DataGridViewComboBoxColumn = CType(Me.dgList.Columns("ColAlign"), DataGridViewComboBoxColumn)
cola.DataSource = dtAlign
cola.DataPropertyName = "ID"
cola.DisplayMember = "Text"
cola.ValueMember = "ID"
You need to set DataPropertyName if you are binding to DataSource like DataTable
You have a DataGridView with some columns set.
Something like this:
But could be anything else.
Let's create a sample DataSource using a List(Of Class):
Public Class MyDataSourceRow
Private _ID As Integer
Private _Text As String
Public Sub New()
End Sub
Public Property ID() As Integer
Get
Return Me._ID
End Get
Set(ByVal value As Integer)
Me._ID = value
End Set
End Property
Public Property Text() As String
Get
Return Me._Text
End Get
Set(ByVal value As String)
Me._Text = value
End Set
End Property
End Class
Creeate a new DataGridViewComboBoxColumn, set it's basic properties, specify a DataSource and add it to the DataGridView:
Dim MyDataSource = New List(Of MyDataSourceRow)
MyDataSource.Add(New MyDataSourceRow With {.ID = 1, .Text = "Left"})
MyDataSource.Add(New MyDataSourceRow With {.ID = 2, .Text = "Right"})
MyDataSource.Add(New MyDataSourceRow With {.ID = 3, .Text = "Center"})
Dim MyColumn As DataGridViewComboBoxColumn = New DataGridViewComboBoxColumn
MyColumn.Width = 150
' Name of the new Column. Will also be the text of the Header
MyColumn.Name = "ComboAlignment"
'Define its DataSource
MyColumn.DataSource = MyDataSource
' The DataPropertyName is the field of your DataSource to which your column value is bound
MyColumn.DataPropertyName = "ID"
' This is the value you want the User to see
MyColumn.DisplayMember = "Text"
' ValueMember is the value passed to the DataGrid when a User selects an entry in the DropDown
MyColumn.ValueMember = "ID"
'Insert the column in the first position -> Index(0)
Me.DataGridView1.Columns.Insert(0, MyColumn)
Now, this is the result:
For some reason, you are confusing the .Name of the column with it's .DataPropertyName

Parsing through DataTable for matching value and creating a new DataTable from the results

In VB.NET I'm trying to count through the value's in a list, until the value for "stock" in my DataTable, Boxes, is equal to the value in my list. When this occurs a row should be created in my new DataTable "output". I would then continue counting through the list looking for other matching instances to add into "output".
So far counting through the list and then within that count going through the datatable to match value's works wonderfully. The part where I'm getting hung up is when I try to take the matching row and put it into another table.
Dim output As DataTable
Dim jsonstringy As String = BoxComms.WebGet("http://foo.foo.foo") 'PULLS JSON STRING
Dim Boxes = Newtonsoft.Json.JsonConvert.DeserializeObject(Of DataTable)(jsonstring)
Dim MyString As String = TextBox1.Text 'MAKE STRING OF STOCK #'s FROM TEXTBOX
MyString = MyString.Replace(" ", "") 'GET RID OF SPACES
Dim MyArray() As String = MyString.Split(",") 'SEPERATE COMMA DELIMETED LIST
Dim MyList As List(Of String) = MyArray.ToList()
For Each value In MyList 'COUNT THROUGH LIST OF STOCK #'s
For Each row As DataRow In Boxes.Rows
If row("stock") = value Then 'IF STOCK # IS EQUAL TO ANY OF NUMBERS IN TEXTBOX
output.ImportRow(row) 'ADD ROW FROM DATATABLE Boxes to DATATABLE output
End If
Next row
Next
The solution was fairly simple, my DataTable needed a schema first. in order to copy the schema all I needed was to initialize the new table as a clone. to do so the code was as follows.
Dim output As DataTable = Boxes.Clone()
clone vs copy. copy get's the schema and data clone just gets the schema. then as I was parsing through the first table looking for matches I had to add the row to my new table with the following code.
output.Rows.Add(row.ItemArray)
the full code is as follows
Dim jsonstring As String = BoxComms.WebGet("https://foo.foo.foo")
Dim Boxes = Newtonsoft.Json.JsonConvert.DeserializeObject(Of DataTable)(jsonstring)
Dim output As DataTable = Boxes.Clone()
Dim MyString As String = TextBox1.Text
MyString = MyString.Replace(" ", "")
Dim MyArray() As String = MyString.Split(",")
Dim MyList As List(Of String) = MyArray.ToList()
For Each value In MyList
For Each row As DataRow In Boxes.Rows
If row("stock") = value Then
Console.WriteLine(row("stock"))
output.Rows.Add(row.ItemArray)
End If
Next row
Next
BoxControl.DataGridView1.DataSource = output
Me.Close()
End If

In VB.NET I am having problems populating a DataGridView combobox using a datatable

I have a datatable (dsBrands) that I want to display in a datagridview. The combobox in the datagridview gets its selections from a different table(dsGroups). How do I show in the combobox the value that is currently stored in the dsBrands table? Below is my code that works except that the combobox is left blank. The combobox collection does populate correctly from dsGroups. The value that needs to be displayed in the combobox from dsBrands is in the collection. The --> is just to make the line stand out and not in the actual code.
Private Sub FillBrands()
Dim TransCount As Integer
Dim Group As String
If Not ds.Tables.Contains("dsBrands") Then Else ds.Tables("dsBrands").Clear()
conn.Open()
daBrands = New OleDbDataAdapter("Select * from Brands", conn)
daBrands.FillSchema(ds.Tables("dsBrands"), SchemaType.Source)
daBrands.Fill(ds, "dsBrands")
conn.Close()
dgv1.AutoGenerateColumns = False
bs1.DataSource = ds.Tables("dsBrands")
dgv1.DataSource = bs1
Dim colDisplay As New DataGridViewCheckBoxColumn
colDisplay.DataPropertyName = "Display"
colDisplay.HeaderText = "Include?"
colDisplay.Name = "Display"
colDisplay.Visible = True
Dim colGroupList As New DataGridViewComboBoxColumn
colGroupList.HeaderText = "Add to Group"
colGroupList.Name = "GroupList"
colGroupList.Visible = True
colGroupList.DataSource = ds.Tables("dsGroups")
--> colGroupList.DataPropertyName = "BGroup" 'BGroup is a field in dsBrands table.
dgv1.Columns.Add(colDisplay)
dgv1.Columns.Add(colBrand)
dgv1.Columns.Add(colGroup)
dgv1.Columns.Add(colGroupList)
End Sub
Just as when binding a regular ComboBox, you need to set the DisplayMember and ValueMember properties of your column. The DisplayMember specifies the column whose values get displayed and the ValueMember specifies the column whose values correspond to the grid data. ValueMember should specify the PK column of the table bound to the column that corresponds to the FK column of the table bound to the grid.

VB.Net Programmatically set DataGridViewComboBox .ValueMember on Rows.Add

I am trying to programmatically set the value of a DataGridViewComboBox when I populate the grid, but I keep getting an error about not being able to sort the combobox - "ComboBox that has a DataSource set cannot be sorted. Sort the data using the underlying data model."
I am not trying to sort the data of the combobox, I am simply trying to set the value member when I perform the Rows.Add function.
EDIT** The entire column should contain the same combobox items, and is not row specific.
Public Sub LoadComboBox()
Dim comboBox = New Dictionary(Of Integer, String)
Try
//myStepTable = data ' SQL query to get ID and Step
For Each row As DataRow In myStepTable.Rows
comboBox.Add(row.Item("StepID"), row.Item("Step"))
Next
'colStep is the defined name of the combobox column
colStep.DataSource = New BindingSource(comboBox, Nothing)
colStep.DisplayMember = "Value"
colStep.ValueMember = "Key"
End Sub
Public Sub LoadGrid()
LoadComboBox()
myDataTable = data 'SQL query to get row data
For Each row As DataRow In myDataTable.Rows
dgvRecipeSteps.Rows.Add(row.Item("RowID"), row.Item("StepID"))
Next
End Sub

Unable to set valuemember/display member to bind combobox

I'm having trouble adding dynamically a combo box column to my datagridview (before you ask, yes it must be dynamic and not done in editor).
The main feature is that the combobox cell is different for each row, so it must be done using combo box cell. checkedRows is a datatable.
Name of the datagridview is editCameraTable. It already has a few columns at this point:
'create new column
Dim resComboColumn As New DataGridViewComboBoxColumn _
With {.HeaderText = "Resolution", .ReadOnly = False, .DisplayIndex = 7, .Name = "Resolution", _
.DisplayMember = "Name", .ValueMember = "ID", .DataPropertyName = "ID"}
'add combo box column
EditCameras.editCameraTable.Columns.Insert(17, resComboColumn)
addResCmbBox(checkedRows, resComboColumn)
Pretty straight forward. You'll notice I have the value member, dataproperty name, etc. Here's the addResCmbBox definition:
Public Function addResCmbBox(ByRef DT As DataTable, column As DataGridViewComboBoxColumn)
Dim resolutions As String()
'for each camera
For i As Integer = 0 To DT.Rows.Count - 1
Dim camera As camera = convertDTtoCam(DT, i)
'get the resarray
Select Case DT.Rows(i).Item("Maker").ToString.ToLower
Case "acti"
resolutions = ACTi.GetResArray(camera)
Case Else
resolutions = ACTi.GetResArray(camera)
End Select
'add items to combobox list
Dim comboCell As New DataGridViewComboBoxCell
comboCell.DataSource = resolutions
For j As Integer = 0 To resolutions.Length - 1
'set to current resolution value
If resolutions(j).TrimStart("N") = camera.res Then
comboCell.Value = resolutions(j)
End If
Next
comboCell.DisplayMember = "Name"
comboCell.ValueMember = "ID"
EditCameras.editCameraTable("Resolution", i) = comboCell
Next
Return Nothing
End Function
camera is a structure. I have no problems until I get to the displayMember and value member problem, i.e. the last line starting with "editcameras.editcameratable...".
When doing so, the exception of "The Field Name does not exist" pops up. If I don't assign the displayMember and valueMember, I have no problems. But, I can't get the value selected in the comboBox (it comes back as Null). At runtime, the combobox column has the valuemember and display name as "ID" and "Name".
How can I bind this comboboxcell to the row so that I can later get it's selected value?
UPDATE:
I did as was commented, and created a struct/class that was meant to be the resolution property:
Public Class ResolutionStruct
Property Name As String
Property ID As String
End Class
And within the loop I create a list of this class, and assign the values to it:
Dim resolutionList As New List(Of ACTi.ResolutionStruct)
For j As Integer = 0 To resolutions.Length - 1
Dim resClass As New ACTi.ResolutionStruct
resClass.Name = resolutions(j)
resClass.ID = resolutions(j)
resolutionList.Add(resClass)
Next
'set combocell values
comboCell.DisplayMember = "Name"
comboCell.ValueMember = "ID"
comboCell.DataSource = resolutionList
EditCameras.editCameraTable("Resolution", i) = comboCell
However, the comboboxCell doesn't show any value when it drops down. So, now I've bound the values but it shows nothing. Is there anything else I should be doing so that I get both the holy combo of seeing the values I'm picking AND having them be bound to the data grid view? :D
UPDATE 2
So, mea culpa. I was adding the combobox cell to the wrong column!
So now, it is showing the values. I click a value, and try to grab the selected value as as string:
Dim cmbbox2 As DataGridViewComboBoxCell = editCameraTable("Resolution", i)
resolution(i) = cmbbox2.Selected.ToString()
But it still says it's a null value! Mid build I checked the combobox props. IN fact "selected" is a boolean as false. It has no value, says it has no items as well. Any ideas on why it says it is null?
UPDATE3:
I recently resorted a different column in the table, and the values from the combo box are cleared! I guess it's really never being attached/bound in the first place.
UPDATE4:
Fixed it!!
Apparently this line:
editCameraTable.Sort(editCameraTable.Columns("ID"), System.ComponentModel.ListSortDirection.Ascending)
Caused the table to freak out! I can now get the value (woohoo!)
Right, I'll try to explain this shortly:
DisplayMember and ValueMember are supposed to be set using properties. For example you create a class containing Name and ID
Public Class Test
Property Name as String
Property ID as String
End Class
Create a few of these objects and put them in a list. Set the list as the datasource to the combobox. Now you can access the DisplayMember and ValueMember as you have written it in your code. Value would be the ID and SelectedItem would be the entire class.
What you are doing now is that you are adding a list of strings to the combobox. A String does not contain the Property Name nor ID, so naturally you can't fetch them. See it like this:
To be able to use Value and/or DisplayMember you need to be able to fetch the Property by yourself. In this case:
resolutions(j).Name
or
resolutions(j).ID
This does not work.
But for example you would be able to do this:
resolutions(j).Length
So You would be able to do this, which would display the Length in the combobox:
Combobox.DisplayMember = "Length"
To currently get the value you would have to do:
Combobox.SelectedItem.ToString()
But since you have it in a combobox column My guess is that this won't cut it since you can't fetch the value from the DataGridView.
EDIT: You are still doing this right?
<DataGridView>.Item("Resolution", i) = comboCell
Otherwise you will have empty comboboxes.
EDIT2: No need to fetch value from Combobox, get it from Grid cell instead:
<DataGridView>.Item("Resolution", i).Value
When creating the columns don't forget to set a defaultvalue to the combobox, otherwise it might be Nothing:
comboCell.DisplayMember = "Name"
comboCell.ValueMember = "ID"
comboCell.Value = resolutions(0)