Fast Export of Large Datatable to Excel Spreadsheet in VB.Net - vb.net

I have an interesting conundrum here, how do I quickly (under 1 minute) export a large datatable (filled from SQL, 35,000 rows) into an Excel spreadsheet for users. I have code in place that can handle the export, and while nothing is "wrong" with the code per se, it is infuriatingly slow taking 4 minutes to export the entire file (sometimes longer if a user has less RAM or is running more on their system). Sadly, this is an improvement over the 10+ minutes it used to take using our old method. Simply put, can this be made any faster, without using 3rd party components? If so, how? My code is as follows, the slow down occurs between messageboxes 6 and 7 where each row is written. Thank you all for taking the time to take a look at this:
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnJeffTest.Click
Test(MySPtoExport)
End Sub
Private Sub Test(ByVal SQL As String)
'Declare variables used to execute the VUE Export stored procedure
MsgBox("start stop watch")
Dim ConnectionString As New SqlConnection(CType(ConfigurationManager.AppSettings("ConnString"), String))
Dim cmdSP As New SqlClient.SqlCommand
Dim MyParam As New SqlClient.SqlParameter
Dim MyDataAdapter As New SqlClient.SqlDataAdapter
Dim ExportDataSet As New DataTable
Dim FilePath As String
MsgBox("stop 1 - end of declare")
Try
' open the connection
ConnectionString.Open()
' Use the connection for this sql command
cmdSP.Connection = ConnectionString
'set this command as a stored procedure command
cmdSP.CommandType = CommandType.StoredProcedure
'get the stored procedure name and plug it in
cmdSP.CommandText = SQL
'Add the Start Date parameter if required
Select Case StDt
Case Nothing
' there's no parameter to add
Case Is = 0
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add("#StartDate", SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtStartDate.Text
End Select
MsgBox("stop 2 - sql ready")
'Add the End Date parameter if required
Select Case EdDt
Case Nothing
' there's no parameter to add
Case Is = 0
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add("#EndDate", SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtEndDate.Text
End Select
'Add the single parameter 1 parameter if required
Select Case SPar1
Case Is = Nothing
' there's no parameter to add
Case Is = ""
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add(SPar1, SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtSingleReportCrt1.Text
End Select
'Add the single parameter 2 parameter if required
Select Case Spar2
Case Is = Nothing
' there's no parameter to add
Case Is = ""
' there's no parameter to add
Case Else
'add the parameter name, it's direction and its value
MyParam = cmdSP.Parameters.Add(Spar2, SqlDbType.VarChar)
MyParam.Direction = ParameterDirection.Input
MyParam.Value = Me.txtSingleReportCrt2.Text
End Select
MsgBox("stop 3 - params ready")
'Prepare the data adapter with the selected command
MyDataAdapter.SelectCommand = cmdSP
' Set the accept changes during fill to false for the NYPDA export
MyDataAdapter.AcceptChangesDuringFill = False
'Fill the Dataset tables (Table 0 = Exam Eligibilities, Table 1 = Candidates Demographics)
MyDataAdapter.Fill(ExportDataSet)
'Close the connection
ConnectionString.Close()
'refresh the destination path in case they changed it
SPDestination = txtPDFDestination.Text
MsgBox("stop 4 - procedure ran, datatable filled")
Select Case ExcelFile
Case True
FilePath = SPDestination & lblReportName.Text & ".xls"
Dim _excel As New Microsoft.Office.Interop.Excel.Application
Dim wBook As Microsoft.Office.Interop.Excel.Workbook
Dim wSheet As Microsoft.Office.Interop.Excel.Worksheet
wBook = _excel.Workbooks.Add()
wSheet = wBook.ActiveSheet()
Dim dt As System.Data.DataTable = ExportDataSet
Dim dc As System.Data.DataColumn
Dim dr As System.Data.DataRow
Dim colIndex As Integer = 0
Dim rowIndex As Integer = 0
MsgBox("stop 5 - excel stuff declared")
For Each dc In dt.Columns
colIndex = colIndex + 1
_excel.Cells(1, colIndex) = dc.ColumnName
Next
MsgBox("stop 6 - Header written")
For Each dr In dt.Rows
rowIndex = rowIndex + 1
colIndex = 0
For Each dc In dt.Columns
colIndex = colIndex + 1
_excel.Cells(rowIndex + 1, colIndex) = dr(dc.ColumnName)
Next
Next
MsgBox("stop 7 - rows written")
wSheet.Columns.AutoFit()
MsgBox("stop 8 - autofit complete")
Dim strFileName = SPDestination & lblReportName.Text & ".xls"
If System.IO.File.Exists(strFileName) Then
System.IO.File.Delete(strFileName)
End If
MsgBox("stop 9 - file checked")
wBook.SaveAs(strFileName)
wBook.Close()
_excel.Quit()
End Select
MsgBox("File " & lblReportName.Text & " Exported Successfully!")
'Dispose of unneeded objects
MyDataAdapter.Dispose()
ExportDataSet.Dispose()
StDt = Nothing
EdDt = Nothing
SPar1 = Nothing
Spar2 = Nothing
MyParam = Nothing
cmdSP.Dispose()
cmdSP = Nothing
MyDataAdapter = Nothing
ExportDataSet = Nothing
Catch ex As Exception
' Something went terribly wrong. Warn user.
MessageBox.Show("Error: " & ex.Message, "Stored Procedure Running Process ", _
MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
'close the connection in case is still open
If Not ConnectionString.State = ConnectionState.Closed Then
ConnectionString.Close()
ConnectionString = Nothing
End If
' reset the fields
ResetFields()
End Try
End Sub

Even though the question was asked several years ago, I thought I would add my solution since the question was posed in VB and the "best answer" is in C#. This solution writes 22,000+ rows (1.9MB) in 4 seconds on an i7 System w/ 16GB RAM.
Imports Excel = Microsoft.Office.Interop.Excel
Public Class Main
Private Sub btnExportToExcel(sender As Object, e As EventArgs) Handles btnExpToExcel.Click
'Needed for the Excel Workbook/WorkSheet(s)
Dim app As New Excel.Application
Dim wb As Excel.Workbook = app.Workbooks.Add()
Dim ws As Excel.Worksheet
Dim strFN as String = "MyFileName.xlsx" 'must have ".xlsx" extension
'Standard code for filling a DataTable from SQL Server
Dim strSQL As String = "My SQL Statement for the DataTable"
Dim conn As New SqlConnection With {.ConnectionString = "My Connection"}
Dim MyTable As New DataTable
Dim cmd As New SqlCommand(strSQL, conn)
Dim da As New SqlDataAdapter(cmd)
da.Fill(MyTable)
'Add a sheet to the workbook and fill it with data from MyTable
'You could create multiple tables and add additional sheets in a loop
ws = wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count))
DataTableToExcel(MyTable, ws, strSym)
wb.SaveAs(strFN) 'save and close the WorkBook
wb.Close()
MsgBox("Export complete.")
End Sub
Private Sub DataTableToExcel(dt As DataTable, ws As Excel.Worksheet, TabName As String)
Dim arr(dt.Rows.Count, dt.Columns.Count) As Object
Dim r As Int32, c As Int32
'copy the datatable to an array
For r = 0 To dt.Rows.Count - 1
For c = 0 To dt.Columns.Count - 1
arr(r, c) = dt.Rows(r).Item(c)
Next
Next
ws.Name = TabName 'name the worksheet
'add the column headers starting in A1
c = 0
For Each column As DataColumn In dt.Columns
ws.Cells(1, c + 1) = column.ColumnName
c += 1
Next
'add the data starting in cell A2
ws.Range(ws.Cells(2, 1), ws.Cells(dt.Rows.Count, dt.Columns.Count)).Value = arr
End Sub
End Class
Hope it helps.

As when using VBA to automate Excel, you can assign an array directly to the value of a Range object: this is done as a single operation, so you remove the overhead associated with making multiple calls across the process boundaries between your .Net code and the Excel instance.
Eg, see the accepted answer here: Write Array to Excel Range

The answer from CPRouse worked for me except that it left off the last row of data. In the Private Sub DataTableToExcel function, I added 1 to the rows.count on this line and it wrote all the records. ws.Range(ws.Cells(2, 1), ws.Cells(dt.Rows.Count + 1, dt.Columns.Count)).Value = arr

Here is a piece of my own code that performs a very fast export of data from a DataTable to an Excel sheet (use the "Stopwatch" object to compare the speed and let me a comment):
Dim _excel As New Excel.Application
Dim wBook As Excel.Workbook
Dim wSheet As Excel.Worksheet
wBook = _excel.Workbooks.Add()
wSheet = wBook.ActiveSheet()
Dim dc As System.Data.DataColumn
Dim colIndex As Integer = 0
Dim rowIndex As Integer = 0
'Nombre de mesures
Dim Nbligne As Integer = DtMesures.Rows.Count
'Ecriture des entêtes de colonne et des mesures
'(Write column headers and data)
For Each dc In DtMesures.Columns
colIndex = colIndex + 1
'Entête de colonnes (column headers)
wSheet.Cells(1, colIndex) = dc.ColumnName
'Données(data)
'You can use CDbl instead of Cobj If your data is of type Double
wSheet.Cells(2, colIndex).Resize(Nbligne, ).Value = _excel.Application.transpose(DtMesures.Rows.OfType(Of DataRow)().[Select](Function(k) CObj(k(dc.ColumnName))).ToArray())
Next

We had a VB.NET app that did exactly this, and took even longer for our users who were on slow PC's... sometimes 15 minutes.
The app is now an ASP/VB.NET app which simply builds an HTML table and outputs the result as an .xls extension... excel is able to read the HTML table and parse it into a grid format. You can still pass in XML for formatting and options, horizontal pane locking, etc.
If you don't have the option of using ASP.NET... try looking into a way to build an HTML table string and have excel parse & populate for you... much faster! I'm sure excel can parse other types as well.... XML, Arrays, HTML, etc... all would be quicker than manually building each row through VB.NET objects.

Related

Retrieving MS Access Database to vb6 and filter data using 2 DTpicker for the sum rate

Private Sub Command5_Click()
Dim li As ListItem
Dim lngRunningTotal As Long
For Each li In ListView1.ListItems
lngRunningTotal = lngRunningTotal + CLng(li.SubItems(6)) 'calculate all the in Total min column
Next
sumText.Text = CStr(lngRunningTotal) 'display total
End Sub
I want to filter data using 2 DTpicker for the sum rate.
Here's how you can do it with the code you posted:
Private Sub Command5_Click()
Dim li As ListItem
Dim lngRunningTotal As Long
Dim iLogDateIndex As Integer
Dim iMinutesIndex As Integer
iLogDateIndex = 5
iMinutesIndex = 6
For Each li In ListView1.ListItems
If CDate(li.SubItems(iLogDateIndex)) >= DTPickerStart And CDate(li.SubItems(iLogDateIndex)) <= DTPickerEnd Then
lngRunningTotal = lngRunningTotal + CInt(li.SubItems(iMinutesIndex)) ' calculate all the in Total min column
End If
Next
' Display total
sumText.Text = CStr(lngRunningTotal)
End Sub
The following demonstrates how to filter your data using an SQL Statement:
Private Sub cmdQuery_Click()
Dim objAdoConnection As New ADODB.Connection
Dim objRecordset As ADODB.Recordset
Dim sConnectionString As String
Dim sSQLStatement As String
Dim sDatabaseFile As String
Dim itm As ListItem
' Path to Access database
sDatabaseFile = "C:\Temp\Stack\ADO\Database.accdb"
' Connection string
sConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & sDatabaseFile
' Open Connection
objAdoConnection.Open sConnectionString
' Open recordset with SQL query
sSQLStatement = "SELECT * FROM TimeCards WHERE WorkDate BETWEEN #" & DTPickerStart & "# AND #" & DTPickerEnd & "#"
Set objRecordset = objAdoConnection.Execute(sSQLStatement)
' Clear ListView
ListView1.ListItems.Clear
' Move Recordset to first record
objRecordset.MoveFirst
' Display record data
Do While Not objRecordset.EOF
' Add record to ListView
Set itm = ListView1.ListItems.Add(, , objRecordset.Fields("ID"))
itm.SubItems(1) = objRecordset.Fields("EmployeeID")
itm.SubItems(2) = objRecordset.Fields("WorkDate")
itm.SubItems(3) = objRecordset.Fields("WorkHours")
' Move to next record
objRecordset.MoveNext
Loop
' Close connection and release objects
objAdoConnection.Close
Set objRecordset = Nothing
Set objAdoConnection = Nothing
End Sub
I made a quick UI with two DTPicker controls and a Query button. The results get shown into a ListView control:
I also created a simple TimeCards table with the following data for testing:
You can research SQL Statements on the internet and find many ways you can filter your data.

How to put data of MS excel of one column inside array in vb.net

I have data on my MS.Excel spreadsheet which contain different column (Sn , Amount and tech id). I am trying to put all the data of tech id on tech id in array like :-
mydata = [43219 , 43220 , 43221 , 43222 ,43223 ,43224 , 43225 ]
My code of only one main processing function:-
Importing :-
Imports System.IO
Imports System.Data.OleDb
main processing function:-
Dim conString1 As String
Dim Mydata(200) As Integer
Dim connection As OleDbConnection
Dim adapter As OleDbDataAdapter
Private Sub LoadData(conStr As String)
con = New OleDbConnection(conStr)
Dim query As String = "SELECT * FROM [Sheet0$]"
adapter = New oleDbDataAdapter(query, connection)
'Putting data indide array
'For intCount = 0 To lengthofcolumn
'Mydata(intCount) = ?
'Next intCount
Debug.Print(adapter)
End Sub
Calling :-
conString1 = String.Format("Provider = Microsoft.Jet.OLEDB.4.0;Data Source = '{0}'; Extended Properties = Excel 8.0", 'F:\MicroTest\data\log.xlsx)')
LoadData(conString1)
I am a student , I am learning so please help ,I did't find this solution , Mostly I found solution of viewing excel data in datagrid
My test data was in B2:B8.
You will need to add the Reference: Microsoft Excel 14.0 Object Library
Dim oExcel As New Microsoft.Office.Interop.Excel.Application
oExcel.Workbooks.Open("C:\TEMP\test_data.xlsx")
Dim oSheet As Microsoft.Office.Interop.Excel.Worksheet = oExcel.Sheets(1)
' I would use list instead of an array.
Dim oTest As New List(Of String)
For Each oValue As String In oSheet.Range("B2:B8").Value2
oTest.Add(oValue)
Next
' Using an array
Dim oData(200) As Integer
Dim iCounter As Integer = 0
For Each oValue As String In oSheet.Range("B2:B8").Value2
oData(iCounter) = CType(oValue, Integer)
iCounter += 1
Next
oExcel.Quit()
I think your approach is good, accessing the file with OleDB and not openning an instance of Excel.
I used a DataReader and DataTable to collect and hold the data in memory.
The Using...End Using blocks ensure your objects that have a Dispose method are closed and disposed properly even if there is an error.
Private Sub LoadData()
Dim dt As New DataTable()
Dim conStr As String = "Your connection string"
Using con As New OleDbConnection(conStr)
Dim query As String = "SELECT * FROM [Sheet1$]"
Using cmd As New OleDbCommand(query, con)
con.Open()
Using dr As OleDbDataReader = cmd.ExecuteReader()
dt.Load(dr)
End Using
End Using
End Using
'The number of rows in the DataTable less the first 2 rows which are title and blank
'and subtract 1 because vb.net arrays are defined array(upper bound)
Dim arraySize As Integer = dt.Rows.Count - 3
Dim myData(arraySize) As Integer
Dim arrayIndex As Integer = 0
'Putting data indide array
For rowIndex As Integer = 2 To dt.Rows.Count - 1
myData(arrayIndex) = CInt(dt.Rows(rowIndex)(3)) '3 is the index of the TechID column
arrayIndex += 1
Next
'Checking the array - delete in final version
'I used i as a variable name because this is a very tiny
'loop and will be deleted eventually. Otherwise, I would
'have used a more descriptive name.
For Each i As Integer In myData
Debug.Print(i.ToString)
Next
End Sub

error Public member 'Value' on type 'Integer' not found. exporting data to excel from datagridview including images

I have a datagrid which has two image column. I want to export the data to excel. And using this code
SaveFileDialog1.Filter = "Excel Files (*.xlsx*)|*.xlsx"
SaveFileDialog1.ShowDialog()
Dim filename As String = SaveFileDialog1.FileName
'verfying the datagridview having data or not
If ((DataGridView1.Columns.Count = 0) Or (DataGridView1.Rows.Count = 0)) Then
Exit Sub
End If
'Creating dataset to export
Dim dset As New DataSet
'add table to dataset
dset.Tables.Add()
'add column to that table
For i As Integer = 0 To DataGridView1.ColumnCount - 1
dset.Tables(0).Columns.Add(DataGridView1.Columns(i).HeaderText)
Next
'add rows to the table
Dim dr1 As DataRow
For i As Integer = 0 To DataGridView1.RowCount - 1
dr1 = dset.Tables(0).NewRow
For j As Integer = 0 To DataGridView1.Columns.Count - 1
Dim cj = DataGridView1.Rows(i).Cells(j).Value
If (cj.GetType = GetType(Byte())) Then
'Error = Publicmember 'Value' on type 'Integer' not found.
Dim data As Byte() = DirectCast(cj.Value, Byte())
Dim ms As New System.IO.MemoryStream(data)
Dim k As System.Drawing.Image = System.Drawing.Image.FromStream(ms)
dr1(j) = k
Else
dr1(j) = DataGridView1.Rows(i).Cells(j).Value
End If
Next
dset.Tables(0).Rows.Add(dr1)
Next
Dim excel As New Excel.Application()
Dim wBook As Microsoft.Office.Interop.Excel.Workbook
Dim wSheet As Microsoft.Office.Interop.Excel.Worksheet
wBook = excel.Workbooks.Add()
wSheet = wBook.ActiveSheet()
Dim dt As System.Data.DataTable = dset.Tables(0)
Dim dc As System.Data.DataColumn
Dim dr As System.Data.DataRow
Dim colIndex As Integer = 0
Dim rowIndex As Integer = 0
For Each dc In dt.Columns
colIndex = colIndex + 1
excel.Cells(1, colIndex) = dc.ColumnName
Next
For Each dr In dt.Rows
rowIndex = rowIndex + 1
colIndex = 0
For Each dc In dt.Columns
colIndex = colIndex + 1
excel.Cells(rowIndex + 1, colIndex) = dr(dc.ColumnName)
Next
Next
wSheet.Columns.AutoFit()
Dim strFileName As String = filename
Dim blnFileOpen As Boolean = False
Try
Dim fileTemp As System.IO.FileStream = System.IO.File.OpenWrite(strFileName)
fileTemp.Close()
Catch ex As Exception
blnFileOpen = False
End Try
If System.IO.File.Exists(strFileName) Then
System.IO.File.Delete(strFileName)
End If
wBook.SaveAs(strFileName)
excel.Workbooks.Open(strFileName)
excel.Visible = True
It is giving me error Public member 'Value' on type 'Integer' not found. Though this condition works with itextsharp i mean for PDF creation time. please help me. if remove that condition and run the code then it will create an excel file with image column as 'System.Byte[]'.
I get the feeling that you think that you're storing the grid cell in cj and then getting the Value of that cell but you have actually already got the Value of the cell and put that into cj. Get rid of the Value property access on the following line and your code will likely work:
Dim cj = DataGridView1.Rows(i).Cells(j).Value
By the way, don't do this:
If (cj.Value.GetType = GetType(Byte())) Then
It would be preferable to do this in isolation:
If TypeOf cj.Value Is Byte() Then
but, given that you're going to cast as that type if it is then you should use TryCast instead:
Dim data As Byte() = TryCast(cj.Value, Byte())
If data IsNot Nothing Then
'Use data here.
End If
TryCast is like DirectCast except that it returns Nothing if the cast fails rather than throwing an exception.

Datatable columns and rows are in a wrong order

I'm trying to place some information from a spreadsheet using EPPLUS into a databale which will be exported to sql server afterwards.
The Problem I am having is that when I create the datatable columns, and then start inserting the rows, the end result shows a misalignment between row information and column headings.
Here is the code:
Sub ConvertPackage2DT()
Dim FILENAME As String = "C:\Data.xlsx"
Dim flInfo As New FileInfo(FILENAME)
_package = New ExcelPackage(flInfo) 'load the excel file
Dim strSheetName As String = "Radiology"
Dim Conn As SqlConnection = New SqlConnection("Data Source=dwsqlserver1\instance3;Initial Catalog=LocalDatasets_Dev;Integrated Security=True") 'connection to server end
Dim DTExport As New DataTable
If Not IsNothing(_package) Then
Dim _workbook As ExcelWorkbook = _package.Workbook
Dim _sheets As ExcelWorksheets = _workbook.Worksheets
Dim sh As ExcelWorksheet = _sheets.Item(strSheetName)
If _package.Workbook.Worksheets.Count <> 0 Then
For Each _sheet In _sheets
Dim SHEET_NAME As String = _sheet.Name
Dim tbl As New DataTable
Dim hasHeader = True ' adjust accordingly '
For ColNum = 1 To sh.Dimension.End.Column
tbl.Columns.Add(sh.Cells(1, ColNum).Value)
Next
System.Diagnostics.Debug.Print(tbl.Columns.Count)
For rowNum = 2 To sh.Dimension.End.Row
Dim v(sh.Dimension.End.Column - 1) As Object
For X = 0 To sh.Dimension.End.Column - 2
v(X) = sh.Cells(rowNum, X + 1).Value
Next
tbl.Rows.Add(v)
'tbl.Rows.Add(wsRow.Select(Function(cell) cell.Text).ToArray)
Next
Conn.Open()
Using bulkcopy As SqlBulkCopy = New SqlBulkCopy(Conn)
bulkcopy.DestinationTableName = "[LocalDatasets_Dev].[dbo].[" & GetNewTableName(6) & "]"
bulkcopy.WriteToServer(tbl)
End Using
Conn.Close()
MsgBox("Complete")
Exit Sub
Next _sheet
End If
End If
End Sub
End Module
Can anyone help? Thank you kindly

How to export datagridview to excel using vb.net?

I have a datagridview in vb.net that is filled up from the database. I've researched and I found out that there is no built in support to print directly from datagridview. I don't want to use crystal report because I'm not familiar with it.
I'm planning to export it to excel to enable me to generate report from the datagridview.
Can you provide me ways to do this?
Code below creates Excel File and saves it in D: drive
It uses Microsoft office 2007
FIRST ADD REFERRANCE (Microsoft office 12.0 object library ) to your project
Then Add code given bellow to the Export button click event-
Private Sub Export_Button_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles VIEW_Button.Click
Dim xlApp As Microsoft.Office.Interop.Excel.Application
Dim xlWorkBook As Microsoft.Office.Interop.Excel.Workbook
Dim xlWorkSheet As Microsoft.Office.Interop.Excel.Worksheet
Dim misValue As Object = System.Reflection.Missing.Value
Dim i As Integer
Dim j As Integer
xlApp = New Microsoft.Office.Interop.Excel.ApplicationClass
xlWorkBook = xlApp.Workbooks.Add(misValue)
xlWorkSheet = xlWorkBook.Sheets("sheet1")
For i = 0 To DataGridView1.RowCount - 2
For j = 0 To DataGridView1.ColumnCount - 1
For k As Integer = 1 To DataGridView1.Columns.Count
xlWorkSheet.Cells(1, k) = DataGridView1.Columns(k - 1).HeaderText
xlWorkSheet.Cells(i + 2, j + 1) = DataGridView1(j, i).Value.ToString()
Next
Next
Next
xlWorkSheet.SaveAs("D:\vbexcel.xlsx")
xlWorkBook.Close()
xlApp.Quit()
releaseObject(xlApp)
releaseObject(xlWorkBook)
releaseObject(xlWorkSheet)
MsgBox("You can find the file D:\vbexcel.xlsx")
End Sub
Private Sub releaseObject(ByVal obj As Object)
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj)
obj = Nothing
Catch ex As Exception
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
Excel Method
This method is different than many you will see. Others use a loop to write each cell and write the cells with text data type.
This method creates an object array from a DataTable or DataGridView and then writes the array to Excel. This means I can write to Excel without a loop and retain data types.
I extracted this from my library and I think I changed it enough to work with this code only, but more minor tweaking might be necessary. If you get errors just let me know and I'll correct them for you. Normally, I create an instance of my class and call these methods. If you would like to use my library then use this link to download it and if you need help just let me know.
https://zomp.co/Files.aspx?ID=zExcel
After copying the code to your solution you will use it like this.
In your button code add this and change the names to your controls.
WriteDataGrid("Sheet1", grid)
To open your file after exporting use this line
System.Diagnostics.Process.Start("The location and filename of your file")
In the WriteArray method you'll want to change the line that saves the workbook to where you want to save it. Probably makes sense to add this as a parameter.
wb.SaveAs("C:\MyWorkbook.xlsx")
Public Function WriteArray(Sheet As String, ByRef ObjectArray As Object(,)) As String
Try
Dim xl As Excel.Application = New Excel.Application
Dim wb As Excel.Workbook = xl.Workbooks.Add()
Dim ws As Excel.Worksheet = wb.Worksheets.Add()
ws.Name = Sheet
Dim range As Excel.Range = ws.Range("A1").Resize(ObjectArray.GetLength(0), ObjectArray.GetLength(1))
range.Value = ObjectArray
range = ws.Range("A1").Resize(1, ObjectArray.GetLength(1) - 1)
range.Interior.Color = RGB(0, 70, 132) 'Con-way Blue
range.Font.Color = RGB(Drawing.Color.White.R, Drawing.Color.White.G, Drawing.Color.White.B)
range.Font.Bold = True
range.WrapText = True
range.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter
range.VerticalAlignment = Excel.XlVAlign.xlVAlignCenter
range.Application.ActiveWindow.SplitColumn = 0
range.Application.ActiveWindow.SplitRow = 1
range.Application.ActiveWindow.FreezePanes = True
wb.SaveAs("C:\MyWorkbook.xlsx")
wb.CLose()
xl.Quit()
xl = Nothing
wb = Nothing
ws = Nothing
range = Nothing
ReleaseComObject(xl)
ReleaseComObject(wb)
ReleaseComObject(ws)
ReleaseComObject(range)
Return ""
Catch ex As Exception
Return "WriteArray()" & Environment.NewLine & Environment.NewLine & ex.Message
End Try
End Function
Public Function WriteDataGrid(SheetName As String, ByRef dt As DataGridView) As String
Try
Dim l(dt.Rows.Count + 1, dt.Columns.Count) As Object
For c As Integer = 0 To dt.Columns.Count - 1
l(0, c) = dt.Columns(c).HeaderText
Next
For r As Integer = 1 To dt.Rows.Count
For c As Integer = 0 To dt.Columns.Count - 1
l(r, c) = dt.Rows(r - 1).Cells(c)
Next
Next
Dim errors As String = WriteArray(SheetName, l)
If errors <> "" Then
Return errors
End If
Return ""
Catch ex As Exception
Return "WriteDataGrid()" & Environment.NewLine & Environment.NewLine & ex.Message
End Try
End Function
Public Function WriteDataTable(SheetName As String, ByRef dt As DataTable) As String
Try
Dim l(dt.Rows.Count + 1, dt.Columns.Count) As Object
For c As Integer = 0 To dt.Columns.Count - 1
l(0, c) = dt.Columns(c).ColumnName
Next
For r As Integer = 1 To dt.Rows.Count
For c As Integer = 0 To dt.Columns.Count - 1
l(r, c) = dt.Rows(r - 1).Item(c)
Next
Next
Dim errors As String = WriteArray(SheetName, l)
If errors <> "" Then
Return errors
End If
Return ""
Catch ex As Exception
Return "WriteDataTable()" & Environment.NewLine & Environment.NewLine & ex.Message
End Try
End Function
I actually don't use this method in my Database program because it's a slow method when you have a lot of rows/columns. I instead create a CSV from the DataGridView. Writing to Excel with Excel Automation is only useful if you need to format the data and cells otherwise you should use CSV. You can use the code after the image for CSV export.
CSV Method
Private Sub DataGridToCSV(ByRef dt As DataGridView, Qualifier As String)
Dim TempDirectory As String = "A temp Directory"
System.IO.Directory.CreateDirectory(TempDirectory)
Dim oWrite As System.IO.StreamWriter
Dim file As String = System.IO.Path.GetRandomFileName & ".csv"
oWrite = IO.File.CreateText(TempDirectory & "\" & file)
Dim CSV As StringBuilder = New StringBuilder()
Dim i As Integer = 1
Dim CSVHeader As StringBuilder = New StringBuilder()
For Each c As DataGridViewColumn In dt.Columns
If i = 1 Then
CSVHeader.Append(Qualifier & c.HeaderText.ToString() & Qualifier)
Else
CSVHeader.Append("," & Qualifier & c.HeaderText.ToString() & Qualifier)
End If
i += 1
Next
'CSV.AppendLine(CSVHeader.ToString())
oWrite.WriteLine(CSVHeader.ToString())
oWrite.Flush()
For r As Integer = 0 To dt.Rows.Count - 1
Dim CSVLine As StringBuilder = New StringBuilder()
Dim s As String = ""
For c As Integer = 0 To dt.Columns.Count - 1
If c = 0 Then
'CSVLine.Append(Qualifier & gridResults.Rows(r).Cells(c).Value.ToString() & Qualifier)
s = s & Qualifier & gridResults.Rows(r).Cells(c).Value.ToString() & Qualifier
Else
'CSVLine.Append("," & Qualifier & gridResults.Rows(r).Cells(c).Value.ToString() & Qualifier)
s = s & "," & Qualifier & gridResults.Rows(r).Cells(c).Value.ToString() & Qualifier
End If
Next
oWrite.WriteLine(s)
oWrite.Flush()
'CSV.AppendLine(CSVLine.ToString())
'CSVLine.Clear()
Next
'oWrite.Write(CSV.ToString())
oWrite.Close()
oWrite = Nothing
System.Diagnostics.Process.Start(TempDirectory & "\" & file)
GC.Collect()
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
DATAGRIDVIEW_TO_EXCEL((DataGridView1)) ' PARAMETER: YOUR DATAGRIDVIEW
End Sub
Private Sub DATAGRIDVIEW_TO_EXCEL(ByVal DGV As DataGridView)
Try
Dim DTB = New DataTable, RWS As Integer, CLS As Integer
For CLS = 0 To DGV.ColumnCount - 1 ' COLUMNS OF DTB
DTB.Columns.Add(DGV.Columns(CLS).Name.ToString)
Next
Dim DRW As DataRow
For RWS = 0 To DGV.Rows.Count - 1 ' FILL DTB WITH DATAGRIDVIEW
DRW = DTB.NewRow
For CLS = 0 To DGV.ColumnCount - 1
Try
DRW(DTB.Columns(CLS).ColumnName.ToString) = DGV.Rows(RWS).Cells(CLS).Value.ToString
Catch ex As Exception
End Try
Next
DTB.Rows.Add(DRW)
Next
DTB.AcceptChanges()
Dim DST As New DataSet
DST.Tables.Add(DTB)
Dim FLE As String = "" ' PATH AND FILE NAME WHERE THE XML WIL BE CREATED (EXEMPLE: C:\REPS\XML.xml)
DTB.WriteXml(FLE)
Dim EXL As String = "" ' PATH OF/ EXCEL.EXE IN YOUR MICROSOFT OFFICE
Shell(Chr(34) & EXL & Chr(34) & " " & Chr(34) & FLE & Chr(34), vbNormalFocus) ' OPEN XML WITH EXCEL
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Regarding your need to 'print directly from datagridview', check out this article on CodeProject:
The DataGridViewPrinter Class
There are a number of similar articles but I've had luck with the one I linked.
The following code works fine for me :)
Protected Sub ExportToExcel(sender As Object, e As EventArgs) Handles ExportExcel.Click
Try
Response.Clear()
Response.Buffer = True
Response.AddHeader("content-disposition", "attachment;filename=ExportEthias.xls")
Response.Charset = ""
Response.ContentType = "application/vnd.ms-excel"
Using sw As New StringWriter()
Dim hw As New HtmlTextWriter(sw)
GvActifs.RenderControl(hw)
'Le format de base est le texte pour éviter les problèmes d'arrondis des nombres
Dim style As String = "<style> .textmode { } </style>"
Response.Write(Style)
Response.Output.Write(sw.ToString())
Response.Flush()
Response.End()
End Using
Catch ex As Exception
lblMessage.Text = "Erreur export Excel : " & ex.Message
End Try
End Sub
Public Overrides Sub VerifyRenderingInServerForm(control As Control)
' Verifies that the control is rendered
End Sub
Hopes this help you.
Dim rowNo1 As Short
Dim numrow As Short
Dim colNo1 As Short
Dim colNo2 As Short
rowNo1 = 1
colNo1 = 1
colNo2 = 1
numrow = 1
ObjEXCEL = CType(CreateObject("Excel.Application"), Microsoft.Office.Interop.Excel.Application)
objEXCELBook = CType(ObjEXCEL.Workbooks.Add, Microsoft.Office.Interop.Excel.Workbook)
objEXCELSheet = CType(objEXCELBook.Worksheets(1), Microsoft.Office.Interop.Excel.Worksheet)
ObjEXCEL.Visible = True
For numCounter = 0 To grdName.Columns.Count - 1
' MsgBox(grdName.Columns(numCounter).HeaderText())
If grdName.Columns(numCounter).Width > 0 Then
ObjEXCEL.Cells(1, numCounter + 1) = grdName.Columns(numCounter).HeaderText()
End If
' ObjEXCEL.Cells(1, numCounter + 1) = grdName.Columns.GetFirstColumn(DataGridViewElementStates.Displayed)
Next numCounter
ObjEXCEL.Range("A:A").ColumnWidth = 10
ObjEXCEL.Range("B:B").ColumnWidth = 25
ObjEXCEL.Range("C:C").ColumnWidth = 20
ObjEXCEL.Range("D:D").ColumnWidth = 20
ObjEXCEL.Range("E:E").ColumnWidth = 20
ObjEXCEL.Range("F:F").ColumnWidth = 25
For rowNo1 = 0 To grdName.RowCount - 1
For colNo1 = 0 To grdName.ColumnCount - 1
If grdName.Columns(colNo1).Width > 0 Then
If Trim(grdName.Item(colNo1, rowNo1).Value) <> "" Then
'If IsDate(grdName.Item(colNo1, rowNo1).Value) = True Then
' ObjEXCEL.Cells(numrow + 1, colNo2) = Format(CDate(grdName.Item(colNo1, rowNo1).Value), "dd/MMM/yyyy")
'Else
ObjEXCEL.Cells(numrow + 1, colNo2) = grdName.Item(colNo1, rowNo1).Value
'End If
End If
If colNo2 >= grdName.ColumnCount Then
colNo2 = 1
Else
colNo2 = colNo2 + 1
End If
End If
Next colNo1
numrow = numrow + 1
Next rowNo1
In design mode: Set DataGridView1 ClipboardCopyMode properties to EnableAlwaysIncludeHeaderText
or on the program code
DataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText
In the run time select all cells content (Ctrl+A) and copy (Ctrl+C) and paste to the Excel Program.
Let the Excel do the rest
Sorry for the inconvenient, I have been searching the method to print data directly from the datagridvew (create report from vb.net VB2012) and have not found the satisfaction result.
Above code just my though, wondering if my applications user can rely on above simple step it will be nice and I could go ahead to next step on my program progress.
A simple way of generating a printable report from a Datagridview is to place the datagridview on a Panel object. It is possible to draw a bitmap of the panel.
Here's how I do it.
'create the bitmap with the dimentions of the Panel
Dim bmp As New Bitmap(Panel1.Width, Panel1.Height)
'draw the Panel to the bitmap "bmp"
Panel1.DrawToBitmap(bmp, Panel1.ClientRectangle)
I create a multi page tiff by "breaking my datagridview items into pages.
this is how i detect the start of a new page:
'i add the rows to my datagrid one at a time and then check if the scrollbar is active.
'if the scrollbar is active i save the row to a variable and then i remove it from the
'datagridview and roll back my counter integer by one(thus the next run will include this
'row.
Private Function VScrollBarVisible() As Boolean
Dim ctrl As New Control
For Each ctrl In DataGridView_Results.Controls
If ctrl.GetType() Is GetType(VScrollBar) Then
If ctrl.Visible = True Then
Return True
Else
Return False
End If
End If
Next
Return Nothing
End Function
I hope this helps
another easy way and more flexible , after loading data into Datagrid
Private Sub Button_Export_Click(sender As Object, e As EventArgs) Handles Button_Export.Click
Dim file As System.IO.StreamWriter
file = My.Computer.FileSystem.OpenTextFileWriter("c:\1\Myfile.csv", True)
If DataGridView1.Rows.Count = 0 Then GoTo loopend
' collect the header's names
Dim Headerline As String
For k = 0 To DataGridView1.Columns.Count - 1
If k = DataGridView1.Columns.Count - 1 Then ' last column dont put , separate
Headerline = Headerline & DataGridView1.Columns(k).HeaderText
Else
Headerline = Headerline & DataGridView1.Columns(k).HeaderText & ","
End If
Next
file.WriteLine(Headerline) ' this will write header names at the first line
' collect the data
For i = 0 To DataGridView1.Rows.Count - 1
Dim DataRow As String
For k = 0 To DataGridView1.Columns.Count - 1
If k = DataGridView1.Columns.Count - 1 Then
DataRow = DataRow & DataGridView1.Rows(i).Cells(k).Value ' last column dont put , separate
End If
DataRow = DataRow & DataGridView1.Rows(i).Cells(k).Value & ","
Next
file.WriteLine(DataRow)
DataRow = ""
Next
loopend:
file.Close()
End Sub