Error trying to rebind DataSource to DataGridViewComboBoxCell? - vb.net

I have a DataGridView with two DataGridViewComboBoxColumns. I want to use the selected item in the first column to trigger a re-population of the items in the second column, on a per-row basis.
Here's the code I have so far. "addlInfoParentCat" identifies the first column, and currentRow.Cells.Item(1) is the DataGridViewComboBoxCell that I want to re-populate. ExtEventAdditionalInfoType is a type I defined that contains the string/value pairs.
Private Sub dgvAdditionalInfo_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvAdditionalInfo.CellValueChanged
Dim currentCell As DataGridViewCell
currentCell = Me.dgvAdditionalInfo.CurrentCell
If Not currentCell Is Nothing Then
If currentCell.OwningColumn.DataPropertyName = "addlInfoParentCat" Then
Dim parentTypeID As Integer = currentCell.Value
Dim currentRow As DataGridViewRow = Me.dgvAdditionalInfo.CurrentRow
Dim subtypeCell As DataGridViewComboBoxCell = currentRow.Cells.Item(1)
Dim theChildren As New List(Of ExtEventAdditionalInfoType)
theChildren = Custom_ExtEventAdditionalInfoType.GetChildrenOfThisParentOrderByTypeName(parentTypeID)
subtypeCell.DataSource = Nothing
subtypeCell.DataSource = theChildren
subtypeCell.DisplayMember = "ExtEventAdditionalInfoTypeDescr"
subtypeCell.ValueMember = "ID_ExtEventAdditionalInfoType"
End If
End If
End Sub
Basically what I see is that the binding works great the first time around. When I select a item in the first column, it populates the items correctly in the second. I can add rows to the DataGridView and repeat the process.
The problem comes when I try to change the first-column item after the second column has already been bound. I get an endless string of dialog boxes with the following:
System.ArgumentException: DataGridViewComboBoxCell value is not valid.
Any idea why this is happening? Thanks in advance!
UPDATE CodeByMoonlight's suggestion appears to work.
I clear the DataGridViewComboBoxCell's value before re-binding:
....
subtypeCell.DataSource = Nothing
subtypeCell.Value = Nothing 'here's the change
subtypeCell.DataSource = theChildren
....

Well, it looks like as soon as you remodify the first combo's value, you're invalidating the binding and datasource you used to populate the second combo, causing all the errors.

Related

How to get Design > Name property of DGV column from Header Text property

I am trying to go through un-checked items within a CheckedListBox1 and based on the values returned hide relevant columns within DataGridView1 but the issue is that the values displayed in CheckedListBox1 are the HeaderText property of DGV column and not the Name property which is required for hiding the column within DGV.
See below code:
For Each checked_item As Object In CheckedListBox1.Items
If Not CheckedListBox1.CheckedItems.Contains(checked_item) Then
DataGridView1.Columns("").Visible = False
End If
Next
Is there a way to retrieve "Name" property of DGV column when referencing the column's HeaderText property?
You don't need the column name to hide the column. You need the column. The name is just a means to get the column. The issue is the way you're populating your CheckedListBox. Displaying the HeaderText makes perfect sense, because that's what the user actually sees. What you should be doing is putting the columns themselves into the CheckedListBox and just displaying the HeaderText. That way, the items are the columns, e.g.
Dim columns = DataGridView1.Columns.Cast(Of DataGridViewColumn)().ToArray()
With CheckedListBox1
.DataSource = columns
.DisplayMember = NameOf(DataGridViewColumn.HeaderText)
End With
The code you posted then becomes this:
For i = 0 To CheckedListBox1.Items.Count - 1
Dim column = DirectCast(CheckedListBox1.Items(i), DataGridViewColumn)
column.Visible = CheckedListBox1.GetItemChecked(i)
Next
Note that you should generally set the DataSource last when binding but that doesn't seem to work with a CheckedListBox, which doesn't offically support data-binding. For that reason, the DataSource is set first.
EDIT:
I'm adding this after the comment was added to the question about the ItemCheck event and the checking of the items at startup. The key here is to not actually act on the event until the list has been initialised, i.e. all the items have been initially checked. One way to do that would be like so:
Private isLoaded As Boolean = False
Private Sub Form1_Load(...) Handles MyBase.Load
'Bind the data and check the items in the CheckedListBox here.
isLoaded = True
End Sub
Private Sub CheckedListBox1_ItemCheck(...) CheckedListBox1.ItemCheck
If isLoaded Then
'Act here.
End If
End Sub
The other way to is to prevent event being raised by not handling it while the initialisation is taking place. That can be done in a couple of ways but I'll leave that to you as an exercise if that's what you want to do.
As the ItemCheck event is raised before the state of an item changes, you will need to treat the current item differently to the other items. My loop above would become this:
For i = 0 To CheckedListBox1.Items.Count - 1
Dim column = DirectCast(CheckedListBox1.Items(i), DataGridViewColumn)
'For the item that is being checked/unchecked, use its new state.
'For other items, use their current state.
column.Visible = If(i = e.Index,
e.NewValue = CheckState.Checked,
CheckedListBox1.GetItemChecked(i))
Next
That said, if all items are initially checked and all columns are initially visible, it's only the current item that you need to care about, so there's no need for a loop at all:
Dim column = DirectCast(CheckedListBox1.Items(e.Index), DataGridViewColumn)
column.Visible = (e.NewValue = CheckState.Checked)
Try this:
For Each checked_item As Object In CheckedListBox1.Items
Dim oCol As DataGridViewColumn = DataGridView1.Columns _
.Cast(Of DataGridViewColumn)() _
.Where(Function(x) x.HeaderText = checked_item).SingleOrDefault()
If oCol IsNot Nothing Then oCol.Visible = _
Not CheckedListBox1.CheckedItems.Contains(checked_item)
Next
Note: System.Linq is required!
[EDIT]
this code is executed in the .ItemCheck event. When the form starts up
I loop through all available columns, populate CheckedBoxList1 and as
default they are un-checked but I want them checked as I want the
columns to be visible at start
If you would like to change the visibility of column of currently selected item in CheckListBox (in ItemCheck event), use this:
Dim oCol As DataGridViewColumn = DataGridView1.Columns _
.Cast(Of DataGridViewColumn)() _
.Where(Function(x) x.HeaderText = checked_item).SingleOrDefault()
If oCol IsNot Nothing Then oCol.Visible = _
Not CheckedListBox1.CheckedItems.Contains(CheckedListBox1.SelectedItem)
Do you see the difference?
The main difference is: there's no foreach loop ;)

Default values when adding a new record

I am trying to add a new record with the Binding Navigator Add button. I have three fields that can be defaulted on the new record. UserId, Start time and End Time. Using the code below I am able to set the Start and End time but not the User Combobox. Is it not possible to set this value?
I have tried setting the value in the BindingNavigatorAddNewItem_Click
Private Sub BindingNavigatorAddNewItem_Click(sender As Object, e As EventArgs) Handles BindingNavigatorAddNewItem.Click
'MsgBox("Add new Log")
Dim User As String = currUser.Name
Dim Index As Integer = cmbOperator.FindString(User)
cmbOperator.SelectedIndex = Index
cmbOperator.SelectedItem = User
dtpLogIn_Date.Value = currUser.Start_Time
dtpLogOut_Date.Value = currUser.End_Time
End Sub
Form after add new redraw
One solution is to set the cmbOperator index in the BindingSource_PositionChanged event. My guess is this fires after the record position changed. Then I wrapped it in code to check if the was a add new. Probably could have put it in the BindingNavigator_PositionChanged as well.
Dim varShiftKey = Shift_LogBindingSource.Current("Shift_Key")
If varShiftKey = -1 Then
Dim User As String = currUser.Name
Dim Index As Integer = cmbOperator.FindString(User)
cmbOperator.SelectedIndex = Index
End If

Making One Column of DataGridView AutoComplete for String Data

I am trying to set up a DataGridView in VB.Net where a single column, called "Supplier" acts as a TextBox with an AutoCompleteSource to help users with entering data. This is the code I put together to attempt to accomplish this:
Private Sub OrderData_EditingControlShowing(ByVal sender As Object, ByVal e As DataGridViewEditingControlShowingEventArgs) Handles orderData.EditingControlShowing
Dim colIndex = orderData.SelectedCells.Item(0).ColumnIndex
Dim headerText As String = orderData.Columns(colIndex).HeaderText
If headerText.Equals("Supplier") Then
Dim autoText As TextBox = TryCast(e.Control, TextBox)
If autoText IsNot Nothing Then
autoText.AutoCompleteMode = AutoCompleteMode.SuggestAppend
autoText.AutoCompleteSource = AutoCompleteSource.CustomSource
autoText.AutoCompleteCustomSource = FillTextBoxData("supplier_name", "suppliers")
End If
End If
End Sub
I kind of works. Unfortunately it adds AutoCompletes to every single editable column in the table, instead of just the "Supplier" column (column index 2).
What do I need to do to fix this?
EDIT: I modified the code a bit to try and fix the issue, and it kind of worked. If I don't selected the "Supplier" column first, then the other columns to not contain an AutoComplete. However if I go from the "Supplier" column to another column, then it contains an AutoComplete. How do I fix this?
When you do
Dim headerText As String = orderData.Columns(2).HeaderText
If headerText.Equals("Supplier") Then
That will be always true, since you are always getting the col 2 header no matter what cell is selected, you need to add that, something like
Dim colIndex = orderData.SelectedCells.Item(0).ColumnIndex
then you can use your code
Dim headerText As String = orderData.Columns(colIndex).HeaderText
Also add an Else clause to the If headerText.Equals("Supplier") Then statement. In the Else portion add the line autoText.AutoCompleteMode = AutoCompleteMode.None

Trying to move items from one to the other

]I am having strange problem when i try to move items from one grid view to another using a bindingsource it adding in an extra blank row some reason
Private Sub btnMove_Click(sender As System.Object, e As System.EventArgs) Handles btnMove.Click
Dim bs As New BindingSource
Dim a As Integer
Dim removeList As List(Of infoRemoteFiles) = fileList
For a = 0 To grdAvailFiles.SelectedRows.Count
grdProcessFiles.Rows.Add(grdAvailFiles.Rows(a).Cells("filename").Value)
removeList.RemoveAll(Function(p As infoRemoteFiles) p.filename = grdAvailFiles.Rows(a).Cells("filename").Value)
Next
bs.DataSource = removeList
grdAvailFiles.DataSource = bs
End Sub
please see what i mean its the row below what I have selected I dont no where its comming from thanks
The extra blank row is added automatically if you have the AllowUserToAddRows property set to True on the DataGridView control.
From the documentation:
If the DataGridView is bound to data, the user is allowed to add rows if both this property and the data source's IBindingList.AllowNew property are set to true.
On a separate note, you shouldn't have to create anew binding source (for the source data grid). Instead, modify the collection that is already bound to the DataSource property.
Try:
For A = 0 To grdAvailFiles.SelectedRows.Count - 1
Note the minus 1, because you're starting from zero.

VB.Net Custom ListBox output issue

Since i needed to disable (grey out) some items inside a ListBox, i'm using a Custom control that can be found here:
Here is my current code:
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dtp.Columns.Add("key")
dtp.Columns.Add("value")
PopulateDataTable(dtp, "myTxt")
_dataView = New DataView(dtp)
'Custom ListBox
List1.ValueMember = "key"
List1.DisplayMember = "value"
List1.DataSource = _dataView
'Legacy ListBox
List2.ValueMember = "key"
List2.DisplayMember = "value"
List2.DataSource = _dataView
UpdateLanguageMenu()
End Sub
Private Function PopulateDataTable(dt As DataTable, resTxt As String)
Using sw As New StringReader(My.Resources.ResourceManager.GetObject(resTxt))
Do
Dim line As String = sw.ReadLine
If line Is Nothing OrElse line.Trim = String.Empty Then Exit Do
Dim strArr() As String
strArr = line.Split(",")
Dim row As DataRow = dt.NewRow()
row("key") = strArr(0)
row("value") = strArr(1)
dt.Rows.Add(row)
Loop
sw.Close()
End Using
End Function
List1 is the Custom ListBox and List2 is the ListBox that comes with VS2012E.
I don't need List2, it's only there to test,
and at runtime, in List2 i get all my values loaded correctly, instead in List1 i get System.Data.DataRowView in all rows..
The strange thing is that, my txt i'm loading is like:
00A1,MyValue1
00A2,Myvalue2
00A3,MyValue3
I have also a Label, and when selecting items on the ListBox i have code to change the Label.Text to List.SelectedValue that is the first part before the comma.
And it get displayed in the label. Only items inside the Custom ListBox are not being displayed.
Populating List1 manually, instead using a DataTable, is working.
And since i'm a beginner i can't locate the problem.
I think your problem has to do with this line: string displayValue = GetItemText(item); in the control. This takes for granted that all items are strings. In your case it is a datarowview hence the result (drv.toString would return something like that). You need to convert "item" into a drv and set display value to be drvItem("value" or "key") instead. So it is basically not your code that is the problem, it is the control.
Actually... After reading the code in the control and not on the code project site, I realised that this line:
displayValue = GetItemText(item);
Doesn't even exist. It is exchanged with
item.ToString()
Which pretty much proves my theory.
Right, how to fix.
In:
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
You have this:
object item = this.Items[e.Index];
What you have to do is to convert item into a DataViewRow and assign value to a variable, something like this:
DataViewRow dvrItem = (DataViewRow)item;
String displayText = dvrItem("key"); or String displayText = dvrItem("value");
Then change all these:
e.Graphics.DrawString(item.ToString(), e.Font, SystemBrushes.GrayText, e.Bounds);
Into:
e.Graphics.DrawString(displayText, e.Font, SystemBrushes.GrayText, e.Bounds);