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 want to use EPPlus to input data and calculate it.
Here is my code:
ExcelWorksheet sheet = ep.Workbook.Worksheets["input"];
sheet.Cells[1, 1].Value = 10;
ep.Workbook.Calculate();
string test = sheet.Cells[1, 5].Text;
ep.Save();
The string test is "#NAME?"
It seems that EPPlus did not load user define function.
When I open the saved file, the calculation will be done automatically.
What should I do to make the user defined function work?
(I'll use this feature later in the ASP.NET to call User define functions in exist excel file.I tried Interop, it can achieve what I want, but a lot slower.)
Thanks!
The formula calc engine in EPPlus cannot execute VBA functions in the workbook. It supports approx. 150 common built in Excel formulas and nothing more than that.
However, you can implement your VBA functions in .NET code. Each function should inherit the EPPlus class ExcelFunction and be registred to EPPlus via the Workbook.FormulaParserManager given the same function name as your VBA functions.
There are samples that illustrates this (create custom functions) in the EPPlus Samples project which can be downloaded from Codeplex.
For version 4.1, you can download the solution "EPPlus with samples" here:
https://epplus.codeplex.com/releases/view/625020
Then goto the "EPPlusSamples" project and check out SampleAddFormulaFunction.cs
You need to create a vba project in Epplus which is where you would define the function. Something like this:
var fileinfo = new FileInfo(#"c:\temp\UserDefinedFunctionTest.xlsm");
if (fileinfo.Exists)
fileinfo.Delete();
var sb = new StringBuilder();
sb.AppendLine("Function test(a)");
sb.AppendLine(" test = a * 3");
sb.AppendLine("End Function");
using (var package = new ExcelPackage(fileinfo))
{
var workbook = package.Workbook;
var worksheet = workbook.Worksheets.Add("Sheet1");
workbook.CreateVBAProject();
var mod1 = workbook.VbaProject.Modules.AddModule("Module1");
mod1.Code = sb.ToString();
worksheet.Cells["A1"].Value = 5;
worksheet.Cells["A2"].Formula = "=test(A1)";
package.Save();
}
Which will look like this:
You need to set formula in ExcelWorkSheet
sheet.Cells[5, 5].Formula = "=A1*3";
This is for first row only , if you want to do in Large number of rows then you have to use loop and inside loop use formula.
I'm trying to unlock a couple of cells using the code below. The cells are merged with each other and I don't want to disjoin them as I am working on a client's quote template and do not want to change his quote structure :) Anyway...here is the code:
Excel.Worksheet ws = wb.Worksheets[1];
ws.Range["F25:F42"].Locked = false; //Error here
I get an error saying:
We can't do that to a merged cell.
Is there a way of getting around this error so that I can unlock/lock the cells as I will?
I suggest doing this:
Excel.Worksheet ws = wb.Worksheets[1];
foreach (Excel.Range ra in ws.Range["F25:F42"]) {
ra.MergeArea.Locked = false;
}
I have a C# data processing application which uses EPPlus to write the final results into an excel sheet. The background color of the rows are changed based on what the data on that row signifies. Time was never an issue as I only dealt with files that were below <100MB before. However, as my requirements have changed and the files get larger, I have noticed that.. just coloring makes my application 60% slower. Removing coloring makes the application significantly faster. The snippet below is an example of the code which I use to color the data to make it visually distinguishing. I'm no expert at EPPlus but is there a way, this can be optimized to make my application faster? Or are there any better ways for me to make the rows visually distinct for the people who will end up looking at the data? Any help will be appreciated!
if (data[4] == "3")
{
// color the type 3 messages here
var fill1 = cell1.Style.Fill;
fill1.PatternType = ExcelFillStyle.Solid;
fill1.BackgroundColor.SetColor(Color.LightGray);
}
if (data[4] == "4")
{
var fill1 = cell1.Style.Fill;
fill1.PatternType = ExcelFillStyle.Solid;
fill1.BackgroundColor.SetColor(Color.BlanchedAlmond);
}
EDIT:
This is the code I use to copy the template and write the excel data into the new worksheet. p is an Excel Package which I convert to a byte Array before writing to the excel file.
Byte[] bin = p.GetAsByteArray();
File.Copy("C:\\Users\\mpas\\Desktop\\template.xlsx", "C:\\Users\\mpas\\Desktop\\result.xlsx");
using (FileStream fs = File.OpenWrite("C:\\Users\\mpas\\Desktop\\result.xlsx")) {
fs.Write(bin, 0, bin.Length);
}
Styling is much faster in EPPlus, and most Excel APIs, if you use named styles. Assign and use the style to cell in EPPlus like this ...
internal static string YourStyleName = "MyStyle";
ExcelNamedStyleXml yourStyle = excel.Workbook.Styles.CreateNamedStyle(YourStyleName);
yourStyle.Style.Font.Color.SetColor(Color.DarkRed);
yourStyle.Style.Fill.PatternType = ExcelFillStyle.Solid;
yourStyle.Style.Fill.BackgroundColor.SetColor(Color.LemonChiffon);
// ...
sheet.Cells[sourceRange].StyleName = YourStyleStyleName
Here is code to open an existing file.
FileInfo AddressList = new FileInfo("c:\test\test.xlsx");
// Open and read the XlSX file.
try
{
using (ExcelPackage package = new ExcelPackage(AddressList))
{
// Get the work book in the file
ExcelWorkbook workBook = package.Workbook;
if (workBook != null)
{
if (workBook.Worksheets.Count > 0)
{
// Get the first worksheet
//ExcelWorksheet Worksheet = workBook.Worksheets.First();
var worksheet = package.Workbook.Worksheets[1];
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