I want to convert an excel file to an image (every format is ok) programmatically (c#). Currently I'm using Microsoft Interop Libraries & Office 2007, but it does not support saving to an image by default.
So my current work-around is as follows:
Open Excel file using Microsoft Interop;
Find out the max range (that contains data);
Use the CopyPicture() on that range, which will copy the data to the Clipboard.
Now the tricky part (and my problems):
Problem 1:
Using the .NET Clipboard class, I'm not able to get the EXACT copied data from the clipboard: the data is the same, but somehow the formatting is distorted (the font of the whole document seems to become bold and a little bit more unreadable while they were not); If I paste from the clipboard using mspaint.exe, the pasted image is correct (and just as I want it to be).
I disassembled mspaint.exe and found a function that it is using (OleGetClipboard) to get data from the clipboard, but I cannot seem to get it working in C# / .NET.
Other things I tried were the Clipboard WINAPI's (OpenClipboard, GetClipboardData, CF_ENHMETAFILE), but the results were the same as using the .NET versions.
Problem 2:
Using the range and CopyPicture, if there are any images in the excel sheet, those images are not copied along with the surrounding data to the clipboard.
Some of the source code
Excel.Application app = new Excel.Application();
app.Visible = app.ScreenUpdating = app.DisplayAlerts = false;
app.CopyObjectsWithCells = true;
app.CutCopyMode = Excel.XlCutCopyMode.xlCopy;
app.DisplayClipboardWindow = false;
try {
Excel.Workbooks workbooks = null;
Excel.Workbook book = null;
Excel.Sheets sheets = null;
try {
workbooks = app.Workbooks;
book = workbooks.Open(inputFile, false, false, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing);
sheets = book.Worksheets;
} catch {
Cleanup(workbooks, book, sheets); //Cleanup function calls Marshal.ReleaseComObject for all passed objects
throw;
}
for (int i = 0; i < sheets.Count; i++) {
Excel.Worksheet sheet = (Excel.Worksheet)sheets.get_Item(i + 1);
Excel.Range myrange = sheet.UsedRange;
Excel.Range rowRange = myrange.Rows;
Excel.Range colRange = myrange.Columns;
int rows = rowRange.Count;
int cols = colRange.Count;
//Following is used to find range with data
string startRange = "A1";
string endRange = ExcelColumnFromNumber(cols) + rows.ToString();
//Skip "empty" excel sheets
if (startRange == endRange) {
Excel.Range firstRange = sheet.get_Range(startRange, endRange);
Excel.Range cellRange = firstRange.Cells;
object text = cellRange.Text;
string strText = text.ToString();
string trimmed = strText.Trim();
if (trimmed == "") {
Cleanup(trimmed, strText, text, cellRange, firstRange, myrange, rowRange, colRange, sheet);
continue;
}
Cleanup(trimmed, strText, text, cellRange, firstRange);
}
Excel.Range range = sheet.get_Range(startRange, endRange);
try {
range.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlPicture);
//Problem here <-------------
//Every attempt to get data from Clipboard fails
} finally {
Cleanup(range);
Cleanup(myrange, rowRange, colRange, sheet);
}
} //end for loop
book.Close(false, Type.Missing, Type.Missing);
workbooks.Close();
Cleanup(book, sheets, workbooks);
} finally {
app.Quit();
Cleanup(app);
GC.Collect();
}
Getting data from the clipboard using WINAPI succeeds, but with bad quality. Source:
protected virtual void ClipboardToPNG(string filename) {
if (OpenClipboard(IntPtr.Zero)) {
if (IsClipboardFormatAvailable((int)CLIPFORMAT.CF_ENHMETAFILE)) {
int hEmfClp = GetClipboardDataA((int)CLIPFORMAT.CF_ENHMETAFILE);
if (hEmfClp != 0) {
int hEmfCopy = CopyEnhMetaFileA(hEmfClp, null);
if (hEmfCopy != 0) {
Metafile metafile = new Metafile(new IntPtr(hEmfCopy), true);
metafile.Save(filename, ImageFormat.Png);
}
}
}
CloseClipboard();
}
}
Anyone got a solution? (I'm using .NET 2.0 btw)
From what I understand from your question I am not able to reproduce the problem.
I selected a range manually in Excel, chose Copy As Picture with the options as shown on screen and Bitmap selected, then I used the following code to save the clipboard data:
using System;
using System.IO;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
using Excel = Microsoft.Office.Interop.Excel;
public class Program
{
[STAThread]
static void Main(string[] args)
{
Excel.Application excel = new Excel.Application();
Excel.Workbook wkb = excel.Workbooks.Add(Type.Missing);
Excel.Worksheet sheet = wkb.Worksheets[1] as Excel.Worksheet;
Excel.Range range = sheet.Cells[1, 1] as Excel.Range;
range.Formula = "Hello World";
// copy as seen when printed
range.CopyPicture(Excel.XlPictureAppearance.xlPrinter, Excel.XlCopyPictureFormat.xlPicture);
// uncomment to copy as seen on screen
//range.CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlBitmap);
Console.WriteLine("Please enter a full file name to save the image from the Clipboard:");
string fileName = Console.ReadLine();
using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
{
if (Clipboard.ContainsData(System.Windows.DataFormats.EnhancedMetafile))
{
Metafile metafile = Clipboard.GetData(System.Windows.DataFormats.EnhancedMetafile) as Metafile;
metafile.Save(fileName);
}
else if (Clipboard.ContainsData(System.Windows.DataFormats.Bitmap))
{
BitmapSource bitmapSource = Clipboard.GetData(System.Windows.DataFormats.Bitmap) as BitmapSource;
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.QualityLevel = 100;
encoder.Save(fileStream);
}
}
object objFalse = false;
wkb.Close(objFalse, Type.Missing, Type.Missing);
excel.Quit();
}
}
Regarding your second problem: As far as I know it is not possible in Excel to select both a cell range and an image at the same time. If you want to get both in an image at the same time you might have to print the Excel sheet to an image/PDF/XPS file.
SpreadsheetGear for .NET will do it.
You can see our ASP.NET (C# and VB) "Excel Chart and Range Imaging Samples" samples here and download a free trial here if you want to try it out.
SpreadsheetGear also works with Windows Forms, console applications, etc... (you did not specify what type of application you are creating). There is also a Windows Forms control to display a workbook in your application if that is what you are really after.
Disclaimer: I own SpreadsheetGear LLC
This is a bug with GDI+ when it comes to converting metafiles to a bit map format.
It happens for many EMFs that displays charts with texts. To re-create, you simply need to create a chart in excel that displays data for its X and Y axis. Copy the chart as a picture and paste in word as a metafile. Open the docx and you will see an EMF in the media folder. If you now open that EMF in any windows based paint program that converts it to a bitmap, you will see distortions, in particular, text and lines become larger and distorted. Unfortunately, it is one of those issues that Microsoft is not likely to admit or do anything about. Let's hope Google and Apple take over the office/word processing world soon as well.
Because asp.net thread does not have the right ApartmentState to access Clipboard Class, so you must write code to access Clipboard in new thread. For example:
private void AccessClipboardThread()
{
// access clipboard here normaly
}
in main thread:
....
Excel.Range range = sheet.get_Range(startRange, endRange); //Save range image to clipboard
Thread thread = new Thread(new ThreadStart(AccessClipboardThread));
thread.ApartmentState = ApartmentState.STA;
thread.Start();
thread.Join(); //main thread will wait until AccessClipboardThread finish.
....
Interestingly I have been doing this in a STA compartment for some while with success. I wrote an app that runs on a weekly basis and mails out project status reports including some graphs I generate programmatically using Excel.
Last night this failed the graphs all returned null. I'm debugging today and find no explanation just that the method Clipboard.GetImage() returns null suddenly which it did not. By setting a breakpoint at this call, I can effectively demonstrate (by pressing CTRL+V in MS-Word) that the image IS indeed in the clipboard. Alas continuing on Clipboard.GetImage() returns null (whether I'm snooping like this or not).
My code runs as a console app and the Main method has the [STAThread] attribute. I debug it as a windows forms app (all my code is in a library and I simply have two front ends to that).
Both return null today.
Out of interest I spun off the chart fetcher into a thread as outlined (and note that thread.ApartmentState is deprecated), and it runs sweet, but still, nulls are returned.
Well, I gave up and did what any IT geek would do, rebooted.
Voila ... all was good. Go figure ... is this why we all loathe computers, Microsoft Windows and Microsoft Office? Hmmmm ... There is something , something entirely transient that can happen to you PC that makes Clipboard.GetImage() fail!
If you don't mind Linux (style), you can use OpenOffice (or LibreOffice) to convert the xls first to pdf, then use ImageMagic to convert the pdf to image. A basic guide can be found at http://www.novell.com/communities/node/5744/c-linux-thumbnail-generation-pdfdocpptxlsimages .
Also, there seems to be .Net APIs for both of the programs mentioned above. See:
http://www.opendocument4all.com/download/OpenOffice.net.pdf
and
http://imagemagick.net/script/api.php#dot-net
Related
I have to open an excel file and want to get the data to word a document. I use to this code:
private Excel.Application excelapp;
Type ExcelType = Type.GetTypeFromProgID("Excel.Application");
dynamic ExcelInst = Activator.CreateInstance(ExcelType);
this.excelapp = ExcelInst;
this.workbook = this.excelapp.Workbooks.Open(Filename : this.filePath, ReadOnly: true);
I used all the techniques to close/quit/dispose the opened process (except for Process.Kill), but those won't work. How do I fully terminate any running background tasks?
You just need to call the Quit method of the Excel Application class when you are done:
private Excel.Application excelapp;
Type ExcelType = Type.GetTypeFromProgID("Excel.Application");
dynamic ExcelInst = Activator.CreateInstance(ExcelType);
this.excelapp = ExcelInst;
this.workbook = this.excelapp.Workbooks.Open(Filename : this.filePath, ReadOnly: true);
' do whatever you need there
this.workbook.Save();
this.excelapp.Quit();
If unsaved workbooks are open when you use this method, Excel displays a dialog box asking whether you want to save the changes. You can prevent this by saving all workbooks before using the Quit method or by setting the DisplayAlerts property to False. When this property is False, Excel doesn't display the dialog box when you quit with unsaved workbooks; it quits without saving them.
If you set the Saved property for a workbook to True without saving the workbook to the disk, Excel will quit without asking you to save the workbook. Note: this does not save the workbook; it just makes it look like it's saved.
My understanding is that since interop uses “COM” objects, you need to “release" those objects. Something like…
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(xlApp);
in that order. This should release those objects from the background tasks
I have had success with the following approach when using office-interop…
Set up all the Excel/Word/Outlook. Etc.… interop stuff in a try/catch/finally statement. This should almost guarantee that your code will properly close and “release” the resources it uses even if there is an exception. Otherwise, the code will always be susceptible to leaking a resource.
Word.Application app = null;
Word.Document doc = null;
try {
app = new Word.Application();
//app.Visible = true;
doc = app.Documents.Add();
// do some stuff with the document…
doc.SaveAs2(filename);
MessageBox.Show("Document created successfully !");
}
catch (Exception e) {
MessageBox.Show("ERROR: " + e.Message);
}
finally {
if (doc != null) {
doc.Close();
Marshal.ReleaseComObject(doc);
}
if (app != null) {
app.Quit();
Marshal.ReleaseComObject(app);
}
}
I use the following to open excel and read / update it.
Excel.Application app;
Excel.Workbook wb;
private void openAndRead()
{
app = new Excel.Application();
wb = app.Workbooks.Open(YourFileHere);
//do stuff here read or write
//app.ActiveSheet.Cells[1, "A"] = "write this in cell A1";
//string read= app.ActiveSheet.Cells[1, "A"];
}
private void closeExcel ()
{
app.DisplayAlerts = false; //this will stop popup questions from excel
wb.Close();
app.Quit();
}
If you want to be confident that EXCEL.EXE process will go away, there's one way to realize it. May be this is not the elegant solution, but at least it does work. The main idea is to catch the Excel window's handle. Having this handle, you can get its process and shut it down.
WARNING: If you don't make Excel window visible, this method WILL NOT work!
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
void Kill_Excel()
{
// Create instance of Excel application.
// If you don't make Excel window visible, this method WILL NOT work!
Excel.Application excel = new Excel.Application { Visible = true };
// Catch Excel window handle
IntPtr hwnd = new IntPtr(excel.Hwnd);
// Get EXCEL.EXE process
Process xlproc = Process.GetProcesses().Where(p => p.MainWindowHandle == hwnd).First();
// Do some operations
Excel.Workbook book = excel.Workbooks.Add();
Excel.Worksheet sheet = book.Sheets[1] as Excel.Worksheet;
sheet.Cells[1, 1] = "Hello!";
// Close Excel
book.Close(SaveChanges: false);
excel.Quit();
// Garbage collection
GC.Collect();
GC.WaitForFullGCComplete();
// Kill process - FOR SURE! :)
xlproc.Kill();
}
I wrote a program in C# that is supposed to convert each worksheet in my excel workbook to a csv and save it in their own files. The problem I'm having is that when I open each file, they all have the same content as the very last worksheet. Here is my code:
public void Main()
{
Excel.Application excelApp = new Excel.Application();
Excel.Workbook workbook = excelApp.Workbooks.Open(#"C:\Users\user\Desktop\Book1.xlsx");
foreach (Excel.Worksheet sht in workbook.Worksheets)
{
sht.Select();
System.Diagnostics.Debug.WriteLine(sht.Name.ToString());
workbook.SaveAs(string.Format("{0}{1}.csv", #"C:\Users\user\Desktop\", sht.Name), Excel.XlFileFormat.xlCSV, Excel.XlSaveAsAccessMode.xlNoChange);
}
workbook.Close(false);
Dts.TaskResult = (int)ScriptResults.Success;
}
Any help would be great, thanks!
Update 1
I don't know if it's worth mentioning that I'm trying to do this through a script task in SSIS. So it's just one script task that I run that contains the code above.
Trying to figure out the issue
In normal cases, the code you provided will work perfectly. It may encounter some issue in case that the excel application has shown a message box, need permissions to enable editing, there are permissions issue to access other worksheets since they are protected ...
First of all, open the excel manually and check that you can access all worksheets and perform save operations manually. If you didn't encountered any issue, then you should prevent excel from showing message boxes or other promotion while using Interop.Excel library.
In addition, check that the Csv does not already exists in the destination path.
Try using a similar code:
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Visible = false;
excelApp.DisplayAlerts = false;
Microsoft.Office.Interop.Excel.Workbook workbook = excelApp.Workbooks.Open(#"D:\Book1.xlsx");
workbook.DoNotPromptForConvert = true;
workbook.CheckCompatibility = false;
foreach (Microsoft.Office.Interop.Excel.Worksheet sht in workbook.Worksheets)
{
sht.Select();
System.Diagnostics.Debug.WriteLine(sht.Name.ToString());
if (System.IO.File.Exists(string.Format("{0}{1}.csv", #"D:\", sht.Name)))
{
System.IO.File.Delete(string.Format("{0}{1}.csv", #"D:\", sht.Name);
}
workbook.SaveAs(string.Format("{0}{1}.csv", #"D:\", sht.Name),
Microsoft.Office.Interop.Excel.XlFileFormat.xlCSV, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange);
}
//workbook.Close(false);
workbook.Close(false, Type.Missing, Type.Missing);
excelApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
I tested the following code and it converted all Worksheets successfully.
I need to extract images from excel with start&&end row no. and start && end col no
My current code is as below:-
var excelApp = new Application();
var wb = excelApp.Workbooks.Open(filePath, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing,
Type.Missing, Type.Missing, Type.Missing, Type.Missing);
var ws = (Worksheet)wb.Worksheets["Sheet1"];
int startCol = 0;
int startRow =0;
int endCol = 0;
int endRow = 0;
foreach (var pic in ws.Pictures())
{
int startCol = pic.TopLeftCell.Column;
int startRow = pic.TopLeftCell.Row;
int endCol = pic.BottomRightCell.Column;
int endRow = pic.BottomRightCell.Row;
}
Above code works fine when all images are different but when I put the same image in different cells than it picks only first one.
For example,
Works fine when i put abc.jpeg at B1 cell and xyz.jpeg at C5 cell then results are
two object first startRow=1,endRow=1,startCol=1,endCol=1 and
second startRow=5,endRow=5,startCol=2,endCol=2
But if I put abc.jpeg at B1 cell and C5 cell then result is one object with startRow=1,endRow=1,startCol=1,endCol=1 for both images.It doesn't pic the second image.
Why it's happing?Is there any solution using interop or npoi
TL;DR - NPOI behaves the same as Excel Interop, returning one image when the same image is added twice. It likely does so for the same reason. EPPlus (the last test in this post) handles this scenario the way you would expect, identifying both instances of the picture separately and returning their positions on the worksheet.
I tried first with NPOI. I created a workbook and inserted the same picture into the first sheet in two locations.
-----
| |
-----
-----
| |
-----
Using NPOI
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NPOI.XSSF.UserModel;
using System.IO;
namespace ExcelImageTests
{
[TestClass]
public class NpoiExcelImages
{
[TestMethod]
public void FindsTwoDistinctImagesInFile()
{
XSSFWorkbook workbook;
using (var file = new FileStream(#"C:\Users\path-to-my-file\sotest.xlsx",
FileMode.Open, FileAccess.Read))
{
workbook = new XSSFWorkbook(file);
}
var pictures = workbook.GetAllPictures();
Assert.AreEqual(2, pictures.Count);
}
}
}
This is as far as I got with NPOI. The test fails. NPOI counts one picture, not two.
What's odd is that it also has no reference to shapes, pictures, or drawings at the sheet level. The picture returned is of type XSSFPictureData and contains the binary data for the picture. It doesn't refer to the relationship between the worksheet and the picture. I suspect that's why it's only returning one. There's one image embedded twice.
To confirm I added another picture distinct from the first two. Now the test passed. There are three pictures visible on the sheet but two distinct pictures returned by GetAllPictures().
You mentioned Interop and NPOI, but another option is EPPlus. It's more commonly used, and after a few minutes with NPOI I can see why. NPOI returns a lot of object types, just like Excel Interop, and you have to know what they are so you can cast them to those types.
EPPlus is just a whole lot better. Here's the same test with EPPlus:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OfficeOpenXml;
using System.IO;
namespace ExcelImageTests
{
[TestClass]
public class EPPlusExcelImages
{
[TestMethod]
public void FindsTwoDistinctImagesInFile()
{
var file = new FileInfo(#"C:\Users\path-to-my-file\sotest.xlsx");
using (var package = new ExcelPackage(file))
{
var workbook = package.Workbook;
var sheet = workbook.Worksheets[1];
Assert.AreEqual(2, sheet.Drawings.Count)
var drawingOne = sheet.Drawings[0];
var drawingTwo = sheet.Drawings[1];
// From returns the position of the upper left corner of the picture.
// To returns the position of the lower right corner.
Assert.IsTrue(drawingOne.From.Row < drawingTwo.From.Row);
Assert.IsTrue(drawingOne.From.Column < drawingTwo.From.Column);
Assert.IsTrue(drawingOne.To.Row < drawingTwo.To.Row);
Assert.IsTrue(drawingOne.To.Column < drawingTwo.To.Column);
}
}
}
}
This test passes. It detects two images, and correctly tells me their relative positions. You don't have my worksheet, but I checked and the rows and columns are all correct.
One odd detail is that the worksheet index is 1-based but the rows and columns are 0-based. But that's no big deal.
Also, while all of the objects returned from the package are IDisposable, most examples only show disposing the package itself. One person noted that the Dispose methods for the other objects are empty. That's weird. But it's still better then Excel Interop where you have to release COM objects.
I am trying to an Add-in directly from C# so that when I open a workbook, and do a Workbook.Calculate() the UDF's (User Defined Functions) that are defined in an external addin correctly calculate in the worksheet. Currently, I am looping through each adding and simple setting:
AddIn.Installed = true
This does not work. C# does not load add-in at all, and I want to avoid using VBA. I want to open a workbook an excel workbook with the specific add in loaded, do a full calculated, and should have all values of the worksheet updated, including cells with UDF's.
Thanks for any help....
Some code:
Excel.Workbook wkbk = ExcelApp.ActiveWorkbook;
Excel.XlFixedFormatType paramExportFormat = Excel.XlFixedFormatType.xlTypePDF;
Excel.XlFixedFormatQuality paramExportQuality = Excel.XlFixedFormatQuality.xlQualityStandard;
bool paramOpenAfterPublish = false;
bool paramIncludeDocProps = true;
bool paramIgnorePrintAreas = true;
object paramFromPage = Type.Missing;
object paramToPage = Type.Missing;
ExcelApp.Visible = true;
//foreach (Excel.AddIn aiTemp in ExcelApp.AddIns)
//{
// if (aiTemp.Name.Contains(""))
// {
// aiTemp.Installed = false;
// aiTemp.Installed = true;
// }
//}
while (ExcelApp.CalculationState == Microsoft.Office.Interop.Excel.XlCalculationState.xlCalculating)
{
Thread.Sleep(50);
}
ExcelApp.CalculateFull();
var wksht = wkbk.ActiveSheet;
Excel.Range rng = ((Excel.Worksheet)wksht).get_Range("B1", "B1");
rng.Calculate();
//EnsureCalcFinished();
ExcelApp.Visible = false;
wkbk.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, PathToDocument.Replace(".xlsx", ".pdf"), paramExportQuality, true, false, Type.Missing, Type.Missing, true,Type.Missing);
UPDATE:
I found a link with the method I use to register UDFs.
Ref: http://msdn.microsoft.com/en-us/library/ms173189(v=vs.80).aspx
In Excel, you need to go to Options -> Add-Ins => Excel Add-in (Go..) => automation = select the library and hit OK. Once you do this once, it'll be auto-loaded each time you open excel.
Here is the algorithm to load excel AddIns so that when you open excel workbook, a calculation will include UDF's defined in Add-in:
1)Initiliaze Excel Application
ExcelApp = new Excel.Application();
2)Load AddIns *
foreach (Excel.AddIn ai in ExcelApp.AddIns)
{
ai.Installed = false;
ai.Installed = true;
ExcelApp.Wait(50);
}
**The Key is to load add-ins before you open Excel Workbook.
3)Open Excel Workbook, which will trigger calculations
4)Set Calculation Mode to manual so that any changes in Interop do not trigger lengthy recalc
ExcelApp.Calculation = Microsoft.Office.Interop.Excel.XlCalculation.xlCalculationManual;
5)Perform any manipulations, and perform calc
ExcelApp.CalculateFull();
6)Dispose of Excel Objects appropriately
Hope this helps someone with a similar issue.. Ended up being a simple fix for a simple problem. Just remember to load add-ins before opening the workbook. Otherwise, opening an excel workbook with UDF's dependent on AddIn will fail.
Our application has distribution functionality. It takes several Excel 2007 spreadsheets, copies them into a single sheet, then emails them to the users. The problem is that images and charts are not copying over. I have tried everything I can find on here and various other sources, but sadly nothing seems to work. Our application is written using VSTO, and I have also tried OpenXML, but nothing seems to work. In fact trying to copy over in OpenXML corrupted the files so badly that Excel could not read or recover them. Here's the code we use (note: we have ExcelWrapper simply calls the same functions on the Excel App but hides all the optional items).
private void CreateWorkbook(string filePath, string batchReportsPath)
{
//place the xlsx file into a workbook.
//call getfilesnames
Excel.Workbook bookToCopy;
Excel.Workbook newWorkbook;
Excel.Worksheet tempSheet = new Excel.Worksheet();
newWorkbook = ExcelWrapper.WorkbooksAdd(ExcelApp.Workbooks);
if (File.Exists(filePath))
File.Delete(filePath);
ExcelWrapper.WorkbookSaveAs(newWorkbook, filePath);
List<string> filePaths = new List<string>(Directory.GetFiles(batchReportsPath));
filePaths.ForEach(delegate(string reportPath)
{
string reportPathAndName = reportPath;
bookToCopy = ExcelWrapper.WorkbooksOpen(ExcelApp.Workbooks, reportPathAndName);
int nextSheetNumber = newWorkbook.Sheets.Count;
((Excel.Worksheet)sheetToSend.Sheets[1]).Copy(Type.Missing, newWorkbook.Sheets[nextSheetNumber]);
ExcelWrapper.WorkbookClose(bookToCopy);
});
newWorkbook.Save();
ExcelWrapper.WorkbookClose(newWorkbook);
bookToCopy= null;
tempSheet = null;
newWorkbook = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
I have tried every promising option and searched both the VSTO and OpenXML object models and I am stumped. Please stackoverflow community, you're my only hope.
UPDATE: Here's the answer folks:
//Copy all the images, Charts, shapes
foreach (Excel.Shape o in copySheet.Shapes)
{
if (o.Type == Microsoft.Office.Core.MsoShapeType.msoPicture)
o.CopyPicture();
else
o.Copy();
newWorkbook.Save();
}
You need to do the save after each copy to get the Paste to finalize. Thanks for your input.
You'll need to check the WORKSHEET object of the worksheet you're looking to copy, then run through all the "*Objects" properties, and, for each of those collections, write code to manually copy all the elements in that collection to the new sheet.
For example, you've got:
ChartObjects
ListObjects
OleObjects
Shapes (Which might get copied along with the sheet, I'm not sure).
Perhaps you can get there by a copy/paste? Here's an example that copies cell data, charts and images: MSDN
Good luck!
I don't have an answer but I can give you some pointers. First, this is not an openxml question, it's a vsto question. Second, images and charts are attached to a spreadsheet, they are not content in it (a subtle distinction). There is probably a property of all charts and one of all images attached to the sheet. Copy that across.
(I use the IExtensibility interface instead of VSTO so I know what's in the underlying API, but not the property names - sorry.)
This is how I do it -- moves the entire worksheet from source workbook to a brand new workbook:
public static void MoveWorksheet()
{
public static Excel.Application oXL;
public static Excel._Worksheet ws;
if (File.Exists(Global.Variables.GlobalVariables.recap))
{
//workbookToOpen is location and name
oXL.Workbooks.Open(workbookToOpen, Missing.Value, true);
object ws = null;
try
{
ws = wb.Sheets["Sheet 1"];
if (ws != null)
{
((Worksheet)ws).UsedRange.Interior.ColorIndex = Constants.xlNone;
((Worksheet)ws).Copy();
oXL.ActiveWorkbook.SaveAs("SaveAsFileName" + ".xlsx");
oXL.ActiveWorkbook.Close(true);
}
}
catch {}
}
}