For a project I was working on I had to generate some documents on the server. Not wanting to install Microsoft Word on the server because, well let’s not go there, I’ll have to use bad words; I decided to use Open XML. I started a little C# project to see how this stuff works. At first you frown a little, some cursing and yelling occur; maybe you shed some tears because some simple things turn out to be not that simple, like setting a Picture Content Control. Normally you just fire up Google and look for examples. I did that just that but sadly all examples did something but not just what I wanted: setting multiple Picture Content Controls by Tag name. I found this blog post of Erik White but that code changed all picture content controls when you changed one. That’s because initially a Picture Content Control has some resource Id that points to the same (‘blank’) image in the resources of your document. In another thread some guy Jinesh replied with some code that almost did what I wanted. I used that and added some of my magic to solve this should-be-simple-it’s-2012 problem.
Ok let’s start; first add some using statements and, to prevent some ambiguous (*sigh*) classes, add some aliases.
1 2 3 4 5 6 7 |
using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using A = DocumentFormat.OpenXml.Drawing; using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing; using PIC = DocumentFormat.OpenXml.Drawing.Pictures; |
In this example I’ll use a Bitmap object, this enables me to resize the placeholder using the image dimensions. First I’ll use the Tag name to select the element containing the Picture Content Control, which is a block element. Then I’ll get the Blip element which has a reference to the picture.
1 2 3 4 5 6 7 8 9 |
Bitmap image = new Bitmap(@"F:insert_me.jpg"); SdtElement controlBlock = _mainDocumentPart.Document.Body .Descendants() .Where (r => r.SdtProperties.GetFirstChild().Val == tagName ).SingleOrDefault(); // Find the Blip element of the content control. A.Blip blip = controlBlock.Descendants().FirstOrDefault(); |
The next step is to load the image into the document and assign the resource Id of that image to the Blip.Embed property.
1 2 3 4 5 6 7 8 9 10 |
// Add image and change embeded id. ImagePart imagePart = _mainDocumentPart .AddImagePart(ImagePartType.Jpeg); using (MemoryStream stream = new MemoryStream()) { image.Save(stream, ImageFormat.Jpeg); stream.Position = 0; imagePart.FeedData(stream); } blip.Embed = _mainDocumentPart.GetIdOfPart(imagePart); |
Yes, it’s that simple. You’ve probably seen a lot of different, not working, solutions on the web which had a lot more code. Now the code for the resizing, I suspect there should be an easier way, but for now this will do.
1 2 3 4 5 6 7 8 9 10 11 12 |
// set image dimensions in the document DW.Inline inline = controlBlock .Descendants().FirstOrDefault(); // 9525 = pixels to points inline.Extent.Cy = image.Size.Height * 9525; inline.Extent.Cx = image.Size.Width * 9525; PIC.Picture pic = inline .Descendants().FirstOrDefault(); pic.ShapeProperties.Transform2D.Extents.Cy = image.Size.Height * 9525; pic.ShapeProperties.Transform2D.Extents.Cx = image.Size.Width * 9525; |
Code works fine, but the previous pictures are still in the docx.
Is there a way to find out if those previous pictures are referenced somewhere else, and if not, to remove them?
The Blip elements has an Embed property, this holds the ID of the resource. You could use that ID and the GetPartById to get a reference to the image and then use the DeletePart method to remove it. But that's theory, I haven't tried it and don't have time to try it right now 😉
really nice post, saved allot of time n code ..thanks!
I modified to use an image inside a footer and always i end up with Image cannot be currently displayed.
If I place the control inside the body it works well.
Any help?
Thanks,
Rajesh
I should have found your blog before I got to my (similar) solution… Awesome post
My docx file does not have SDT blocks. Pity.
Is it possible to attach several images into the same content control? I need to do a replacement for an unknown number of images and I’m not sure what the best strategy would be, thanks!
I think I would try to dynamicaly add content controls, because its probably easier to reuse those if you want todo more stuff with those images.
You sir, are a hero, I’ve been breaking my head on this for the past 3 days!
You saved my week, thanks
Hi Eleco
Thank’s for your blog entry – but I have a problem with DocumentFormat.OpenXml 2.5
I find the SdtElement with type SdtBlock – but the element has no Blip
In the Word Template I added a content picture control and a I added a picture to the control.
Do you have a point I can looking for or test
Thanks for your sugestions
The SdtElement might have other child elements that are a picture?
Your solution is working fine but I am not able to center align image, It is always align to left. Is there any way that I can fix it?
Your solution is working fine
how to do the same with javascript?