Unable to get the image out from Clipboard while using excel COM object Range.CopyPicture method
On my local machine everything is working fine but on server I am facing this issue.
Local machine I have office 2016 installed and at server we have Excel-2013 installed.
After deployment when i run the web application then it is throwing exception (object ref not to set instance).
But when I go to server and manually doing ctrl+v (paste) in any word doc directly then it pasting the image there, But from the code it is not able to read the image.
Code-
System.Threading.Thread.Sleep(2000);
xlApp.CutCopyMode = XlCutCopyMode.xlCopy;
Range range = xlWorkSheet.get_Range(startRange, endRange);
System.Windows.Forms.Clipboard.Clear();
range.CopyPicture(XlPictureAppearance.xlScreen, XlCopyPictureFormat.xlBitmap);
//Here getting null in imgWS1
Image imgWS1 = System.Windows.Forms.Clipboard.GetImage();
string fileName = Path.Combine(reportPath, imageName);
//Getting exception- object ref not to set an instance..
imgWS1.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);
Things I have tried
1.) When I am adding the test image in clipboard through
Forms.Clipboard.SetImage(imgtest);
then
Clipboard.GetImage()
is working fine. So here I think some problem is with the CopyPicture method.
2.) I also tried using DataObject but had no luck.
System.Windows.Forms.DataObject o = Forms.Clipboard.GetDataObject() as DataObject;
System.Windows.Forms.Clipboard.SetImage(GetClipboardImage(o));
I am trying to replicate the following method in C# Word interop (NetOffice)
Selection.PasteSpecial Link:=True, DataType:=wdPasteMetafilePicture, _
Placement:=wdInLine, DisplayAsIcon:=False
I am saying "replicate" since I don't want to actually use the PasteSpecial method, since I need to update many fields of type link in one go, and don't like to communicate with excel by relying on copy and paste.
I already have the following code that does mostly what I want but has some issues that I am not able to overcome.
Selection.InlineShapes.AddOLEObject ("Excel.Sheet.12", fileName + $"!{label}", true)
This method fails with an error message
Word is unable to create a link to the object you specified. Please insert the object directly into your file without creating a link
I am only getting this error message, if and only if I use a network drive or unc path with a label.
eg.
T:\TestFile.xlsx!Sheet1!TestRange
T:\TestFile.xlsx!Sheet1!R1C1:R2C2
//notice that T is a network drive and not a local drive
\\TestShare\TestFile.xlsx!Sheet1!TestRange
\\TestShare\TestFile.xlsx!Sheet1!R1C1:R2C2
If I dont use the label
eg.
T:\TestFile.xlsx
T:\TestFile.xlsx
//notice that T is a network drive and not a local drive
\\TestShare\TestFile.xlsx
\\TestShare\TestFile.xlsx
or a local drive eg. C: (with or without label)
C:\TestFile.xlsx
C:\TestFile.xlsx
C:\TestFile.xlsx!Sheet1!TestRange
C:\TestFile.xlsx!Sheet1!R1C1:R2C2
everything works fine.
So, only the combination network drive or unc + a label produces that error message.
I thought, okay no problem I already know how to change the path and label of the produced field and wrote the following code to include the label in a second step.
var inlineShape = Selection.InlineShapes.AddOLEObject("Excel.Sheet.12", fileName, true);
field = inlineShape.Field;
if (!string.IsNullOrEmpty(label))
{
var fieldCode = field.Code.Text;
//replace the produced empty label "" with the label I desire eg. "Sheet1!R1C1:R2C2"
fieldCode = fieldCode.Replace(" \"\" ", $" \"{label}\"");
//write the updated fieldcode back to the field
field.Code.Text = fieldCode;
//update the field
field.Update();
}
BUT this solution, changes the underlying format of the image from WMF (Windows Meta File) to EMF (Enhanced Meta File), but I need it to be stored in the WMF format, since Office 2010 doesn't render EMF correctly.
To get the underlying file, I rename my docx to zip and extract it to look into the word\media directory.
PasteSpecial doesn't throw that error, and produces that desired WMF format, but I can't seem to 100% replicate the routine that happens while PasteSpecial.
As I side note, I don't want to have png or bmp either, since those are generated with a blur around the font, so the best result delivers wmf for Office 2010.
For Office 2013 and higher, I am using the following code to get emf right from the start and don't face issue about network drive/unc + label error.
var field = range.Fields.Add(range, WdEnums.WdFieldType.wdFieldLink);
field.LinkFormat.SourceFullName = fileName + $"!{label}";
Edit:
Here a screenshot of the main problem that I try to solve, as I reminder this is how it looks in Office 2010 in Office 2013 and higher both look decent:
For both I am using paste special
https://support.office.com/en-us/article/paste-special-e03db6c7-8295-4529-957d-16ac8a778719
The first table in the word document produces an EMF File, while using the paste special format "Picture (Enhanced Metafile)", macro recording produces this code:
Selection.PasteSpecial Link:=False, DataType:=wdPasteEnhancedMetafile, _
Placement:=wdInLine, DisplayAsIcon:=False
The second table in the word document produces an WMF File, while using the paste special format "Picture (Windows Metafile)", macro recording produces this code:
Selection.PasteSpecial Link:=True, DataType:=wdPasteMetafilePicture, _
Placement:=wdInLine, DisplayAsIcon:=False
On the right side, you can see the excel file I did copy to paste in the left word document.
As you can clearly see, Office 2010 renders/draws the WMF file (the second table in the word document) alot nicer, so that would be my desired output, but as I mentioned I have struggle replicating the PasteSpecial and keeping the WMF file that renders/draws better.
Edit2:
I recorded my screen to show the error, pls excuse that my environment is in german.
https://imgur.com/a/50wcBGQ
The code I have used is this:
Sub NetworkAndLabel()
'this will fail
Dim fileName As String
Dim label As String
'T is a mapped drive for the share \\NAS1\Ablage
fileName = "T:\rfa\TestFile.xlsx"
'label is in german, in english it would be Sheet1!R1C1:R1C2
label = "Tabelle1!Z1S1:Z2S2"
Selection.InlineShapes.AddOLEObject "Excel.Sheet.12", fileName & "!" & label, True
End Sub
Sub Network()
'this will run successfully
Dim fileName As String
Dim label As String
'T is a mapped drive for the share \\NAS1\Ablage
fileName = "T:\rfa\TestFile.xlsx"
Selection.InlineShapes.AddOLEObject "Excel.Sheet.12", fileName, True
End Sub
Sub LocalAndLabel()
'this will run successfully
Dim fileName As String
Dim label As String
'E is a local drive (second partition, not a seperate drive)
'up till now, I only tested with C and wanted to see if a simple second partition works or not
fileName = "E:\rfa\TestFile.xlsx"
'label is in german, in english it would be Sheet1!R1C1:R1C2
label = "Tabelle1!Z1S1:Z2S2"
Selection.InlineShapes.AddOLEObject "Excel.Sheet.12", fileName & "!" & label, True
End Sub
Sub Local_()
'this will run successfully
Dim fileName As String
Dim label As String
'E is a local drive (second partition, not a seperate drive)
'up till now, I only tested with C and wanted to see if a simple second partition works or not
fileName = "E:\rfa\TestFile.xlsx"
Selection.InlineShapes.AddOLEObject "Excel.Sheet.12", fileName, True
End Sub
I wrote "UsedRange" in C5 of the excel file, to show the differnt output in word if a label is or isn't in use, if I don't specify the label, it uses the UsedRange of the first sheet.
FYI, I have written the code in simple vba to demonstrate that it has nothing to do with C# and can nativly reproduced without anything special. In the end the solution, if there is one, will be included in my C# application.
Edit3:
Just to clarify, if someone knows a way to make Office 2010 render/draw the EMF file better, that would also be a valid solution, for my problem, because at the end of the day, I simply want to have a decent looking linked excel file in my word document. I don't really care if EMF or WMF, I am just trying to work with WMF because it looks better, and have the issues that I described, telling Office 2010 to stay on the WMF format.
Because the only way, I know of, to overcome the error message, is by manipulating the Field, as shown above, but once I call field.Update() the WMF file gets replaced with EMF, and I end up with the bad looking render/draw of the EMF file.
Edit4:
As requested, I had a look into the internal structure of the document and had a look at the differences. The actual difference is basically 0, only had stuff that should be irrelevent, like revisionnumber, characters count and stuff like this.
But I noticed the following, even though this code
Selection.PasteSpecial Link:=True, DataType:=wdPasteMetafilePicture, _
Placement:=wdInLine, DisplayAsIcon:=False
generates a WMF file, which I can see either by looking into the word\media folder and seeing there the produced image1.wmf, or by looking at the file word\_rels\document.xml.rels which has the following entry
<Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.wmf"/>
The actual XML for the OLE Object has the following in word\document.xml
<o:OLEObject Type="Link" ProgID="Excel.Sheet.12" ShapeID="_x0000_i1025" DrawAspect="Content" r:id="rId8" UpdateMode="Always"><o:LinkType>EnhancedMetaFile</o:LinkType><o:LockedField>false</o:LockedField></o:OLEObject>
As you can see the LinkType is defined as EnhancedMetaFile (EMF).
Since the OLE Object is defined like this, I tried if a simple call to field.Update would trigger the change from WMF to EMF, and sadly thats the case, so even without me modifing the field's code, a simple call like this
Selection.InlineShapes(1).Field.Update
will already trigger the conversion from WMF to EMF since the OLE Object is just simply defined like this, the conversion can also be achieved without any custom code, just select the inline shape and hit F9 or right click and select Update Links (translated from my german environment)
Notice that for what ever reason, this code
Selection.InlineShapes.AddOLEObject("Excel.Sheet.12", "E:\rfa\TestFile.xlsx", True).Field.Update
Will not trigger the conversion, but if you save the word document and open it again, than you can call field.Update to trigger the conversion.
So, to have the conversion in one go, without the necessity of saving/closing the word document, you need to atleast call this
With Selection.InlineShapes.AddOLEObject("Excel.Sheet.12", "E:\rfa\TestFile.xlsx!Tabelle1!Z1S1:Z2S2", True).Field
.Code.Text = Replace(.Code.Text, "Z1S1:Z2S2", "Z2S2:Z3S3")
.Update
End With
I have a VSTO addin that receives an excel chart (in byte array) over a network from a server. I would like to paste this chart onto a powerpoint slide programmatically without having to save the chart to disk first.
However, when I look through all the .Add* methods exposed by Microsoft.Office.Interop.PowerPoint.Shapes, they all seem to require a file path in string . In other words, I would have to convert the binary data to a file in a supported format on the system and get its path before I can use those functions.
Are there any way to directly use this binary data and paste it on the powerpoint slide without having to save it as a file on the system first?
using Microsoft.Office.Interop.Excel;
void CreateChart()
{
ChartData gChartData;
Workbook gWorkBook;
Worksheet gWorkSheet;
// Create the chart and set a reference to the chart data.
var myChart = ActivePresentation.Slides[1].Shapes.AddChart() as Microsoft.Office.Interop.PowerPoint.Chart;
gChartData = myChart.ChartData;
// Set the Workbook and Worksheet references.
gWorkBook = gChartData.Workbook;
gWorkSheet = gWorkBook.Worksheets[1];
// Add the data to the workbook.
gWorkSheet.ListObjects["Table1"].Resize(gWorkSheet.Range["A1:B5"]);
gWorkSheet.Range["Table1[[#Headers],[Series 1]]"].Value = "Items";
gWorkSheet.Range["a2"].Value = "Coffee";
gWorkSheet.Range["a3"].Value = "Soda";
gWorkSheet.Range["a4"].Value = "Tea";
gWorkSheet.Range["a5"].Value = "Water";
gWorkSheet.Range["b2"].Value = "1000";
gWorkSheet.Range["b3"].Value = "2500";
gWorkSheet.Range["b4"].Value = "4000";
gWorkSheet.Range["b5"].Value = "3000";
//ToDo: Style
}
fill the data as you like (you may fill it with a for loop and fill the $"a{i}" column with the number of the entry (i))
See this Article for further Information
UPDATE 21.04.2021:
This isn't what the OP asked for.
In his comment he said:
Even if it is in a text format, I would still have trouble pasting it directly onto the slide because all the methods to embed onto PowerPoint slide exposed by Microsoft.Office.Interop.PowerPoint.Shapes only accept the path of the file in string-not the file itself-as its parameter to access the required file... if I'm not mistaken.
This is not correct. I pointed out a way to create charts programmatically without having to create an excel sheet an save it somewhere.
If you would have gotten the data in a defined format (e.g. json) you could use my code to generate the chart.
But now what you seem to be waiting for:
If you click copy with and excel-chart selected, then open your PowerPoint presentation, set your selection where your want the chart to be and paste the chart, it will be inserted there no problem.
But how can I sent this Data and then paste it?
The Excel-Chart has got a method to copy itself:
https://learn.microsoft.com/de-de/office/vba/api/excel.chart(object)
Convert the data in your clipboard to binary data
Send the data
Convert the data back and store it in your clipboard
Use the TextRange of your Powerpoint-Selection to paste the data
https://learn.microsoft.com/de-de/office/vba/api/powerpoint.textrange
I'm working with a console application / WebJob which utilises the EPPlus library for working with Excel -files (.xlsx). My application basically opens a set of Workbooks and merges them together as one file.
The application is running fine locally, but not in Azure. According to the StackTrace the error happens inside the EEPlus library when trying to save an image (I assume this is done to move images from one Workbook to another).
Unhandled Exception: System.ApplicationException: A generic error occurred in GDI+.
---> System.Runtime.InteropServices.ExternalException: A generic error occurred in GDI+.
at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams)
at System.Drawing.Image.Save(Stream stream, ImageFormat format)
at OfficeOpenXml.Drawing.ExcelPicture..ctor(ExcelDrawings drawings, XmlNode node)
at OfficeOpenXml.Drawing.ExcelDrawing.GetDrawing(ExcelDrawings drawings, XmlNode node)
at OfficeOpenXml.Drawing.ExcelDrawings.AddDrawings()
at OfficeOpenXml.Drawing.ExcelDrawings..ctor(ExcelPackage xlPackage, ExcelWorksheet sheet)
at OfficeOpenXml.ExcelWorksheets.Add(String Name, ExcelWorksheet Copy)
The original code, which produces the error open calling ConvertTo.
Part = drawings.Part.Package.GetPart(UriPic);
FileInfo f = new FileInfo(UriPic.OriginalString);
ContentType = GetContentType(f.Extension);
_image = Image.FromStream(Part.GetStream());
ImageConverter ic=new ImageConverter();
var iby=(byte[])ic.ConvertTo(_image, typeof(byte[]));
var ii = _drawings._package.LoadImage(iby, UriPic, Part);
ImageHash = ii.Hash;
After reading several questions on the matter I've tried to modify it using a manual conversion and saving to a MemoryStream. However I'm still getting the error.
Part = drawings.Part.Package.GetPart(UriPic);
FileInfo f = new FileInfo(UriPic.OriginalString);
ContentType = GetContentType(f.Extension);
_image = Image.FromStream(Part.GetStream());
byte[] iby;
using (MemoryStream ms = new MemoryStream())
{
_image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
iby = ms.ToArray();
}
var ii = _drawings._package.LoadImage(iby, UriPic, Part);
ImageHash = ii.Hash;
I'm really stuck on what to try next. The exception doesn't produce that much to go on, and I feel I've already tried all the suggestions out there: Checking folder and file permissions (my app uses the temp folder, which I believe is safe), avoid reusing streams and so on.
Please let me know if you need any additional information and I'll gladly put it up.
This is not possible using the current versions of EPPlus, due to a dependency on the System.Drawing libraries internally.
See here for an official thread on the topic:
https://github.com/JanKallman/EPPlus/issues/427
And here for some other interesting reading: https://photosauce.net/blog/post/5-reasons-you-should-stop-using-systemdrawing-from-aspnet
It seems like the only way through this will be to identify the functions in EPP that depend on System.Drawing and work around them. Or adjust EPP to use a different library for graphics.
This may be a good start for you to begin determining the parts you can avoid: https://github.com/JanKallman/EPPlus/search?q=%22using+system.drawing%3B%22&unscoped_q=%22using+system.drawing%3B%22
I typically use EPPlus in this way:
Grab a template.xlsx I have previously formatted
Add data to it (using C# and EPPlus)
Return the new xlsx to the user
I recently had the same error:
A generic error occurred in GDI+
How it was solved:
My template.xlsx was using Excel's Camera Tool: that is, there was a snapshot in one of the sheets that was being pasted in another sheet.
This works fine locally (in my computer) but not in Azure... I don't know why.
But if I remove the snapshot it works
I am a beginner with C# and windows programming. I have written a service which converts a powerpoint file to individual slide images using the slide.export method that the microsoft.office.interop.powerpoint libraries provide. I am able to get all the slides but some of them seem to be broken and i see a "Image cannot be displayed. Memory exceeded" or the likes of it. I thought it was insufficient memory and then just tried it with a ppt having one slide(whose image was broken) and to my dismay I found the single image to be broken as well.
Am I using export wrongly or should I pass different arguments than I am passing already? I will paste the code below.
Microsoft.Office.Interop.PowerPoint.Application appPpt
= new Microsoft.Office.Interop.PowerPoint.Application();
Microsoft.Office.Interop.PowerPoint.Presentation objActivePresentation
= appPpt.Presentations.Open(strFilePath,
Microsoft.Office.Core.MsoTriState.msoCTrue,
Microsoft.Office.Core.MsoTriState.msoTriStateMixed,
Microsoft.Office.Core.MsoTriState.msoFalse);
foreach (Microsoft.Office.Interop.PowerPoint.Slide objSlide
in objActivePresentation.Slides)
{
//Names are generated based on timestamp.
objSlide.Export(slideName, "PNG", 960, 720);
objSlide.Export(slideNameMedium, "JPG", 307, 231);
objSlide.Export(slideNametn, "JPG", 150, 113);
}
I'm in need of help here. Thanks in advance.
try SaveAs.
Hope it helps.....