VS 2012, VB.net, Winforms (Desktop App)
When I change item of combobox (combo_company. SelectedIndexChanged event), I m getting error System.NullReferenceException.
I am trying to get value of combo_company.SelectedItem and combo_company.SelectedValue. I tried in two way to bind combobox, but what the problem is:
case 1 output:
combo_company.SelectedItem= output OK
combo_company.SelectedValue= System.NullReferenceException
case 2 output:
combo_company.SelectedItem= System.Data.DataRowview
combo_company.SelectedValue= output OK
common code for both case:
Dim da As New SqlDataAdapter
Dim cmd As New SqlCommand
Dim dt As New DataTable
cmd.Connection = con
Dim sql As String = "select company_name, company_id from tbl_company"
cmd = New SqlCommand(sql, con)
If con.State = ConnectionState.Open Then con.Close()
con.Open()
da = New SqlDataAdapter(cmd)
da.Fill(dt)
case 1 code:
combo_company.DisplayMember = "company_name"
combo_company.ValueMember = "company_id"
If dt.Rows.Count >= 1 Then
For i = 0 To dt.Rows.Count - 1
combo_company.Items.Add(dt.Rows(i).Item(0).ToString)
Next
End If
case 1 output:
combo_company.SelectedItem= output OK
combo_company.SelectedValue= System.NullReferenceException
case 2 code:
combo_company.DisplayMember = "company_name"
combo_company.ValueMember = "company_id"
combo_company.DataSource = dt
case 2 output:
combo_company.SelectedItem= System.Data.DataRowview
combo_company.SelectedValue= output OK
Your case 2 is what you should be doing and it is working exactly as it should. When you bind a DataTable to a WinForms control, the data actually comes from its DefaultView, which is a DataView. Each item in a DataView is a DataRowView, so the SelectedItem of the bound control will be a DataRowView, which is exactly what you're seeing.
You say that you want the SelectedItem but I don't think that's actually the case, because the SelectedItem is the whole row. I suspect that what you actually want is the company_name value from that row, i.e. the text displayed in the ComboBox. To get that you use the Text property of the ComboBox. The SelectedValue will give you the value from the column specified in the ValueMember and the Text will give you the value (as a String) of the column specified in the DisplayMember.
To elaborate on the first paragraph, complex data-binding in WinForms requires an object that implements either the IList interface or the IListSource interface. The IListSource interface has one method: GetList, which returns an IList. If you bind an IList then its items are used. If you bind an IListSource then its GetList method is called and the items of the IList returned are used. The DataTable class implements the IListSource interface and its GetList method returns its DefaultView property value, which is a DataView, which implements IList and contains DataRowView objects. When you bind a DataTable, the data you see is from its DefaultView, which is how you're able to filter and sort the bound data.
Related
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
I have been simple-binding the Text property of a TextBox for some time using a DataTable like so:
Dim dtbData As New DataTable
' populate table
Me.BindingSource.DataSource = dtbData
txtBox.DataBindings.Clear()
txtBox.DataBindings.Add(New Binding("Text", Me.BindingSource, "OrderNumber", True))
BindingNavigator1.BindingSource = Me.BindingSource
However, I am trying to change this to use a List(Of CustomObject) instead. I am simply using the dtbData to populate the List and set the DataSource to the List instead. I have set a breakpoint, and the DataTable and List are both populated. If I comment out the line that adds the Binding to the txtBox, then the BindingNavigator works as expected, but if I leave it alone, the binding does not work.
Here is what I've tried:
Dim lstData As New List(Of CustomObject)
Dim dtbData As New DataTable
' populate table
For Each row As DataRow In dtbData.Rows
Dim obj As New CustomObject
obj.OrderNumber = CInt(row("OrderNumber"))
lstData.Add(obj)
Next
Me.BindingSource.DataSource = lstData
txtBox.DataBindings.Clear()
txtBox.DataBindings.Add(New Binding("Text", Me.BindingSource, "OrderNumber", True))
BindingNavigator1.BindingSource = Me.BindingSource
It seems like it should work the same. Instead of a DataTable, the BindingSource is a List(Of CustomObject) though, so I'm not sure if I have to do something differently. The property names being bound are still the same. I have also tried Me.BindingSource.Current in the Binding but that doesn't work either. Can anyone tell me what I am doing incorrectly?
The CustomObject looks like so:
Public Class CustomObject
Public OrderNumber As Integer
End Class
EDIT
I added a Try/Catch block around the binding, and I see the following error message:
Cannot bind to the property or column OrderNumber on the DataSource. Parameter name: dataMember
I just don't see what is different between binding to the OrderNumber column of a DataTable or the OrderNumber property of a CustomObject.
I realized what the problem was. The OrderNumber wasn't defined as a Property of my CustomObject. I changed the definition to this:
Public Class CustomObject
Public Property OrderNumber As Integer
End Class
And now it allows me to bind the Text property of a TextBox to the OrderNumber property of my CustomObject.
I need a simple way to fill the value from database of the current item being added so that I can use it later :
'Filling the MAIN Categories part
Dim DataAdapterCatm As New MySqlDataAdapter("SELECT id,title From catM;", MySqlConnection)
Dim dsMc As New DataTable
DataAdapterCatm.Fill(dsMc)
For counter = 0 To dsMc.Rows.Count - 1
LbMCat.Items.Add(dsMc.Rows(counter).Item("title").ToString)
'LbMCat.ValueMember = dsMc.Rows(counter).Item("id").ToString
'The above line don't work, I need something to replace it
Next
You are misunderstanding what ValueMember means. ValueMember is a string which represents the property that you wish to use as the value. In your case it would be just "id". Notice that it is a property of the ListBox - it is not different for each time.
ValueMember and the related DisplayMember are only valid when using DataBinding with the DataSource property and it not valid when manually adding items to the ListBox via Items.Add. What you want is the following:
Dim DataAdapterCatm As New MySqlDataAdapter("SELECT id,title From catM;", MySqlConnection)
Dim dsMc As New DataTable
DataAdapterCatm.Fill(dsMc)
LbmCat.DataSource = dsMc;
LbMCat.ValueMember = "id";
LbmCat.DisplayMember = "title";
try this in place of both lines in your for loop:
LbMCat.Items.Add(New ListItem(dsMc.Rows(counter).Item("title").ToString(), dsMc.Rows(counter).Item("id").ToString())
In the constructor for ListItem you can specifiy both the value and the text for the ListItem
this is in my OfficeEquipment.Frm
Public Function Loadfunction()
dt = Functions.LoadData()
End Function
and this is the error for the above code
Warning 1 Function 'Loadfunction' doesn't return a value on all code paths. A null reference exception could occur at run time when the result is used. C:\Documents and Settings\IJDinglasan\My Documents\Visual Studio 2008\Projects\Electronic Office Equipment History\Electronic Office Equipment History\Update Office Equipment Profile.vb 9 5 Electronic Office Equipment History
this is in my module Functions
Private Function LoadData() As DataTable
Using sqlconn = New SqlClient.SqlConnection("server = SKPI-APPS1;" & _
"Database = EOEMS;integrated security=true")
Dim dt As New DataTable
sqlconn.Open()
Dim da As New SqlDataAdapter("SELECT * FROM tblOfficeEquipmentProfile", sqlconn)
da.Fill(dt)
Return dt
End Using
End Function
Basically just use the class and the function you have....
Dim dt As DataTable
dt = yourclass.LoadData()
Now dt is your table that you can use where you like.
MrCoDeXeR
EDITS
Where ever you have your function (LoadData) you need to reference that class and the function. For example: say my main class is: frmMain.vb and my class that has my function is: frmStudents I want to get that function.
So.... on frmMain.vb you need to declare another DataTable and assign it.... see below...
Dim dt As DataTable
dt = frmStudents.LoadData()
We call the function from frmStudents and fill our new table with our data. What you need to do is set a breakpoint on: dt = frmStudents.LoadData() and run your solution. When you get to that line press F-11 and see if it jumps over to you other class that has your function. then press F-11 and continue to step-through and see if it throws an error. On your: Return dt in your function, hover over this if it makes it this far and click the magnifying glass and see if data exists or has your column names, if so your good to go...
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