passing a workbook Name into a For Loop C# - c#

So I am doing a few For loops that iterate through different variable changes imported from a template, a SQL script run, data is dumped, loop to next SQL script, etc, and this is done multiple times using the same template file (which at the end of the query dumps is saved as a new file. With that as background:
I need to be able to open the template the first time through the loop, then keep it open through each query dump until done. I don't want to keep reopening the file as its just too big and cumbersome I have this so far:
public void ExportToExcel(DataSet dataSet, string templatePath)
{
Excel.Application excelApp = new Excel.Application();
excelApp.Visible = true;
FileInfo excelFileInfo = new FileInfo(templatePath);
Boolean fileOpenTest = IsFileLocked(excelFileInfo);
if (!fileOpenTest)
{
Excel.Workbook templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
Excel.Workbook templateBook = excelApp.Workbooks[templatePath];
}
for (int i = 0; i < lstQueryDumpSheet.Items.Count; i++)
{
string tabName = lstQueryDumpSheet.Items[i].ToString();
Excel.Worksheet templateSheet = templateBook.Sheets[tabName];
// Copy DataTable
foreach (System.Data.DataTable dt in dataSet.Tables)
{ ... rest of loops...
My Problem is that the code line "Excel.Worksheet templateSheet = templateBook.Sheets[tabName];" tells me that "templateBook" is not assigned, but I am assigning it outside the IF statement so it should pass....right?

This is a problem of scope. Your code:
if (!fileOpenTest)
{
Excel.Workbook templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
Excel.Workbook templateBook = excelApp.Workbooks[templatePath];
}
declares the templateBook variable inside the blocks and thus its scope is limited to within that block. To have the variable persist outside of those blocks you need to declare it outside like this:
Excel.Workbook templateBook;
if (!fileOpenTest)
{
templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
templateBook = excelApp.Workbooks[templatePath];
}

Related

How to release an Excel Object

In my code below I have a commented out foreach loop that works except it does not release the excel object due to the background enum. So I am trying to convert it to a for loop but up until this point I have avoided 'for' loops and I can't seem to get mine to work. I am just pointing out that I know it is broke.
I did comment out the broken 'for' loop and it runs and all works as expected except I can't release the excel objects...
My question, I have tried so many different examples but I can not release the excel object from my task manager and it remains open in the back ground.
I use the "One Dot" rule
I'm using a try/final
I have tried many different Marshal.ReleaseComOjbect(obj), examples from the web
I am using a 'for loop' (not a foreach loop) so no hidden Enum object
Thank you for any assistance, education
My code:
public static List<ExcelObject> listOfExcelObjects = new List<ExcelObject>();
public static void txtSearchBar_Changed(object sender, EventArgs e)
{
Excel.Application excel = null;
Excel.Workbooks workbooks = null;
Excel.Workbook workbook = null;
Excel.Sheets sheets = null;
Excel.Worksheet worksheet = null;
Excel.Range range = null;
try
{
excel = new Excel.Application();
workbooks = excel.Workbooks;
workbook = workbooks.Open(GlobalObject.filePath, Notify: false, ReadOnly: true);
sheets = workbook.Sheets;
for (int i = 1; i < sheets.Count; i++)
{
worksheet = sheets.Item[i];
if (worksheet.Visible == Excel.XlSheetVisibility.xlSheetVisible)
{
Form_MainForm.thisForm.cmbx_WorkSheet.Items.Add(sheets.Item[i].Name);
if (worksheet.Name == "UNIT FEEDER")
{
Form_MainForm.thisForm.cmbx_WorkSheet.Text = worksheet.Name;
worksheet.Select();
Form_MainForm.thisForm.txtBox_StartingRow.Text = $"11";
Form_MainForm.thisForm.txtBox_EndingRow.Text = $"{_Events.EndOfRow()}";
range = worksheet.Range[$"A{Int32.Parse(Form_MainForm.thisForm.txtBox_StartingRow.Text)}", $"A{Int32.Parse(_Events.EndOfRow())}"];
for (int j = 1; j < range.Cells.Count; j++)
{
_Events.listOfExcelObjects.Add(new ExcelObject() { FeederNumber = range.Cells.Item[j] });
}
//foreach (Excel.Range j in worksheet.Range[$"A{Form_MainForm.thisForm.txtBox_StartingRow.Text}", $"A{_Events.EndOfRow()}"].Cells)
//{
// _Events.listOfExcelObjects.Add(new ExcelObject() { FeederNumber = j.Value });
//}
Form_MainForm.thisForm.grd_DataGridView.DataSource = _Events.listOfExcelObjects;
}
}
}
}
finally
{
releaseObject(range);
releaseObject(worksheet);
releaseObject(sheets);
releaseObject(workbook);
releaseObject(workbooks);
releaseObject(excel);
//Marshal.ReleaseComObject(range);
//Marshal.ReleaseComObject(worksheet);
//Marshal.ReleaseComObject(sheets);
//Marshal.ReleaseComObject(workbook);
//Marshal.ReleaseComObject(workbooks);
//Marshal.ReleaseComObject(excel);
}
}
private static void releaseObject(object obj)
{
if (obj != null && Marshal.IsComObject(obj))
{
Marshal.ReleaseComObject(obj);
}
obj = null;
}
You need to close Excel:
excel.Quit();
For those who don't read the comment section 'such as myself'
I have a function EndOfRow() that I did not do any cleaning to release objects. I didn't know about releasing objects when I created this function and I forgot about it. Many hours spent on a simple oversight.
Leaving out the Quit() was also an oversight in my many attemts to resolve my issue but was not the main problem, EndOfRow() was...
So what I learned is to release the objects as soon as I am done using excel.
Thanks Terry

Writing to sheet leads to "name already taken"

I'm learning how to use Interop.Excel. The test Winforms program reads an existing Excel file, checks if a tab names "Added_by_program" exists, deletes the sheet if it does, and creates a new sheet named "Added_by_program." If I don't try to write to the new sheet, the program runs perfectly, over and over. I get problems when I try to write to it. If the sheet is not present in the original file, the program runs perfectly one time, and writes correctly to the newly created sheet. but on subsequent runs, I get:
"System.Runtime.InteropServices.COMException: 'That name is already taken. Try a different one.'"
for the line that tries to name the new sheet. I have to manually kill the open Excel instance. What am I missing?
Code (irrelevant lines taken out)
using System;
using System.IO;
using System.Windows.Forms;
using Microsoft.Office.Interop.Excel;
namespace excelReadWrite
{
public partial class Form1 : Form
{
string readFolder = myPath;
string inFileName = #"Aram test excel file.xlsx";
string newSheetName = "Added_by_program";
Range rawRange = null;
Range pasteRange = null;
int rawCols = 0;
int rawRows = 0;
int iInSheet = 0;
int iNewSheet = 0;
int nInSheets = 0;
bool foundRawSheet = false;
bool foundNewSheet = false;
Worksheet worksheet = null;
public Form1()
{
InitializeComponent();
}
private void start_Button_Click(object sender, EventArgs e)
{
string inFile = myPath+ inFileName;
int nSheets = 0;
string sheetNames = "";
// Open Excel workbook to read
Microsoft.Office.Interop.Excel.Application xl = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = xl.Workbooks.Open(inFile);
// Count worksheets in opened Excel file
nSheets = workbook.Worksheets.Count;
nSheets_TextBox.Text = nSheets.ToString();
nInSheets = 0;
foreach (Worksheet worksheet in workbook.Worksheets)
++nInSheets;
//foreach (Worksheet worksheet in workbook.Worksheets)
for (int iSheet = nInSheets; iSheet >= 1; --iSheet)
{
worksheet = workbook.Worksheets[iSheet];
sheetNames += " " + worksheet.Name;
// The program is going to add a worksheet. If it already exists, delete it before adding it.
if (string.Equals(worksheet.Name, newSheetName))
{
workbook.Worksheets[iSheet].Delete();
}
}
// Add a new sheet and name it
if (foundRawSheet)
{
newWorksheet = workbook.Worksheets.Add();
newWorksheet.Name = newSheetName;
// THE NEXT LINE IS THE PROBLEM LINE
// "Written" WILL BE WRITTEN TO A1:C3 WHEN THE SHEET IS CREATED, BUT THIS LINE
// CAUSES THE ERROR IN SUBSEQUENT RUNS
// IF I COMMENT IT OUT, THE PROGRAM RUNS FINE, REPEATEDLY
newWorksheet.Range["A1", "C3"].Value2 = "Written";
workbook.Save();
workbook.Close();
xl.Quit();
}
}
}
Did you set xl.DisplayAlerts=false?
If not, deleting a worksheet with existing data will cause a confirm dialog to be displayed. .
If the Excel application is visible, the Worksheet.Delete will block until the dialog is acknowledged.
If the Excel application is not visible, your code execution will proceed (the dialog is effectively canceled --> delete not confirmed), but the worksheet will not be deleted.

Background Excel Thread Not Getting Killed C#

Need to work with Excel Interop. I can successfully open and read from an excel file but while closing it, the background process for that excel does not get killed. Tried using several solutions from previous SO links, but no luck! So my ask is, how to kill the background process???
Below is the UPDATED CODE that I am currently using:
Excel.Application application = new Excel.Application();
var workbooks = application.Workbooks;
Excel.Workbook workbook = workbooks.Open(path);
Excel.Worksheet worksheet = workbook.ActiveSheet;
Excel.Range range = worksheet.UsedRange;
var rows = range.Rows;
// Some business logic
for (int row = 2; row <= rows.Count; row++)
{
//Read the data from the excel
}
// Some business logic
//close the excel
rows.Clear();
cell.Clear();
range.Clear();
workbook.Close(false);
application.Quit();
while (Marshal.FinalReleaseComObject(rows) != 0) { }
while (Marshal.FinalReleaseComObject(cell) != 0) { }
while (Marshal.FinalReleaseComObject(range) != 0) { }
while (Marshal.FinalReleaseComObject(worksheet) != 0) { }
while (Marshal.FinalReleaseComObject(workbook) != 0) { }
while (Marshal.FinalReleaseComObject(workbooks) != 0) { }
while (Marshal.FinalReleaseComObject(application) != 0) { }
rows = null;
cell = null;
range = null;
worksheet = null;
workbook = null;
workbooks = null;
application = null;
GC.Collect();
GC.WaitForPendingFinalizers();
By following the above code, I get the below exception in my debugger:
Any help on this will be appreciated.
You are using range.Rows.Count, this might violate the "Never use 2 dots with com objects." rule. See here
You could try including this ;
var rows = range.Rows
for (int row = 2; row <= rows.Count; row++)
{
//Read the data from the excel
}
rows.Clear(); //rows is itself a range object
The few times I've had to use Excel interop, I haven't had any issues when following these simple rules:
Always wrap any Excel interop in try-finally
blocks. In the finally block put all releasing logic.
Use Marshal.FinalReleaseComObject to release named COM
references as its essentially doing the ref count loop for you.
Eagerly release COM objects from deepest to
shallowest. In your case I'd start with range then worksheet
then workbook and so on.
Correctly release unreferenced COM objects (two dot rule) with GC.Collect() and GC.WaitForPendingFinalizers(). Do this before manually
releasing pending COM objects you hold a named reference to.

How to check if the Worksheet already exist in Interop

I want to check if the sheet exists before creating it.
using Excel = Microsoft.Office.Interop.Excel;
Excel.Application excel = new Excel.Application();
excel.Visible = true;
Excel.Workbook wb = excel.Workbooks.Open(#"C:\"Example".xlsx");
Excel.Worksheet sh = wb.Sheets.Add();
int count = wb.Sheets.Count;
sh.Name = "Example";
sh.Cells[1, "A"].Value2 = "Example";
sh.Cells[1, "B"].Value2 = "Example"
wb.Close(true);
excel.Quit();
This extension method returns the worksheet if it exists, null otherwise:
public static class WorkbookExtensions
{
public static Excel.Worksheet GetWorksheetByName(this Excel.Workbook workbook, string name)
{
return workbook.Worksheets.OfType<Excel.Worksheet>().FirstOrDefault(ws => ws.Name == name);
}
}
The linq method .Any() can be used instead of FirstOrDefault to check whether the worksheet exists as well...
Create a loop like this:
// Keeping track
bool found = false;
// Loop through all worksheets in the workbook
foreach(Excel.Worksheet sheet in wb.Sheets)
{
// Check the name of the current sheet
if (sheet.Name == "Example")
{
found = true;
break; // Exit the loop now
}
}
if (found)
{
// Reference it by name
Worksheet mySheet = wb.Sheets["Example"];
}
else
{
// Create it
}
I'm not into Office Interop very much, but come to think of it, you could also try the following, much shorter way:
Worksheet mySheet;
mySheet = wb.Sheets["NameImLookingFor"];
if (mySheet == null)
// Create a new sheet
But I'm not sure if that would simply return null without throwing an exception; you would have to try the second method for yourself.
Why not just do this:
try {
Excel.Worksheet wks = wkb.Worksheets["Example"];
} catch (System.Runtime.InteropServices.COMException) {
// Create the worksheet
}
wks.Select();
The other way avoids throwing and catching exceptions, and certainly it's a legitimate answer, but I found this and wanted to put this up as an alternative.

Copying A Formula--And applying it to a new cell range

So here's what I am going through. I am using the Excel dll with c# in order to go inside a big and nasty excel sheet so that others don't have to.
We have a formula in one cell that is rather large and we don't want to copy it to every row because of this. This formula uses multiple values on the row that it is placed on. If it is on row 1, it uses lots of cells from that row.
When one copies this formula normally in excel, the new ranges of the cells are modified to reflect the new starting position.
The problem is that when I copy the formula like this, it still gives me all of the values that have to do with the first row instead of the row where I pasted it.....Here is my code:
sheet.Cells[77][row].Formula = sheet.Cells[77][1].Formula;
Can somebody let me know how to make the formula actually apply to the new row instead of row 1?
This will probably work, as it works from VBA... in most cases.
sheet.Cells[77][row].FormulaR1C1 = sheet.Cells[77][1].FormulaR1C1;
This would work because FormulaR1C1(not a very informative link) uses R1C1 notation which describes the referenced cells location in relation to the current cell instead of saying which cells to use. This means the actual references are dependent on the cell with the formula. When you just use Formula, you're copying the string of the Formula exactly including the hard coded cell references.
You could use Application.ConvertFormula
So, let's say my Cell = Cells77 has a formula that says =Sum(B77,C77) (Cells from the same row).
if want to copy it to a cell right below it, you would do something like:
string formula = Sheet1.Cells[77][2].Formula;
Sheet1.Cells[77][2].Formula = app.ConvertFormula(formula, XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, XlReferenceType.xlRelative, Sheet1.Cells[77][3]);
Full console app that works (You need to modify cells though).
static void Main(string[] args)
{
var app = new Microsoft.Office.Interop.Excel.Application();
var workbook = app.Workbooks.Open(#"C:\Users\user\Desktop\Book1.xlsx");
Microsoft.Office.Interop.Excel.Worksheet Sheet1 = (Microsoft.Office.Interop.Excel.Worksheet)workbook.Worksheets.get_Item("Sheet1");
string formula = Sheet1.Cells[5][3].Formula;
Sheet1.Cells[5][4].Formula = app.ConvertFormula(formula, XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, XlReferenceType.xlRelative, Sheet1.Cells[5][3]);
workbook.SaveAs(#"C:\Users\user\desktop\test.xlsx");
workbook.Close();
}
You can modify third and forth parameter of ConvertFormula method to your liking. Read more about the method here: ConvertFormula.
If you want to stretch formula accross multiple rows, you can try to use range.AutoFill()
Hi guys m posting this because this code is used to copy the formula behind a cell in Excel:
public void copy_Formula_behind_cell()
{
Excel.Application xlapp;
Excel.Workbook xlworkbook;
Excel.Worksheet xlworksheet;
Excel.Range xlrng;
object misValue = System.Reflection.Missing.Value;
xlapp = new Excel.Application();
xlworkbook =xlapp.Workbooks.Open("YOUR_FILE", 0, true, 5, "",
"",true,Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "\t",
false,
false, 0, true, 1, 0);
xlworksheet = (Excel.Worksheet)xlworkbook.Worksheets.get_Item(1);
string sp = xlworksheet.Cells[3,2].Formula;//It will Select Formula using Fromula method//
xlworksheet.Cells[8,2].Formula =
xlapp.ConvertFormula(sp,XlReferenceStyle.xlA1,
XlReferenceStyle.xlR1C1, XlReferenceType.xlAbsolute,
xlworksheet.Cells[8][2]);
//This is used to Copy the exact formula to where you want//
xlapp.Visible = true;
xlworkbook.Close(true, misValue, misValue);
xlapp.Quit();
releaseObject(xlworksheet);
releaseObject(xlworkbook);
releaseObject(xlapp);
}
private void releaseObject(object obj)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
}
catch (Exception ex)
{
obj = null;
MessageBox.Show("Unable to release the Object " + ex.ToString());
}
finally
{
GC.Collect();
}
}
I am posting this code for range the excel formulas using c# code and Microsoft.Office.Interop.Excel Library:
class Program
{
static void Main(string[] args)
{
Program p = new Program();
p.Excel();
}
public void Excel()
{
Application xlApp = new Application();
Workbook xlWorkBook;
Worksheet xlWorkSheet;
object misValue = Missing.Value;
xlWorkBook = xlApp.Workbooks.Add(misValue);
xlWorkSheet = (Worksheet)xlWorkBook.Worksheets.get_Item(1);
for (int r = 1; r < 5; r++) //r stands for ExcelRow and c for ExcelColumn
{
// Its a my sample example: Excel row and column start positions for writing Row=1 and Col=1
for (int c = 1; c < 3; c++)
{
if (c == 2)
{
if (r == 1)
{
xlWorkSheet.Cells[r, c].Formula = "=SUM(A1+200)";
}
continue;
}
xlWorkSheet.Cells[r, c] = r;
}
}
Range rng = xlWorkSheet.get_Range("B1");
// This is the main code we can range our excel sheet formulas
rng.AutoFill(xlWorkSheet.get_Range("B1", "B4"), XlAutoFillType.xlLinearTrend);
xlWorkBook.Worksheets[1].Name = "MySheetData";//Renaming the Sheet1 to MySheet
xlWorkBook.SaveAs(#"E:\test.xlsx");
xlWorkBook.Close();
Marshal.ReleaseComObject(xlWorkSheet);
Marshal.ReleaseComObject(xlWorkBook);
Marshal.ReleaseComObject(xlApp);
}
}

Categories

Resources