I have a problem selecting a cell in excel using c# while running through a "for" loop.
I have 3 datatables in a dataset(dsReportGrid) which I'm exporting to excel (one sheet per table).
When I try to select a range so I can freeze panes, it goes well in the first iteration of the loop (dsReportGrid.table[0]), but when it gets to second iteration when I try to select the cell in the secondsheet it throws me a COM exception on the select event (the code line with two asterisks).
Why is it happening and how can I handle it?
Thanks,
private void saveXlS()
{
Excel.Application xlApp;
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
Excel.Range xlCells;
Excel.Range freezeCell;
object misValue = System.Reflection.Missing.Value;
xlApp = new Excel.ApplicationClass();
xlWorkBook = xlApp.Workbooks.Add(misValue);
for (int table = 0; table <= dsReportGrid.Tables.Count -1; table++)
{
xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(table +1);
Excel.Range k = xlWorkSheet.get_Range("a2");
**k.Select();**
k.Application.ActiveWindow.FreezePanes = true;
break;
}
...rest of code writing from dataset to excel
}
Try activating the worksheet before attempting to select the range. I think you can only select a range on an active worksheet.
Related
I have 2 datable and I added for loop to create the sheet and save it, but when I open excel file I can see only one(1st) sheet.
Please check why 2nd is not added in excel.
Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
Excel.Workbook wb = xlApp.Worksheets.Add();
Excel.Worksheet ws = null;
Excel.Range chartRange =null;
for(int i=0; i<dataset.Tables.Count; i++)
{
// here I am creating new sheet for each datatable but only 1st datatable value I can see in
XL, 2nd sheet is not created
ws = (Excel.Worksheet)worksheets.Add();
ws.Name = dataset.Table[i].TableName;
//business logic here to assign data
var dgarray = new object[rows.count, columns.count];
//for loop to assign column name
dgarray[0,c] = column name;
//for loop to assign row value
dgarray[rowindex, colindex] = row value;
int rowcount = dgarray.getlength(0);
int columscount = dgarray.getlength(1);
chartRange = (Excel.Range)ws.cells[1,1];
chartRange = chartRange.get_Resize(rowcount,colcount);
chartRange.set_Value(Excel.XlRangeValueDatatype.XlRangeValueDefault,dgarray);
}
((Excel.Worksheet).ws).SaveAs(filename);
I did succeed to export data from a DataGridView to excel file by using a loop and writing cell by cell, but the problem is i have more than 2000 rows so the exporting process takes a lot of time.
My question is : Is there anything to change so i can minimize the exportation time ?
Thanks
'exporter
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim xlApp As Excel.Application
Dim xlBook As Excel.Workbook
Dim xlSheet As Excel.Worksheet
xlApp = New Excel.Application
xlBook = xlApp.Workbooks.Open(Filename:=Path.Combine(Application.StartupPath, "EMP_.xlsx"), IgnoreReadOnlyRecommended:=True, ReadOnly:=False, Editable:=True)
xlSheet = xlBook.Worksheets(1)
If DataGridView1.DataSource IsNot Nothing Then
Dim i, j As Integer
For i = 1 To DataGridView1.RowCount - 1
For j = 1 To DataGridView1.ColumnCount
xlSheet.Cells(i + 1, j) = DataGridView1.Rows(i - 1).Cells(j - 1).Value
Next
Next
xlApp.Visible = True
xlApp.UserControl = True
xlApp.Quit()
xlApp = Nothing
Else
MsgBox("Le tableau est vide")
End If
End Sub
Not sure if this is something might be open too. By chance if you could load your DataGridView via setting the DataSource of the DataGridView to a DataTable then the following would be an option.
Using SpreadSheetLight (installed via NuGet) you can use a function like the sample below. Pass in the following, full path and file name, sheet name, the DataTable from the DataGridView e.g. Dim dt As DataTable = CType(DataGridView1.DataSource,DataTable) and the last argument if True included column headers (DataColumn names) or False exclude column names.
SpreadSheetLight home page.
Public Sub SimpleExportRaw(
pFileName As String,
pSheetName As String,
pDataTable As DataTable,
pColumnHeaders As Boolean)
Using doc As New SLDocument()
doc.SelectWorksheet(pSheetName)
doc.ImportDataTable(1, SLConvert.ToColumnIndex("A"), pDataTable, pColumnHeaders)
doc.SaveAs(pFileName)
End Using
End Sub
I have a problem with my below code which freezes and does nothing while running.
INFRASTRUCTURE:
Visual Studio 2017
.NET Framework 4.7.2
OS: Windows 7
GOAL:
I have a DataGridView which has 3 columns, I'm listing some information in this DataGridView from another source to fill first and second row.
The first row is the parameter name and the second one is this parameter's current value.
The third column is not filling in this process, I will fill that 3rd column from my database Excel file to compare the parameter's current value with the database value stored in Excel. This is where my problems start.
I'm trying to use below code for filling DataGridView parameter value from Excel sheet which I'm using a database;
Some parameters not stored in an Excel sheet so in fact, I need a function like vlookup to map data with the parameter name.
In excel A column is my parameter name and B column is the parameter's database value.
I'm trying to import this excel and trying to match parameter names in DataGridView and Excel if the parameter name is same it should writing Excel parameter value to a 3rd column in DataGridView.
Public Class BuildImportExcel
Public Shared Sub NewMethod(ByVal dgv As DataGridView)
Dim ofd As OpenFileDialog = New OpenFileDialog With {
.Filter = "Excel |*.xlsx",
.Title = "Import Excel File"
}
ofd.ShowDialog()
Try
If ofd.FileName IsNot "" Then
Dim xlApp As New Excel.Application
If xlApp Is Nothing Then
MessageBox.Show("Excel is not properly installed!")
Else
Dim xlBook As Excel.Workbook = xlApp.Workbooks.Open(ofd.FileName)
Dim xlSheet As Excel.Worksheet = CType(xlBook.ActiveSheet, Excel.Worksheet)
For i = 0 To dgv.Rows.Count
If dgv.Rows(i).Cells(0).Value IsNot "" Then
Dim look As Boolean = True
Dim found As Boolean = False
Dim rowLook As Integer = 2
Dim rowFound As Integer = 0
While look = True
If xlSheet.Range("A" & rowLook).Value IsNot "" Then
If xlSheet.Range("A" & rowLook).Text Is dgv.Rows(i).Cells(0).Value Then
found = True
rowFound = rowLook
End If
Else
look = False
End If
rowLook = rowLook + 1
End While
If found = True Then
dgv.Rows(i).Cells(2).Value = xlSheet.Range("B" & rowFound).Text
End If
End If
Next
xlApp.Quit()
Release(xlSheet)
Release(xlBook)
Release(xlApp)
End If
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
Private Shared Sub Release(ByVal sender As Object)
Try
If sender IsNot Nothing Then
Marshal.ReleaseComObject(sender)
sender = Nothing
End If
Catch ex As Exception
sender = Nothing
End Try
End Sub
End Class
But the problem is it's not working freezing I thought parameter list about 200 rows so it causes to freezing and try it with small portions like 5 parameters and still same. Seems something wrong and I couldn't find it.
Also is it logic way to match them with that method or do you suggest anything like OLEDB connection?
EDIT:
I turn off Option Strict and then change IsNot to operator <> and then it start to work but i'd like to use Option Strict On how can i handle this operators?
You are making this more difficult than it has to be. A questionable and problematic area is the While look = True loop. This assumes a lot and, in my test, will cause the code to freeze often. The main problem here is that the code is (somewhat) looping through the rows from the Excel file. The issue here is that you do not KNOW how may rows there are! There is no checking for this and the code will fail on the line…
If xlSheet.Range("A" & rowLook).Value IsNot "" Then
When you reach the bottom of the given Excel file and it never finds a match.
Another issue is the line…
If xlSheet.Range("A" & rowLook).Text Is dgv.Rows(i).Cells(0).Value Then
This is ALWAYS going to fail and will never be true. My understanding…
The Is operator determines if two object references refer to the same
object. However, it does not perform value comparisons.
Given this, I recommend you simplify things a bit. Most important are the loops through the Excel worksheet which is “expensive” and if there is a lot of rows, you may have a performance problem. When I say a lot of rows, I mean tens of thousands of rows…. 200 rows should be fine.
It would un-complicate things if we could tell how many rows from the worksheet are returned from….
Dim xlSheet As Excel.Worksheet = CType(xlBook.ActiveSheet, Excel.Worksheet)
This is basically an Excel Range, and it is from this Range that we can get the number of rows in this excel range with…
Dim totalExcelRows = xlSheet.UsedRange.Rows.Count
Now, instead of the complicated and problematic While loop, we can replace it with a simple for loop. This will eliminate some variables and will keep the loop index in row range in the excel file.
I hope this makes sense… below is an example of what is described above.
Public Shared Sub NewMethod(ByVal dgv As DataGridView)
Dim ofd As OpenFileDialog = New OpenFileDialog With {
.Filter = "Excel |*.xlsx",
.Title = "Import Excel File",
.InitialDirectory = "D:\\Test\\ExcelFiles"
}
ofd.ShowDialog()
Try
If ofd.FileName IsNot "" Then
Dim xlApp As New Excel.Application
If xlApp Is Nothing Then
MessageBox.Show("Excel is not properly installed!")
Else
Dim xlBook As Excel.Workbook = xlApp.Workbooks.Open(ofd.FileName)
Dim xlSheet As Excel.Worksheet = CType(xlBook.ActiveSheet, Excel.Worksheet)
Dim totalExcelRows = xlSheet.UsedRange.Rows.Count
For i = 0 To dgv.Rows.Count
If dgv.Rows(i).Cells(0).Value IsNot Nothing Then
For excelRow = 1 To totalExcelRows
If xlSheet.Range("A" & excelRow).Text.ToString() = dgv.Rows(i).Cells(0).Value.ToString() Then
dgv.Rows(i).Cells(2).Value = xlSheet.Range("B" & excelRow).Text
Exit For
End If
Next
Else
Exit For
End If
Next
xlApp.Quit()
Marshal.ReleaseComObject(xlSheet)
Marshal.ReleaseComObject(xlBook)
Marshal.ReleaseComObject(xlApp)
End If
End If
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
In VB.NET I'm reading an Excel Spreadsheet like so:
Dim xlApp As Excel.Application = New Excel.Application
Dim xlWorkBook As Excel.Workbook = xlApp.Workbooks.Open(Server.MapPath(SavePath & sFilename))
Dim xlWorkSheet As Excel.Worksheet = xlWorkBook.Sheets(1)
Dim eRange As Excel.Range = xlWorkSheet.Range("C3:C" & xlApp.Rows.End(Excel.XlDirection.xlDown).Row)
Dim bottomRange As Integer = xlApp.Rows.End(Excel.XlDirection.xlDown).Row
...
After I open it up and read some data I want to close it and make it possible for someone to delete it MANUALLY(clicking on the file and pressing delete) afterwards, so after looking around I found this sub that is supposed to release the locks on the objects that I create:
Private Sub ReleaseObject(ByVal obj As Object)
Try
Dim intRel As Integer = 0
Do
intRel =
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(obj)
Loop While intRel > 0
Catch ex As Exception
MsgBox("Error releasing object" & ex.ToString)
obj = Nothing
Finally
GC.Collect()
End Try
End Sub
But whenever I create a spreadsheet and close my program and then try to delete the file MANUALLY, not in my program, I get the error:
The action can't be completed because the file is open in Excel 2016.
Choose the file and try again.
This error even persists after I have closed my program. Can someone please help me out in figuring out how to release these locks Excel has on the files? I should also note that I pass xlApp to the sub.
I'm trying to insert a cell beginning with the first cell in a range (via the .getCellRangeByName() method of a Document's active Sheet).
I found out how to do this with a Dispatcher from the OpenOffice library (.uno:InsertCell), but I'd prefer to use something that doesn't require the dispatcher if possible.
Example code that I plan to wire-up to a button...
Sub AddManualBalance(EntryDate As Date, EntryAmount As Currency)
Dim Doc As Object
Dim Sheet As Object
Doc = ThisComponent
If Doc Is Nothing Then
Return
EndIf
Sheet = Doc.getCurrentController().getActiveSheet()
If Sheet Is Nothing Then
Return
EndIf
Dim TargetCells As Object
TargetCells = Sheet.getCellRangeByName("B9:C9");
// insert a cell in both the B and C columns at position 9,
// then move all other cells down
// add my EntryDate as a value to the new cell in B column
// add my EntryAmount as a value to the new cell in C column
End Sub
Thanks in advance for any help!
P.S. I really dislike Basic, but it seems that with spreadsheets and office app automation, that's the preferred language. Is there any way to do LibreOffice/OpenOffice macros in a more C-like language?
Following code does what you want:
Dim Doc As Object
Dim Sheet As Object
Dim oDestCell As Object
Dim CellRangeAddress As New com.sun.star.table.CellRangeAddress
Doc = ThisComponent
Sheet = Doc.Sheets(0)
CellRangeAddress.Sheet = 0
CellRangeAddress.StartColumn = 1
CellRangeAddress.StartRow = 8
CellRangeAddress.EndColumn = 2
CellRangeAddress.EndRow = 8
Sheet.insertCells(CellRangeAddress, com.sun.star.sheet.CellInsertMode.DOWN)
oDestCell=Sheet.getCellByPosition(1,8)
oDestCell.setValue(EntryDate)
oDestCell=Sheet.getCellByPosition(2,8)
oDestCell.setValue(EntryAmount)
You can read about C++ and LibreOffice at api.libreoffice.org
Thanks to Mac, my code is now...
Public Doc As Object
Public Sheet As Object
Sub AddManualBalance()
GetCurrentSheet()
REM insert two new cells, move cells down
Dim TargetCells As New com.sun.star.table.CellRangeAddress
TargetCells.Sheet = 3
TargetCells.StartColumn = 1
TargetCells.StartRow = 8
TargetCells.EndColumn = 2
TargetCells.EndRow = 8
Sheet.insertCells(TargetCells, com.sun.star.sheet.CellInsertMode.DOWN)
REM get date and balance from text boxes, add value to cells
Dim BalanceDate As Object
Dim BalanceAmount As Object
Dim Forms As Object
Dim MainForm As Object
Forms = Doc.getCurrentController().getActiveSheet().getDrawPage().getForms()
MainForm = Forms("MainForm")
BalanceDate = MainForm.getByName("BalanceDate")
BalanceAmount = MainForm.getByName("BalanceAmount")
Sheet.getCellByPosition(1,8).setValue(BalanceDate.CurrentValue)
Sheet.getCellByPosition(2,8).setValue(BalanceAmount.CurrentValue)
End Sub
Sub GetCurrentSheet()
REM get references to document and active sheet, test if exist
If ThisComponent Is Nothing Then
Return
End If
Doc = ThisComponent
If Doc Is Nothing Then
Return
EndIf
Sheet = Doc.getCurrentController().getActiveSheet()
End Sub