Enable Excel Macro in codes? - c#

I am working an existing project which is base do Excel class to copy values from one worksheet A to another one B. There are some formulas and Addin functions in ws A cells. The copy codes are used to copy only values out to ws B as a result. Here is a block of simplified codes:
using Excel = Microsoft.Office.Interop.Excel;
....
object missing = System.Type.Missing;
Excel.Application app = new Excel.Application();
// Creating Excel application with source workbook in memory
Excel.Workbook workbook = app.Workbooks.Open("SourceA.xls",
Excel.XlUpdateLinks.xlUpdateLinksNever,....);
...
app.Calculate() // Forcing to recalculate value in cells.
...
//create destination workbook
Excel.Workbook destWb = app.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet);
Excel.WorkSheet destSheet = (Excel.Worksheet)destWb.Worksheets[1];
Excel.Range destRange = (Excel.Range)destSheet.Cells[1, 1];
//rename sheet 1
destSheet.Name = "wsB Report";
//Set Source Data Range from
Excel.WorkSheet sourceWA = (Excel.Worksheet)workbook.Worksheets["wsA"];
//Get the predefined named range "DataRange" from source wsA
Excel.Range sourceRange = sourceWA.get_Range("DataRange", missing);
//copy data
sourceRange.Copy(missing);
//paste values and number formats
destRange.PasteSpecial(Excel.XlPasteType.xlPasteValuesAndNumberFormats,
Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, missing, missing);
//paste formats
destRange.PasteSpecial(Excel.XlPasteType.xlPasteFormats,
Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, missing, missing);
//paste column widths
destRange.PasteSpecial(Excel.XlPasteType.xlPasteColumnWidths,
Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, missing, missing);
....
destWb.SaveAs("destB.xls", missing, ....);
The problem I have is that the copies cells in destB.xls are all marked as "#NAME?". They should be values copied. I open the source excel file and I can see the data in the source range are updated with values. However, when I run my codes, the values seem not available.
I guess that the Excel application I created in my codes may not have Macro enabled. This may be reason values cannot be copied. For example, when I open the source excel file, I'll see a prompt to enable or disable Macros. If I click on Enable button, the excel will take some time to display values in data cells.
If that is the reason, is there any way in codes to enable Macro so that all formulas in cells will be able to update values?
Another reason might be caused by OS or Windows security updates. Not sure if any Windows security settings may affect Excel macros. If this is the case, should I change Windows security level or any way to force Macros enabled in my project?
By the way, my project runs in Windows XP and Windows 2008 Server, with Office Excel 2003 installed. The project was done in VS 2008.

I went further into my codes and I found that by making Excel app visible, I'd able to see what actually happened. As Ben Voigt's suggestion, the security settings for Macro can be done in Excel, but in codes, you can force to execute any VBA-like codes no matter what the setting is. Here are some codes to make Excel visible and see all the prompt dialogs:
Excel.Application app = new Excel.Application();
Excel.Workbook workbook = app.Workbooks.Open("SourceA.xls", ...);
app.Visible = true; //set to 'true' when debbugging, Exec is visible
app.DisplayAlerts = true; //enable all the prompt alerts for debug.
...
For example, I rebuild links in my C# codes, set values in cells and refresh or calculate values(simulate F9). By making Excel visible, I saw what was going on and found bugs in my codes which did not do what I want.

Related

C# Excel Interop row limit -> HRESULT: 0x800A03EC exception thrown

I have a C# application which purpose is to store a big amount of data. I am using Microsoft.Office.Interop.Excel (Microsoft.Office.Interop.Excel.dll Version 14.0.0.0) to help me accomplish this. I have Excel 2007 installed.
I use the following lines:
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelWorkBook = excelApp.Workbooks.Add(misValue);//*--------> LINE NOT WORKING */
excelWorksheetBeingWritten = (Excel.Worksheet)excelWorkBook.Worksheets.get_Item(1);
My code then iterates through a big list of objects, and each time a row must be written I do something like:
var startCell = excelWorksheetBeingWritten.Cells[excelLineCounter, 1];
var endCell = excelWorksheetBeingWritten.Cells[excelLineCounter, 2];
string[] tmpArray = new string[2] { stringVar1, stringVar2 };
tmpRange = excelWorksheetBeingWritten.Range[startCell, endCell];
tmpRange.Value = tmpArray;
When excelLineCounter exceeds 65536, the "HRESULT: 0x800A03EC exception" is thrown. I am perfectly aware of the (in)famous pre-Excel2007 row limit (which is precisely 65536). What I don't understand is why the interops are using that limit, when Excel 2007 (my version) has a documented limit of 1.048.576 rows.
On the other hand, if I replace the above "LINE NOT WORKING" by the following, it seems to use the Excel 2007 row limit, and the exception vanishes:
excelWorkBook = excelApp.Workbooks.Open(#"H:\Workbook1.xlsx");//*--------> LINE WORKING */
Note: "Workbook1.xlsx" is an empty workbook previously saved as "Excel Workbook (*.xlsx)"
Can someone please tell me what kind of sorcery do I need to do in order to configure the Excel Interop objects to use the Excel 2007 limits by default, preferably without having a previously saved empty .xlsx file laying around?
I encountered a similar issue yesterday and the solution is to change your Excel settings to create xlsx files by default.
In Excel: File -> Options -> Save -> Save files in this format
Your default is probably 'Excel 97-2003 (*.xls)' like mine was. If you change it to 'Excel Workbook (*.xlsx)', your code will work.

Duplicate an Excel chart and move it to another sheet

I am using the C# Excel interop and I want to create a copy of a chart from one sheet but I want this copy on another sheet. I have tried the following:
Excel.ChartObject chartTemplate = (Excel.ChartObject)sheetSource.ChartObjects("chart 1");
object o = chartTemplate.Duplicate();
Excel.ChartObject chart = (Excel.ChartObject)sheetSource.ChartObjects("chart 2");
chart.Name = "Skew" + expiry.ToString("MMMyy");
range = sheetDestination.Range["T" + chartRowCoutner.ToString()];
chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, range);
But when I try this, the last line throws an error:
An unhandled exception of type 'System.Exception' occurred in projectname.exe
Additional information: Error reading Excel file C:\ ...the file path...\template.xlsx: Value does not fall within the
expected range.
I have also tried passing a sheet in instead of a range:
chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, sheetDestination);
but this gives the same error. I can't understand the reason for the error or how to fix it / bypass it.
I am trying to avoid bringing the clipboard into this, but even if I try copying and pasting, I can still only paste it as an image, which is really not ideal:
Excel.ChartArea chartArea = chart.ChartArea;
chartArea.Copy();
range = sheetDestination.Range["T" + chartRowCoutner.ToString()]; // Note that chart is not on the sheet sheetDestination
range.PasteSpecial(Excel.XlPasteType.xlPasteAll);
The only other solution I can think of now is to do this in VBA and then execute the macro via the interop. But surely it can be done in a clean way just using the interop without the clipboard.
You've already got the solution but instead of giving you a fish for a day I'll give you a proper answer that will help you with any C# Excel coding task.
The C# Interop Model for Excel is almost identical to the VBA Excel Model.
This means it's trivial to convert VBA recorded macros to C#. Let's try this with an exercise like moving a chart to a different sheet.
In the Developer Tab in Excel click Record Macro > right click Chart > select Move Chart > choose Object in: Sheet2 > click OK > click Stop Macro Recording.
To see the recorded Macro press Alt + F11 to bring up the VB Editor:
See in the above screenshot how VBA shows you the second parameter for Location() is Name and it's actually a string argument...
Let's convert this VBA Macro to C#:
EDIT by #Ama
The advice below is outdated, there's actually no need to worry about releasing COM objects, this is done automatically at RELEASE mode (DEBUG mode does not). See Hans Passant's answer to "Clean up Excel Interop Objects with IDisposable".
The trick here is: never use 2 dots with com objects.
Notice how I could have written:
var sheetSource = workbookWrapper.ComObject.Sheets["Sheet1"];
but that has two dots, so instead I write this:
var workbookComObject = workbookWrapper.ComObject;
var sheetSource = workbookComObject.Sheets["Sheet1"];
Ref: How do I properly clean up Excel interop objects?
You will see the AutoReleaseComObject code in the above QA that projects like VSTOContrib use.
Here is the complete code:
using Microsoft.Office.Interop.Excel;
...
var missing = Type.Missing;
using (AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application> excelApplicationWrapper = new AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application>(new Microsoft.Office.Interop.Excel.Application()))
{
var excelApplicationWrapperComObject = excelApplicationWrapper.ComObject;
excelApplicationWrapperComObject.Visible = true;
var excelApplicationWrapperComObjectWkBooks = excelApplicationWrapperComObject.Workbooks;
try
{
using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapperComObjectWkBooks.Open(#"C:\Temp\ExcelMoveChart.xlsx", false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
{
var workbookComObject = workbookWrapper.ComObject;
Worksheet sheetSource = workbookComObject.Sheets["Sheet1"];
ChartObject chartObj = (ChartObject)sheetSource.ChartObjects("Chart 3");
Chart chart = chartObj.Chart;
chart.Location(XlChartLocation.xlLocationAsObject, "Sheet2");
ReleaseObject(chart);
ReleaseObject(chartObj);
ReleaseObject(sheetSource);
workbookComObject.Close(false);
}
}
finally
{
excelApplicationWrapperComObjectWkBooks.Close();
ReleaseObject(excelApplicationWrapperComObjectWkBooks);
excelApplicationWrapper.ComObject.Application.Quit();
excelApplicationWrapper.ComObject.Quit();
ReleaseObject(excelApplicationWrapper.ComObject.Application);
ReleaseObject(excelApplicationWrapper.ComObject);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
private static void ReleaseObject(object obj)
{
try
{
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) > 0);
obj = null;
}
catch (Exception ex)
{
obj = null;
Console.WriteLine("Unable to release the Object " + ex.ToString());
}
}
I know Releasing all the Objects, using GC.Collect and not using two dots when assigning seems over the top but at least when I quit the instance of Excel the process is freed, I don't have to programmatically kill the Excel process!
Ref: Microsoft KB: Office application does not quit after automation from .NET client
From the MSDN documentation here:
https://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.chart.location.aspx
it states that for the Name parameter of type object:
Name
Type: System.Object
The name of the sheet where the chart is embedded if Where is xlLocationAsObject or the name of the new sheet if Where is xlLocationAsNewSheet.
This is somewhat misleading from the example at the bottom of the same linked page. It would appear from the example given, that you should actually pass a string of the sheet name. The pertinent line from the example is copied below (the example is for copying to a new sheet):
chart1.Location(Excel.XlChartLocation.xlLocationAsNewSheet,
"Sales");
So, for moving to an existing sheet, I would do:
chart1.Location(Excel.XlChartLocation.xlLocationAsObject,
"ExistingSheetName");
Do NOT pass a range, workbook or worksheet object. Try a string of the sheet name.
Now, from the same MSDN document page linked above, if you want to reposition the chart within the page once you have moved it to another sheet, there are additional instructions, repeated here for convenience:
If you want to move a chart to another position on a sheet, use the P:Microsoft.Office.Interop.Excel.ChartArea.Top property and P:Microsoft.Office.Interop.Excel.ChartArea.Left property of the ChartArea. You can get the ChartArea object of the Chart by using the ChartArea property.
If you're moving a chart to an existing sheet, be careful not to overlap your chart over existing data. If so, you will have to code around that separately.
This isn't the answer to the question you asked, but might be fruitful
if you're making a copy and editing it for different variations THIS IS NOT A SOLUTION
if you're truly just copying a chart then I recommend using Excel's "Camera" function instead. It basically creates a window into another sheet - you can do this programmatically and it's well documented, but a little known feature of excel I thought I'd be remiss if I didn't point out.
-E
If you are looking to make edits & the question is still open let me know that in a comment - I've done this before I just need to look back in my workbook and see exactly how I did it.
'Camera option is nice because it doesn't 'recalculate' the data - so I imagine it operates faster; a concern in large workbooks.

How to load vba macros to the excel file from a text file by a C# code?

My C# code has to create an Excel file with two Worksheets and output some data over there. Besides data columns, the Sheet 1 has to be enabled with a VBA macros which would allow a user to perform some mathematical calculations with provided data upon clicking on a particular cell. This VBA macros are stored in a text file, like C:\VBA_MACROS\VBA1.txt.
Right now I can do it manually, i.e.
C# code creates an Excel file and populates it with data.
I do a right click on Sheet1 and select an option "View Code".
I click on the button "Insert" from the Microsoft Visual Basic for Applications Menu and load the file C:\VBA_MACROS\VBA1.txt.
I close the VBA code window.
Question: can steps 2 - 4 be performed automatically by a C# code as well as the step 1? In this case a user would not have to perform them manually which would be a way more comfortable for her.
To be exact, this is how the application is created:
Excel.Application application = new Excel.Application();
Excel.Workbook workbook = application.Workbooks.Add();
Excel.Worksheet worksheet = workbook.Sheets[1];
Excel.Worksheet worksheet2 = workbook.Sheets[2];
// output data to the worksheets
DataTable2Worksheet(tableMain, worksheet, verSize);
DataTable2Worksheet(tableExtra, worksheet2, 0);
// output workbook to the file
string fileDir = #"D:\MyTests\ExcelTests\";
Output2File(fileDir, workbook);
DataTable2Worksheet and Output2File functions are quite trivial, but how to attach the content of the text file to worksheet = workbook.Sheets[1] by using AddFromFile method?
You'll need a reference to Microsoft.Vbe.Interop;.
Then you need to get a handle on the module you want to insert into.
Then you can just use the CodeModule.AddFromFile method to insert the code in your text file into the module.
VBE.VBProjects("NameOfProject").VBComponents.Item("NameOfWorksheet").CodeModule.AddFromFile("C:\Path\to\file.txt");
The default name for a newly created project is "VBAProject" and you the name of the component for a sheet is the name of the sheet.
So, for your particular case, you could add this line of code at the end of your snippet to insert the VBA into Sheet1.
application.VBE.VBProjects("VBAProject").VBComponents.Item("Sheet1").CodeModule.AddFromFile("C:\VBA_MACROS\VBA1.txt");
I just learned that another option is to use the VBProject property of the Workbook, which makes the call a little cleaner.
workbook.VBProject.VBComponents.Item("Sheet1").CodeModule.AddFromFile("C:\VBA_MACROS\VBA1.txt");

How can I read an Excel 2010 file in my C# code using a DLL?

UPDATE1:
I am using Excel 2010 and I've searched the web and found thousands upon thousands of ways to do this via win form, console, etc. But I can't find a way to do this via DLL. and none of the sample on-line is complete all in bit and pieces.
UPDATE END
I have looked and goggled but did not get the specific what i am looking for, as show below the excel sample sheet.
i'm looking a way to read and store the each cell data in a variable
i have started something like this:
Workbook workbook = open(#"C:\tmp\MyWorkbook.xls");
IWorksheet worksheet = workbook.Worksheets[0];
IRange a1 = worksheet.Cells["A1"];
object rawValue = a1.Value;
string formattedText = a1.Text;
Console.WriteLine("rawValue={0} formattedText={1}", rawValue, formattedText);
Your code can work with a couple changes.
One thing to remember is that Excel worksheets are 1-based, not 0-based (and use Worksheet instead of IWorksheet):
Worksheet worksheet = workbook.Worksheets[1];
And to get a range, it is easiest to call get_Range() on the worksheet object (and use Range instead of IRange):
Range a1 = worksheet.get_Range("A1");
With those two lines of code changed, your example will work fine.
UPDATE
Here is a "complete" example:
Right-click your project in the solution explorer and click "Add
Reference".
Click on the COM tab and sort the list by Component Name. Find "Microsoft Excel 14.0 Object Library" in the list and select it. Click OK.
In the code file where you want this to run, add a using Microsoft.Office.Interop.Excel;
Use this code, which I've modified as little as possible from your example:
var excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(#"C:\tmp\MyWorkbook.xls");
Worksheet worksheet = workbook.Worksheets[1];
Range a1 = worksheet.get_Range("A1");
object rawValue = a1.Value;
string formattedText = a1.Text;
Console.WriteLine("rawValue={0} formattedText={1}", rawValue, formattedText);
Excel.Sheets sheets = workbook.Worksheets;
Excel.Worksheet worksheet = (Excel.Worksheet)sheets.get_Item(1);
System.Array myvalues;
Excel.Range range = worksheet.get_Range("A1", "E1".ToString());
myvalues = (System.Array)range.Cells.Value;
If you don't want to be in a war with com components and registering dlls,
the best way to read excel is Excel Reader for .NET
I have been using it for so long time , and I can say it just works.
and excelReader.IsFirstRowAsColumnNames property makes everything easy.
You can play your data within a dataset.

Save Entire Workbook as PDF Excel 2010 (C#)

Is there anyway to save you entire workbook as a pdf in excel. I found this, http://msdn.microsoft.com/en-us/library/bb407651(v=office.12).aspx, but it does not exactly tell you if it saves the entire workbook as a pdf or just the active sheet. If there is no way to save the entire workbook to pdf, would printing the entire workbook be the best option, or even possible in C#? Below is what I have thus far I just need it to save as pdf so I can send in an email. Thanks for the help.
using Excel = Microsoft.Office.Interop.Excel; //Excel Reference
//Gets Excel and gets Activeworkbook and worksheet
Excel.Application oXL;
Excel.Workbook oWB;
Excel.Worksheet oSheet;
//Create New Instance in Excel
oXL = new Excel.Application();
oXL.Visible = true;
//Open Excel Workbook
oWB = oXL.Workbooks.Open("");
oWB = (Excel.Workbook)oXL.ActiveWorkbook;
oSheet = (Excel.Worksheet)oWB.ActiveSheet;
//Modify Excel Spreadsheet Based on Form
oSheet.Cells[6, 4] = maskedTextBox1.Text; //Change Value in Cell, Cell Location [y-axis, x-axis]
//Save Workbook As
oWB.SaveAs("");
//Save Workbook As PDF
//Close Workbook
oWB.Close("");
//Quit Excel
oXL.Quit();
In 2010 you can save the entire workbook in PDF by making each sheet an "Active" sheet.
Sounds strange but if you notice the print options when you do a pdf there is no option for workbook. To get around this open an excel file and fill in some data in 2-3 work sheets. Now hold your ctrl key and click on each other workbook, it will then become a "Group".
You will notice the [GROUP] name appear at the top of the excel file and now when you print the excel file it will print the entire workbook.
Try this out for yourself. In code, you just need to make each work sheet an active worksheet. I don't work much with the excel object model but it might be worth doing a macro for this and looking at the code.
I recorded a macro and here is the VBA:
Sheets(Array("Sheet1", "Sheet2", "Sheet3")).Select
Looks as though you just need to store each sheet in an array and then simply
Sheets(MyArray).Select
This will then make all sheets active and [grouped] and then you can run a print out to pdf. By recording the macro it also presented the options to print to pdf:
`ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
"C:\Users\MyAccount\Desktop\test.pdf", Quality:=xlQualityStandard, _
IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:= _
True`
In this case active sheet is your group of sheets that you have stored in an array.

Categories