About datagrid-view in vb.net - vb.net

net.I created a checkbox column in data grid-view and call the checked value.If i unchecked and checked another row it shows error.Need to know how to write loop concept for this.

Here's how you should have a datagridview that has a checkbox column, and how you should access it:
Dim dt as New DataTable
dt.Columns.Add("Name")
dt.Columns.Add("Age", GetType(integer))
dt.Columns.Add("IsActiveMember", GetType(Boolean))
dt.Rows.Add("Siva", 32 , True)
dt.Rows.Add("Siva2", 32 , False)
dt.Rows.Add("Siva3", 33 , True)
yourdatagridview.DataSource = dt
i.e. you create a datatable to hold your data, fill it and bind it. the datagridview will create the columns automatically.
When you want to retrieve the checked boxes:
For Each ro as DataRow in DirectCast(yourdatagridview.DataSource, DataTable).Rows
If DirectCast(ro("IsActiveMember"), Boolean) Then
Messageox.Show(ro("Name") & " is an active member")
Else
Messageox.Show(ro("Name") & " is not an active member")
End If
Next ro
If you get tired of directcasting all the time, add a new DataSet object to your project and use a stringly typed dataset

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

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.

Database Lookup From ComboBox selection

I have a question about database values and how to determine the id of a value that has been changed by the user at some point.
As it is currently set up there is a combobox that is populated from a dataset, and subsequent text boxes whose text should be determined by the value chosen from that combobox.
So let's say for example you select 'Company A' from the combobox, I would like all the corresponding information from that company's row in the dataset to fill the textboxes (Name = Company A, Address = 123 ABC St., etc.,)
I am able to populate the combobox just fine. It is only however when I change the index of the combobox that this specific error occurs:
An unhandled exception of type 'System.Data.OleDb.OleDbException'
occurred in System.Data.dll
Additional information: Data type mismatch in criteria expression.
Here is the corresponding code:
Imports System.Data.OleDb
Public Class CustomerContact
Dim cn As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|datadirectory|\CentralDatabase.accdb;")
Dim da As New OleDbDataAdapter()
Dim dt As New DataTable()
Private Sub CustomerContact_Load(sender As Object, e As EventArgs) Handles MyBase.Load
cn.Open()
da.SelectCommand = New OleDbCommand("select * from Customers", cn)
da.Fill(dt)
Dim r As DataRow
For Each r In dt.Rows
cboVendorName.Items.Add(r("Name").ToString)
cboVendorName.ValueMember = "ID"
Next
cn.Close()
End Sub
Private Sub cboVendorName_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboVendorName.SelectedIndexChanged
cn.Open()
da.SelectCommand = New OleDbCommand("select * from Customers WHERE id='" & cboVendorName.SelectedValue & "'", cn)
da.Fill(dt)
Dim r As DataRow
For Each r In dt.Rows
txtNewName.Text = "Name"
txtAddress.Text = "Address"
Next
cn.Close()
End Sub
The error is caught at Line 24 of this code, at the second da.Fill(dt) . Now obviously from the exception I know that I am sending in a wrong datatype into the OleDbCommand, unfortunately I am a novice when it comes to SQL commands such as this. Also please keep in mind that I can't even test the second For loop, the one that is supposed to fill the Customer information into textboxes (for convenience I only copied the first two textboxes, of which there are nine in total). I am think I could use an If statement to determine if the row has been read, and from there populate the textboxes, but I will jump that hurdle when I can reach it.
Any guidance or suggestions would be much appreciated. Again I am a novice at managing a database and the code in question pertains to the project my current internship is having me write for them.
Since you already have all the data from that table in a DataTable, you dont need to run a query at all. Setup in form load (if you must):
' form level object:
Private ignore As Boolean
Private dtCust As New DataTable
...
Dim SQL As String = "SELECT Id, Name, Address, City FROM Customer"
Using dbcon = GetACEConnection()
Using cmd As New OleDbCommand(SQL, dbcon)
dbcon.Open()
dtCust.Load(cmd.ExecuteReader)
End Using
End Using
' pk required for Rows.Find
ignore = True
dtCust.PrimaryKey = New DataColumn() {dtCust.Columns(0)}
cboCust.DataSource = dtCust
cboCust.DisplayMember = "Name"
cboCust.ValueMember = "Id"
ignore = False
The ignore flag will allow you to ignore the first change that fires as a result of the DataSource being set. This will fire before the Display and Value members are set.
Preliminary issues/changes:
Connections are meant to be created, used and disposed of. This is slightly less true of Access, but still a good practice. Rather than connection strings everywhere, the GetACEConnection method creates them for me. The code is in this answer.
In the interest of economy, rather than a DataAdapter just to fill the table, I used a reader
The Using statements create and dispose of the Command object as well. Generally, if an object has a Dispose method, put it in a Using block.
I spelled out the columns for the SQL. If you don't need all the columns, dont ask for them all. Specifying them also allows me to control the order (display order in a DGV, reference columns by index - dr(1) = ... - among other things).
The important thing is that rather than adding items to the cbo, I used that DataTable as the DataSource for the combo. ValueMember doesn't do anything without a DataSource - which is the core problem you had. There was no DataSource, so SelectedValue was always Nothing in the event.
Then in SelectedValueChanged event:
Private Sub cboCust_SelectedValueChanged(sender As Object,
e As EventArgs) Handles cboCust.SelectedValueChanged
' ignore changes during form load:
If ignore Then Exit Sub
Dim custId = Convert.ToInt32(cboCust.SelectedValue)
Dim dr = dtCust.Rows.Find(custId)
Console.WriteLine(dr("Id"))
Console.WriteLine(dr("Name"))
Console.WriteLine(dr("Address"))
End Sub
Using the selected value, I find the related row in the DataTable. Find returns that DataRow (or Nothing) so I can access all the other information. Result:
4
Gautier
sdfsdfsdf
Another alternative would be:
Dim rows = dtCust.Select(String.Format("Id={0}", custId))
This would return an array of DataRow matching the criteria. The String.Format is useful when the target column is text. This method would not require the PK definition above:
Dim rows = dtCust.Select(String.Format("Name='{0}'", searchText))
For more information see:
Using Statement
Connection Pooling
GetConnection() method aka GetACEConnection

How to set a data grid column to be a drop down when binding to a table

I am able to create a data table and bind it to a datagridview. If possible I want something more sophisticated. I want to limit the first column to values 1 to 12 and the 2nd column to either AM or PM.
Thanks in advance.
mDataTable = GetTable()
DataGridView1.DataSource = mDataTable
Function GetTable() As DataTable
' Create new DataTable instance.
Dim table As New DataTable
' Create four typed columns in the DataTable.
table.Columns.Add("Hour", GetType(Integer))
table.Columns.Add("AM/PM", GetType(String))
table.Columns.Add("Delete", GetType(Boolean))
For i = 1 To mSchedules.Count
table.Rows.Add(SetAMPMHour(mSchedules(i - 1).ScheduleINetHour), SetAMPM(mSchedules(i - 1).ScheduleINetHour), False)
Next
Return table
End Function
If the combobox column is going to have fixed values, you can manually define those values either at design time or at runtime (althought both ways require you to set AutoGenerateColumns property to False before you bind your data to your grid).
If you want to do it by code, try this:
DataGridView1.AutoGenerateColumns = False
Dim hoursCol, timeOfDayCol As New DataGridViewComboBoxColumn
Dim deleteCol As New DataGridViewCheckBoxColumn
For i As Integer = 1 To 12
hoursCol.Items.Add(i)
Next
timeOfDayCol.Items.Add("AM")
timeOfDayCol.Items.Add("PM")
hoursCol.DataPropertyName = "Hour"
timeOfDayCol.DataPropertyName = "AM/PM"
deleteCol.DataPropertyName = "Delete"
DataGridView1.Columns.Add(hoursCol)
DataGridView1.Columns.Add(timeOfDayCol)
DataGridView1.Columns.Add(deleteCol)
mDataTable = GetTable()
DataGridView1.DataSource = mDataTable
If you want to do it in design time:
Create your Combobox Column...
...set DataPropertyName to the name of the column of the DataTable that will be binded to that GridView column, and in Collection define the values that will populate the ComboBox
Remember that, even if you define the values in design time, you have to programatically set AutoGenerateColumns to False before you set the data source. Also, if you create a ComboBox Column in design time, you'll have to fill it with String values.

vb.net working with multiple controls added at runtime

I am adding a tab page and datagridview to a Tab Control for every record in a datatable.
I would like to have a new Tab/DataGridView for each record (there will be ~3 for right now). I am declaring a new DataGridView D. How do I refer to these controls later?
I will want to do things like save changes in the datagridview to the database. Currently I can get the data on the screen and it looks good, but I believe I am not adding the DataGridView controls correctly because I keep re-using "D" as the control.
Dim dt As New DataTable
GetDataTable("SELECT * FROM aFeeTypes DescSeq", dt)
Dim i As Integer
'for each class in the datatable add a tab and a datagridview
For i = 0 To dt.Rows.Count - 1
Dim dr As DataRow
dr = dt.Rows(i)
Dim D = New DataGridView
D.Visible = True
Dim tp As New TabPage
tp.Name = "tp" & i
tp.Text = dr.Item("Desc2")
frmUI.tcFee.TabPages.Add(tp)
frmUI.tcFee.TabPages(i).Controls.Add(D)
dgv_Fill(D, "SELECT * FROM Fee WHERE ClassID=" & dr.Item("ClassID") & " ORDER BY Seq")
D.AutoResizeColumns()
D.Width = tp.Width
D.Height = tp.Height
Next i
this does not work:
With frmUI.Controls("D" & i)
.AutoResizeColumns()
.Width = tp.Width
.Height = tp.Height
End With
D is purely the variable name in the scope you are using it in.
You need to give the control a unique Name that yo can reference it by later.
The Name property can be used at run time to evaluate the object by
name rather than type and programmatic name. Because the Name property
returns a String type, it can be evaluated in case-style logic
statements (Select statement in Visual Basic, switch statement in
Visual C# and Visual C++).
I found a solution to the problem. The problem is that the new row has an autonumber ID.
When da.update(dt) occurs, the new row is inserted to the database. The database knows the new autonumber ID. However, the datatable does not.
For the datatable to know, the dataadapter is refilled. However, this causes the datatable to have all the old rows plus all the new rows and they all appear in the datagridview.
By clearing the old rows from the datatable, the fill refreshes all the data in the datatable and therefore refreshes the datagridview.
Public Sub dgv_AddRow(ByVal dgvName As String)
Dim dgv As DataGridView = colDataGridView.Item(dgvName)
Dim da As SqlDataAdapter = colDataAdapter.Item(dgvName)
Dim dr As DataRow = frmUI.allDataSet.Tables(dgvName).NewRow
'autopopulate the class id
dr("ClassID") = dgv.Parent.Tag
'add the new row
frmUI.allDataSet.Tables(dgvName).Rows.Add(dr)
'get the changed table
Dim dt As DataTable = frmUI.allDataSet.Tables(dgvName)
'inserts the new row to the database. concurrency violation
'will occur because datatable does not know the new Autonumber ID for the new row.
da.Update(dt)
'remove the datatable rows so they can be replaced using the adapter fill
'if rows are not cleared, following fill causes datatable to
'have existing rows plus all the rows again due to the fill
frmUI.allDataSet.Tables(dgvName).Rows.Clear()
'refill the adapter (refill the table)
'Everything you do to the underlying dataTable
'gets displayed instantly on the datagridview
da.Fill(frmUI.allDataSet, dgvName)
End Sub