Im just starting to use EPPLus Lib to create "complex" workbooks via C#, and i just ran into some trouble while trying to create two pivot tables.
The first one creates fine, but when i try to create the second one it doesnt throw any exceptions but when i try to open the worknook using excel it says
"Excel found unreadable content in 'myworkbook.xlsx'. Do you want to
recover the contents of this workbook? If you trust the source of this
workbook, clickYes"
And when i press 'yes':
Repair log ->
Removed Feature: PivotTable report from /xl/pivotTables/pivotTable2.xml part (PivotTable > view) Removed
Records: Workbook properties from /xl/workbook.xml part (Workbook)
Repaired Records: Workbook properties from /xl/workbook.xml part
(Workbook)
Here's the code that i build:
CreatePivotTable("Pivot1", "Pivot1", rng1);
CreatePivotTable("Pivot2", "Pivot2", rng2);
public void CreatePivotTable(string pivotSheet, string pivotName, ExcelRangeBase srcRange)
{
if (m_wb.Worksheets[pivotSheet] != null)
m_wb.Worksheets.Delete(pivotSheet);
var ws = m_wb.Worksheets.Add(pivotSheet);
var pivot = ws.PivotTables.Add(ws.Cells["A1"], srcRange, pivotName);
}
Any ideas?
Thanks!
What was wrong and i didnt put it in my question was that i was reopening the workbook on step before, like this:
CreatePivotTable("Pivot1", "Pivot1", rng1);
Save();
CreatePivotTable("Pivot2", "Pivot2", rng2);
private void Save()
{
m_writer.Save();
m_writer.OpenWorkbook ();
}
And since the save method of epplus closes the workbook, the program lost some sort of reference or just got lost with some info.
In short, to use epplus correctly, you should write everything u need before saving and closing the workbook, its not good to reopen.
Thank you.
Related
I use the EPPlus library to batch edit some existing XLSM files. Inside the files I replace a line of VBA code and that's it. Everything works nice, if I edit the same line in the Excel code editor by hand.
When I open some of the files with Excel 2013 (15.0.4989.1000), the following error message is shown.
We found a problem with some content in 'test.xlsm'. Do you want us to
recover as much as we can? If you trust the source of this workbook,
click Yes.
If I click yes, the repair report shows the following entry. But the message is somewhat too generic to help me further.
Removed Records: Named range from /xl/workbook.xml-Part (Arbeitsmappe)
This is my C# code, which edits the XLSM file. Can I update my code or do I have to update the XLSM-file before editing it?
static void PatchVba(string filePath, string oldCode, string newCode)
{
var wbFileInfo = new FileInfo(filePath);
using (var package = new ExcelPackage(wbFileInfo, false))
{
foreach (var m in package.Workbook.VbaProject.Modules)
{
if (m.Code.Contains(oldCode))
{
m.Code = m.Code.Replace(oldCode, newCode);
Console.WriteLine("VBA Patched in \"{0}\"", filePath);
}
}
try
{
package.SaveAs(wbFileInfo);
}
catch
{
Console.WriteLine("Could not save patched file \"{0}\".", filePath);
}
}
}
I found out what the problem is. In the edited XLSM-file, a range name is used multiple times with overlapping scope. I was too focused on my C# code to find the root cause.
So removing the named ranges solves the issue. But it would still be interesting to know, why I can edit it without problems using Excel, but not by using EPPlus.
I am creating an excel with drop down values using Open XML (DocumentFormat.OpenXML). There are no errors in the code and excel gets created. But I get couple of errors while opening the excel. Excel gets opened with data correctly if I click OK to the errors and go ahead. It looks like the problem is due to the addition of namespace in order to have the dropdown values.
Question is, Is there any way to get rid of the error?
Error 1:
We found a problem with some content in 'sample.xlsx'. Do you want us
to try to recover as much as we can? If you trust the source of this
workbook, click Yes.
Error 2:
Errors were detected in file 'Sample.xlsx'
Repaired Records: Cell information
from /xl/worksheets/sheet2.xml part
Repaired Records: Cell information from
/xl/worksheets/sheet3.xml part
C# Namespace addition for creating excel:
worksheet1 = new Worksheet() { MCAttributes = new MarkupCompatibilityAttributes() { Ignorable = "x14ac" } };
worksheet1.AddNamespaceDeclaration("x14ac", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac");
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.
We have a VSTO addin for Excel. The main functionality creates reports that are used to generate workbooks. When I run a batch of reports, I get a System.AccessViolationException when using Excel.Worksheet.Copy, which also crashes Excel. Here's how I recreate it:
1) Open and run report #1 with a single parameter which creates one workbook. We close the workbook.
2) Open and run the same report with several parameters. This create 5 workbooks but crashes when creating the second, but ONLY if we have run the first single output report (see step 1). If we remove the report from step 1 from the batch, this creates all 5 workbooks without error.
I've checked to make sure that the sheet we are copying is from the workbook is open, and is not referencing the first report. In fact, we close the first one so I know that it's not. Again, this ONLY happens if we have the report in step one, which it does not access at all, so how could that be affecting a sheet from a completely different workbook?
This doesn't even finish out my try/catch so that I can get more info. It simply blows up Excel and I have to restart.
UPDATE:
Here's the basic code:
function void ReplaceSheets(Dictionary<Excel.Worksheet, IReportSheet> sheetReports)
{
List<string> oldNames = new List<string>(sheetReports.Count);
foreach (Excel.Worksheet oldSheet in sheetReports.Keys)
{
Excel.Worksheet veryHiddenSheet = null;
Excel.Worksheet newSheet = null;
try
{
string sheetName = oldSheet.Name;
veryHiddenSheet = WorkbookHelper.FindSheet(this.DocumentView, MakeHiddenSheetName(sheetName, "--VH--"));
veryHiddenSheet.Visible = Excel.XlSheetVisibility.xlSheetVisible; //Sheet has to be visible to get the copy to work correctly.
veryHiddenSheet.Copy(this.DocumentView.Sheets[1], Type.Missing);//This is where it crashes
newSheet = (Excel.Worksheet)this.DocumentView.Sheets[1]; //Get Copied sheet
/* do other stuff here*/
}
finally
{
veryHiddenSheet = null;
newSheet = null;
}
}
}
I never found a way in VSTO to "fix" this. I switched code to NetOffice, and I was able to get some better error message. Excel/Com was not releasing the memory attached to the spreadsheets. I rebuilt the reports from blank 2010 spreadsheets and it took care of it. I think it was a corrupted 2007 spreadsheet that may have occured on converting to 2010 or something like that. I recommend NetOffice over VSTO because the exception handling is far superior, and you have access to the source code, but it does have it's quirks. (You'll need to pay attention to loading order for taskpanes.)
I am trying to get the name of the workbook before it actually opens up.
((Excel.AppEvents_Event)this.Application).WorkbookOpen += new Excel.AppEvents_WorkbookOpenEventHandler(App_WorkBookOpen);
private void App_WorkBookOpen(Excel.Workbook Wb)
{
System.Windows.Forms.MessageBox.Show("Shakti " + " " + Wb.Name);
}
With the handler as shown above, Excel application shows the workbook name when it is opened completely.My intention is to do some formal check before it is actually opened up and data is shown to the user.
Is there any way or mechanism to extract the file name before the contents are loaded on to Excel and shown to the user? Any sort of help is highly appreciated.Thanks.
AFAIK you can't do that. But like I mentioned in my comment you could hide the workbook the moment it is visible. So the user will see the workbook open for a split second and then go invisible. In that split second you can read the name of the workbook and then hide the workbook.
Based on your calculations/conclusion you can then close/unhide the workbook as required.
You can hide the workbook using
Wb.Windows[1].Visible = false;
No you can't.
You anyway could create a Macro on a WorkBook Module with Open class tag as here:
Private Sub Workbook_Open()
Dim ws As Workbooks
For Each ws In ActiveWorkbook.Worksheets
MsgBox ws.Name
Next
ActiveWorkbook.Worksheets.Close
End Sub
Then call this sub via c# on opening the file, this sub runs before loading the workbook then it closes it. It has not that sense, because you'll never access the wb again...
Maybe with some tweaking here and there you could accomplish your task, but it depends to you.
Hope it helps...
Isn't Wb.name the same as the filename? In which case, since you must know the filename/location in order to open it, you can check it beforehand?