Open XML – Setting multiple Picture Content Controls by Tag name, without going crazy

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.

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.

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.

// 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.

// 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;

14 thoughts on “Open XML – Setting multiple Picture Content Controls by Tag name, without going crazy

  1. Anoniem

    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?

    Reply
  2. Eelco Mulder

    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 😉

    Reply
  3. Rajesh Jenny

    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

    Reply
  4. Rafael

    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!

    Reply
  5. Manfred

    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

    Reply
  6. Arjun

    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?

    Reply

Leave a Reply to Eelco Mulder Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.