I am trying to user ReportViewer to run a report with a dataset filled from a datagridview. I can populate the dataset but I when I run the report, it's only showing the first row. I have done countless hours of searching on this and nothing seems to work.
I have make sure =First(...) was removed from my report field expression
I think the problem has something to do with the XML for the dataset but I am not very familiar with it.
This is just an example, once I figure this out I can transfer my knowledge to my actual project.
Here's what I have:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
dgv.Columns.Add("FirstName", "First Name")
dgv.Columns.Add("LastName", "Last Name")
dgv.Rows.Add("John", "Smith")
dgv.Rows.Add("Jane", "Doe")
createData()
End Sub
Private Sub createData()
Dim dt As New DataTable("namesTable")
dt.Columns.Add("FirstName")
dt.Columns.Add("LastName")
For Each row As DataGridViewRow In dgv.Rows
dt.Rows.Add(row.Cells(0).Value, row.Cells(1).Value)
MsgBox(row.Cells(0).Value & " " & row.Cells(1).Value)
Next
MsgBox(dt.Rows.Count)
DataSet1.Tables.Add(dt)
MsgBox(DataSet1.GetXml)
Dim DSReport As New ReportDataSource()
DSReport.Name = "DataSet1"
DSReport.Value = DataSet1.Tables("namesTable")
ReportViewer.ProcessingMode = Microsoft.Reporting.WinForms.ProcessingMode.Local
ReportViewer.LocalReport.ReportEmbeddedResource = "Report1.rdlc"
ReportViewer.LocalReport.DataSources.Clear()
ReportViewer.LocalReport.DataSources.Add(DSReport)
ReportViewer.LocalReport.Refresh()
ReportViewer.RefreshReport()
End Sub
As I'm adding to the dt, my MsgBox message shows me, correctly, my two names.
The MsgBox dt count is 2 so my two names added.
The Dataset XML shows this, which I think is the problem but I don't know how to fix it:
DataSet1 XML
The report then shows:
ReportViewer
Thanks for any help, this is driving me crazy.
Thanks!
It looks like your datasource is not right, why not just use your dataset that you just created. This code works for me:
ReportViewerHeader.LocalReport.DataSources.Clear()
Dim rds As New Microsoft.Reporting.WebForms.ReportDataSource("namesTable", dt)
ReportViewerHeader.LocalReport.ReportPath = "Report1.rdlc"
ReportViewerHeader.LocalReport.DataSources.Add(rds)
ReportViewerHeader.DataBind()
ReportViewerHeader.LocalReport.Refresh()
Related
VB.net app changes values in a datagridview programmatically. The values are all as they should be, but the save routine (dtShipments is the datatable that is the source for the datagridview)
Dim dtChanges As DataTable = dtShipments.getchanges()
If more than one has changed, dtChanges is always missing the last row.
In the routine that changes the cell values, I have tried DatagridView1.EndEdit and DatagridView1.CommitEdit, but the behavior is the same. I even tried adding a SendKeys.Send(vbTab) line, since hitting the tab key when making the changes manually is enough to get all the changes to show up in .GetChanges.
What am I missing?
code per request:
Private Sub btnAssign_Click(sender As Object, e As EventArgs) Handles btnAssign.Click
strModErr = "Form1_btnAssign_Click"
Dim sTruck As String = ""
Try
sTruck = Me.DataGridView2.SelectedCells(0).Value.ToString
For Each row As DataGridViewRow In DataGridView1.SelectedRows
row.Cells("Truck").Value = sTruck
Next
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
Catch ex As Exception
WriteErrorToLog(Err.Number, strModErr + " - " + Err.Description)
End Try
End Sub
Private Function SaveChanges() As Boolean
strModErr = "Form1_SaveChanges"
Dim Conn As New SqlConnection(My.Settings.SQLConnectionString)
Dim sSQL As String = "UPDATE fs_Shipments SET Truck = #Truck, Stop = #Stop WHERE SalesOrder = #SO"
Dim cmd As New SqlCommand(sSQL, Conn)
Dim sSO, sTruck As String
Dim iStop As Integer = 0
Try
DataGridView1.EndEdit()
DataGridView1.ClearSelection()
Dim dtChanges As DataTable = dtShipments.getchanges() 'DataRowState.Modified
If Not dtChanges Is Nothing Then
Conn.Open()
For Each row As DataRow In dtChanges.Rows
sSO = row("SalesOrder").ToString
sTruck = row("Truck").ToString
iStop = CInt(row("Stop").ToString)
With cmd.Parameters
.Clear()
.AddWithValue("#SO", sSO)
.AddWithValue("#Truck", sTruck)
.AddWithValue("#Stop", iStop)
End With
cmd.ExecuteNonQuery()
Next
End If
Return True
Catch ex As Exception
WriteErrorToLog(Err.Number, strModErr + " - " + Err.Description)
Return False
End Try
End Function
I am not exactly 100% sure why this happens. The problem appears to be specific to when the user “selects” the cells in the first grid, and then calls the SaveChanges code “BEFORE” the user has made another selection in the first grid. In other words, if the user “selects” the rows to “assign” the truck to in grid 1, then, “AFTER” the “selected” cells have been filled with the selected truck, THEN, the user selects some other cell in grid 1, THEN calls the save changes code. In that case the code works as expected.
All my attempts at committing the changes, either failed or introduced other issues. I am confident a lot of this has to do with the grids SelectedRows collection. IMHO, this looks like a risky way to set the cell values, I would think a more user-friendly approach may be to add a check box on each row and assign the truck values to the rows that are checked. But this is an opinion.
Anyway, after multiple attempts, the solution I came up with only further demonstrates why using a BindingSource is useful in many ways. In this case, it appears the DataTable is not getting updated with the last cell change. Again, I am not sure “why” this is, however since it appears to work using a BindingSource, I can only assume it has something to do with the DataTable itself. In other words, before the “Save” code is executed, we could call the tables AcceptChanges method, but then we would lose those changes. So that is not an option.
To help, below is a full (no-fluff) example. In the future, I highly recommend you pull out the parts of the code that do NOT pertain to the question. Example, all the code that saves the changes to the DB is superfluous in relation to the question… so remove it. The more unnecessary code you add to your question only increases the number of SO users that will “ignore” the question. If you post minimal, complete and working code that reproduces the problem so that users can simply copy and paste without having to add addition code or remove unnecessary code will increase you chances of getting a good answer. Just a heads up for the future.
The solution below simply adds a BindingSource. The BindingSource’s DataSource is the DataTable dtShipments then the BindingSource is used a DataSource to DataGridView1. Then in the SaveChanges method, “before” the code calls the dtShipment.GetChanges() method, we will call the BindingSources.ResetBinding() method which should complete the edits to the underlying data source, in this case the DataTable dtShipments.
If you create a new VS-VB winforms solution, drop two (2) grids and two (2) buttons onto the form as shown below, then change the button names to “btnAssign” and “btnApplyChanges.” The assign button will add the selected truck in grid two to the selected rows in grid one. The apply changes button simply resets the binding source and displays a message box with the number of rows that were changed in the dtShipments DataTable. It should be noted, that the code calls the dtShipments.AcceptChanges() method to clear the changes. Otherwise, the code will update changes that have already been made.
Dim dtShipments As DataTable
Dim dtTrucks As DataTable
Dim shipBS As BindingSource
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dtShipments = GetShipmentsDT()
shipBS = New BindingSource()
shipBS.DataSource = dtShipments
dtTrucks = GetTrucksDT()
dtShipments.AcceptChanges()
DataGridView1.DataSource = shipBS
DataGridView2.DataSource = dtTrucks
End Sub
Private Function GetShipmentsDT() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("ID", GetType(String))
dt.Columns.Add("Truck", GetType(String))
dt.Rows.Add(1)
dt.Rows.Add(2)
dt.Rows.Add(3)
dt.Rows.Add(4)
Return dt
End Function
Private Function GetTrucksDT() As DataTable
Dim dt = New DataTable()
dt.Columns.Add("Truck", GetType(String))
For index = 1 To 10
dt.Rows.Add("Truck" + index.ToString())
Next
Return dt
End Function
Private Sub btnAssign_Click(sender As Object, e As EventArgs) Handles btnAssign.Click
Dim sTruck = DataGridView2.SelectedCells(0).Value.ToString
'Dim drv As DataRowView
For Each row As DataGridViewRow In DataGridView1.SelectedRows
'drv = row.DataBoundItem
'drv("Truck") = sTruck
row.Cells("Truck").Value = sTruck
Next
'DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
'shipBS.ResetBindings(True)
'DataGridView1.CurrentCell = DataGridView1.Rows(0).Cells(0)
End Sub
Private Function SaveChanges() As Boolean
Try
shipBS.ResetBindings(True)
Dim dtChanges As DataTable = dtShipments.GetChanges()
If (dtChanges IsNot Nothing) Then
MessageBox.Show("There are " & dtChanges.Rows.Count & " rows changed in the data table")
' update sql DB
dtShipments.AcceptChanges()
End If
Return True
Catch ex As Exception
Return False
End Try
End Function
Private Sub btnApplyChanges_Click(sender As Object, e As EventArgs) Handles btnApplyChanges.Click
Dim ChangesMade As Boolean
ChangesMade = SaveChanges()
End Sub
Lastly, since you are using VB… I highly recommend that you set the “Strict” option to “ON.” When this option is ON, it will flag possible errors that you may be missing. To do this for all future VB solutions, open VS, close any solutions that may be open, then go to Tools->Options->Projects and Solutions->VB Defaults, then set “Option Strict” to “On.” The default setting is off.
I hope this makes sense and helps.
I am using a datagridview to display table data and changing values of a particular cell. Depending on requirement I may need to change such values for more than one row.
I am trying to use datagridview1.CellValueChanged to populate a Dataset (i.e. create a collection of changes made) and subsequently saving the changes by clicking on a command button.
My problem is that though for each change, the sub is being called ONLY the last change is being saved. I was thinking of using the Dataset to store multiple records where the values are changed and then SAVE all the rows in the Dataset in the database table (using Update).
Could there be some solution to my predicament.
PS. Before trying this (ADO.net dataset) I was updating a temporary table and then using that I was updating the database.
Grateful for a solution please.
Code:::
Private Sub dGridVwCreaCode_CellValueChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dGridVwCreaCode.CellValueChanged
Dim qryStr_CodeShtText_Changed As String
Dim var_CodeID_Changed As Long
var_CodeID_Changed = dGridVwCreaCode(e.ColumnIndex - 2, e.RowIndex).Value
qryStr_CodeShtText_Changed = "SELECT Code_ID, Code, Code_Descrip FROM Code_SAP " & _
"WHERE (Code_SAP.Code_ID = " & var_CodeID_Changed & ")"
var_CodeShtText_Changed = dGridVwCreaCode(e.ColumnIndex, e.RowIndex).Value.ToString
If Not CatGenieConnPublic.State = ConnectionState.Open Then
CatGenieConnPublic.Open()
End If
da_CodeShtText_Changed = New OleDb.OleDbDataAdapter(qryStr_CodeShtText_Changed, CatGenieConnPublic)
da_CodeShtText_Changed.Fill(ds_CodeShtText_Changed, "Code_SAP")
cb_CodeShtText_changed = New OleDb.OleDbCommandBuilder(da_CodeShtText_Changed)
ds_CodeShtText_Changed.Tables("Code_SAP").Rows(1).Item("Code_Descrip") = var_CodeShtText_Changed
To save the changes (following sub being called from a Button_Click):
Private Sub Save_Changed_CodeShtText()
da_CodeShtText_Changed.Update(ds_CodeShtText_Changed, "Code_SAP")
MsgBox("Changes saved to database....", vbOKOnly + vbInformation)
If CatGenieConnPublic.State = ConnectionState.Open Then
CatGenieConnPublic.Close()
End If
'SET BOOLEAN TO FALSE AS CHANGED VALUES HAVE BEEN SAVED
bool_CellVal_HasChanged = False
End Sub
PS. Somehow I am not able to place all the code lines together, pl pardon me.
What I was missing out on was incrementing the "row" count in the code line:
ds_CodeShtText_Changed.Tables("Code_SAP").Rows(rowNum_Increment - 1).Item("Code_Descrip") = var_CodeShtText_Changed
So every time the user changes data in the particular cell, rows number in incremented by "1" and is collected in the dataset.
I hope you can help as this little problem has been real headaches and after lengthy research I have found no viable solution.
My program uses an employee pay number to retrieve data from a database with nearly all forms being docked using WeifenLuo. When the pay number is changed, it clears the datasets with new information but we don't want the users to have to manually close all the open docked windows down - in effect each open window needs to refresh with new employee information. I have tried .refersh(), .invalidate() - which does not seem to reload the data within each combo/textbox.
After much research I tried this:
Private Sub tbPayNumber_KeyDown(sender As Object, e As KeyEventArgs) Handles tbPayNumber.KeyDown
If e.KeyCode = Keys.Enter Then
Call Paynumber_Authentication()
'close and re-open any active forms
Dim table As New DataTable
table.Columns.Add("Forms", GetType(Form))
For Each frm As Form In MdiChildren
table.Rows.Add(frm)
Next
For i = MdiChildren.Length - 1 To 0 Step -1
MdiChildren(i).Close()
Next
For i As Integer = 0 To table.Rows.Count - 1
Dim ResetForms = table.Rows(i)("Forms")
ResetForms.Show(pnlDockMain, DockState.Document)
Next
End If
End Sub
If I run the code as above I get the annoying MS designed error "Cannot access a disposed object."
If I change the last part of the code to:
For i As Integer = 0 To table.Rows.Count - 1
Dim ResetForms As New Form
ResetForms = table.Rows(i)("Forms")
ResetForms.Show(pnlDockMain, DockState.Document)
Next
I get the informous overload resolution error on the ResetForms.Show line - too many arguments.
Its worth saying that each form will get any information it needs as the form opens, which works correctly. Any help reloading each form would be appreciated as the only other way I can think of is to list every textbox on every form (over 30 forms) and give them new values individually - and manually. A lot of people talk about this problem, taking about the .IsDisposed method etc So I'm hoping you might find an elegant solution to this.
Thanks in advance, Shane
Private Sub tbPayNumber_KeyDown(sender As Object, e As KeyEventArgs) Handles tbPayNumber.KeyDown
If e.KeyCode = Keys.Enter Then
Call Paynumber_Authentication()
Call Load_Employee()
'close and re-open any active forms
Dim table As New DataTable
table.Columns.Add("Forms", GetType(String))
For Each frm As Form In MdiChildren 'load the name of each open form into a dataset
table.Rows.Add(frm.Name)
Next
For i = MdiChildren.Length - 1 To 0 Step -1 'closes all open docked windows
MdiChildren(i).Close()
Next
For i As Integer = 0 To table.Rows.Count - 1
Select Case table.Rows(i)("Forms")
Case "EmployeeDetails"
Dim LoadForm As New EmployeeDetails
EmployeeDetails.Show(pnlDockMain, DockState.Document)
Case "HomePage"
Dim LoadForm As New HomePage
HomePage.Show(pnlDockMain, DockState.Document)
Case "SOEInput"
Dim LoadForm As New SOEInput
SOEInput.Show(pnlDockMain, DockState.Document)
End Select
Next
End If
End Sub
So I can fill the combobox I have going in Visual Studio just how I want with ALL results with the following:
Dim pnum As New List(Of String)
For Each polnumber As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
pnum.Add(polnumber.Policy_Number)
Next
pnum.Reverse()
Me.Policy_NumberComboBox.DataSource = pnum
Awesome. Now I want to limit the pnum by taking what was input/selected from Insured_NameTextBox on the form and only returning the Policy_Number with a matching Insured_Name. I figure this can be performed with an If statement, but everything I try (stringcompare, InsuredName_TextBox = Me.InsuredDataSet.ClaimsInsured, etc.) either doesn't limit the results OR limits the results entirely so nothing shows up. Any idea where to put the If statement and what should be compared?
UPDATE:
I think there is some confusion so I'm including the entire load sub below:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'IncidentsDataSet.Claims_Incidents' table. You can move, or remove it, as needed.
Me.Claims_IncidentsTableAdapter.Fill(Me.IncidentsDataSet.Claims_Incidents)
'TODO: This line of code loads data into the 'InsuredDataSet.Claims_Insured' table. You can move, or remove it, as needed.
Me.Claims_InsuredTableAdapter.Fill(Me.InsuredDataSet.Claims_Insured)
'textbox autocomplete mode
Dim Iname As New AutoCompleteStringCollection()
For Each insname As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
Iname.Add(insname.Insured_Name)
Next
Me.Insured_NameTextBox.AutoCompleteCustomSource = Iname
'combobox autocomplete code (now sorting by last included!)
Dim pnum As New List(Of String)
For Each polnumber As InsuredDataSet.Claims_InsuredRow In Me.InsuredDataSet.Claims_Insured
pnum.Add(polnumber.Policy_Number)
Next
pnum.Reverse()
Me.Policy_NumberComboBox.DataSource = pnum
End Sub
Try something like this:
Me.Policy_NumberComboBox.DataSource = InsuredDataSet.Claims_Insured.Where(Function(r) r.Insured_Name = Insured_NameTextBox.Text).Select(Function(r) r.Policy_Number).Reverse()
We're getting closer. Based on the update to your question, you're running this code when the form loads. However, at the point where the form loads, your textbox will always be empty. What do you do when the value in the textbox changes, to re-filter your data?
This is C#
Me.InsuredDataSet.Claims_Insured.Where(x => x.Insured_Name == Insured_NameTextBox.Text);
I created a dataset which contains a table from my SQL Database named "BillHeaders"
Here's what my report viewer looks like, it will contain two reports. One that holds a job number and description, the other that holds a job number, description and contract number.
Here are my buttons that will execute either report
Here are my reports in my "Reports" folder. Both constructed and ready to go.
Unfortuantely when I use this code (respectively for each button):
Private Sub btnJobNoDesc_Click(sender As System.Object, e As System.EventArgs) Handles btnJobNoDesc.Click
'Reset the viewer
frmReportViewer.ReportViewer1.Reset()
'Dim the required datasources. Need a seperate ReportDatasource for each table in the report
Dim ReportDataSource1 As Microsoft.Reporting.WinForms.ReportDataSource = New Microsoft.Reporting.WinForms.ReportDataSource
'Give datasource name and set the specific datatables
ReportDataSource1.Name = "dsBillHeaders_BillHeaders"
ReportDataSource1.Value = frmReportViewer.dsBillHeaders.BillHeaders
'Clear the datasources in the report and add the new ones
frmReportViewer.ReportViewer1.LocalReport.DataSources.Clear()
frmReportViewer.ReportViewer1.LocalReport.DataSources.Add(ReportDataSource1)
frmReportViewer.ReportViewer1.LocalReport.ReportEmbeddedResource = "ReportViewer_Tutorial.rptJobNoDesc.rdlc"
frmReportViewer.ReportViewer1.RefreshReport()
frmReportViewer.Show()
End Sub
I get this result:
What am I doing wrong with my datasource?
For one you are not setting the processing mode to local
Here is code that works, when I create the report I make sure to have the datasource name in the report correspond to the table name of my dataset, not sure if that is required but it keeps things simpler.
_reportViewerNewContracts.ProcessingMode = Microsoft.Reporting.WinForms.ProcessingMode.Local
_reportViewerNewContracts.LocalReport.ReportPath = "Reports\NewContractsReport.rdlc"
Dim reportDataSource1 As New Microsoft.Reporting.WinForms.ReportDataSource()
reportDataSource1.Name = _contractDataset.NewContracts.TableName
'Name of the report dataset in our .RDLC file
reportDataSource1.Value = _contractDataset.NewContracts
Me._reportViewerNewContracts.LocalReport.DataSources.Add(reportDataSource1)
'fill data
_reportViewerNewContracts.RefreshReport()
(I have seen lots of sites that recommend the datasetname underscore tablename but that never worked for me)
Here's what I figured out:
Private Sub btnJobNoDesc_Click(sender As System.Object, e As System.EventArgs) Handles btnJobNoDesc.Click
'Reset the form
Dim rv As New frmReportViewer
'Reset the viewer
rv.ReportViewer1.Reset()
Dim ds As New dsBillHeaders
Dim ta As New dsBillHeadersTableAdapters.BillHeadersTableAdapter
ta.Fill(ds.BillHeaders)
rv.ReportViewer1.LocalReport.ReportEmbeddedResource = "ReportViewer_Tutorial.rptJobNoDesc.rdlc"
rv.ReportViewer1.LocalReport.DataSources.Clear()
Dim sReportDataSource As ReportDataSource
sReportDataSource = New ReportDataSource()
sReportDataSource.Name = "dsBillHeaders"
sReportDataSource.Value = ds.BillHeaders
rv.ReportViewer1.LocalReport.DataSources.Add(sReportDataSource)
rv.ReportViewer1.RefreshReport()
rv.Show()
End Sub
Using this code, I can generate multiple forms just by changing the name of the report on the ReportEmbeddedResource, sReportDataSource.Value and the dataset if necessary.