Recent Posts

Tags

News

  • Sentient

    My services as a speaker, consultant, trainer and developer are available through Sentient.

    Sentient is a leading provider of software development, consultancy and training services on the Microsoft platform.

    Please feel free to contact me if you have a question about something posted in my blog.

Community

Email Notifications

Links

Archives

Sentient thoughts about .NET

Jonathan Greensted's .NET weblog

Inserting Pictures using WordML

I've seen many, many requests on the Microsoft newsgroups asking how you can insert a picture into a Word document without saving it to the file system first.

This example application described in this blog illustrates both methods for inserting a picture firstly using the Word object model (InlineShapes.AddPicture) and secondly using Word's XML support (InsertXML).

The Word object model code is very straight forward:

private void buttonInsertNormal_Click(object sender, System.EventArgs e)

{

  object oMissing = Type.Missing;

  object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;

  _wordApp.ActiveDocument.Content.Collapse(ref oCollapseEnd);

 

  string filename = @"c:\temp\WordMLImage.bmp";

 

  try

  {

    pictureBox1.Image.Save(filename);

    _wordApp.ActiveWindow.Selection.Range.InlineShapes.AddPicture(filename,

      ref oMissing, ref oMissing, ref oMissing);

  }

  finally

  {

    File.Delete(filename);

  }

}

The only problem with this code is the requirement to write the image to the file system so Word can reload it.  There are many circumstances where you would prefer to avoid touching the file system or maybe your application does not have write permission.

Fortunately there is an alternative approach using Word's XML support.  This approach requires us to constructing a WordML document fragment representing the image to be inserted and then calling Word's InsertXML function to place the image in the document.

The code to do this is as follows:

private void buttonInsertWordML_Click(object sender, System.EventArgs e)

{

  object oMissing = Type.Missing;

  object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;

  _wordApp.ActiveDocument.Content.Collapse(ref oCollapseEnd);

 

  try

  {

    PictWriter pw = new PictWriter(pictureBox1.Image, "Example Image", "Example Image");

    _wordApp.ActiveWindow.Selection.Range.InsertXML(pw.ToString(), ref oMissing);   

  }

  catch (Exception ex)

  {

    // do something sensible here.

  }

}

You will notice the use of a custom class called Sentient.WordML.PictWriter which handles the task of converting the image into the WordML document fragment.

The complete class source is included in the zip download at the end of the article however the core elements are the following two methods. 

WriteDoc handles writing the WordML document wrapper.  InsertXML expects a complete WordML document including full namespace references and style definitions if you are using styles (we are not).

WritePict handles writing the actual image data into the XML stream as Base64.

///<summary>

/// Write the whole WordML document

///</summary>

///<param name="wtr">The XmlTextWriter to write to</param>

protected void WriteDoc(XmlTextWriter wtr)

{

  // start <xml> tag

  wtr.WriteStartDocument();

 

  // add processing instructions

  wtr.WriteProcessingInstruction("mso-application", "progid=\"Word.Document\"");

 

  // start <wordDocument> tag 

  wtr.WriteStartElement("w", "wordDocument", WordMLNS);

 

  // write namespaces 

  foreach(string prefix in _namespaces.AllKeys)

  {

    wtr.WriteAttributeString("xmlns", prefix, null, _namespaces[prefix]);

  }

 

  // start <body> tag 

  wtr.WriteStartElement("body", WordMLNS);

 

  // call WritePict to add our image to the Xml stream.

  WritePict(wtr);

 

  // end <body> tag 

  wtr.WriteEndElement();

 

  // end <wordDocument> tag 

  wtr.WriteEndElement();

 

  // end <xml> tag

  wtr.WriteEndDocument();

}

 

///<summary>

/// Write the Pict WordML element

///</summary>

///<param name="wtr">The XmlTextWriter to write to</param>

protected void WritePict(XmlTextWriter wtr)

{

  if(_data==null)

  {

    return;

  }

 

  // start <pict> tag

  wtr.WriteStartElement("pict", WordMLNS);

 

  // start <binData> tag

  wtr.WriteStartElement("binData", WordMLNS);

  wtr.WriteAttributeString("name", WordMLNS, string.Format("wordml://{0}{1}", _name, _extension));

 

  // write the image as Base64

  wtr.WriteBase64(_data, 0, _data.Length);

 

  // end <binData> tag

  wtr.WriteEndElement();

 

  // start <shape> tag which describes the shape containing the image

  wtr.WriteStartElement("shape", VMLNS);

  wtr.WriteAttributeString("id", "_x0000_" + _name);

  wtr.WriteAttributeString("style", string.Format("width:{0}px;height:{1}px", _width, _height));

 

  // start <imagedata> tag which links to the <binData> above.

  wtr.WriteStartElement("imagedata", VMLNS);

  wtr.WriteAttributeString("src", string.Format("wordml://{0}{1}", _name, _extension));

  wtr.WriteAttributeString("title", OfficeNS, _title);

 

  // end <imagedata> tag

  wtr.WriteEndElement();

 

  // end <shape> tag

  wtr.WriteEndElement();

 

  // end <pict> tag

  wtr.WriteEndElement();

}

As you can see the InsertXML approach requires a little more code however does achieve our objective of not touching the file system.

The only drawback I've found with the InsertXML approach is you dont get the image autosizing functionality which means if you want the image to be reduced or enlarged in size you'll have to do this yourself in code either by resizing the image before inserting it or using the Word object model to manipulate the InlineShape properties after it has been inserted.

A major bonus is that using InsertXML is considerably faster than writing the image to the file system and then calling InlineShape.Add so you get a considerable performance improvement especially if you are working with large images.

The source for this experimentation can be downloaded from the following page on the Sentient website:

      http://www.sentient.co.uk/WordMLInsertPicture.aspx

If you know of a better way to do this please add a comment to this blog.

Posted: Jan 11 2005, 05:39 PM by jonathangreensted | with 6 comment(s)
Filed under:

Comments

jonathangreensted said:

sorry but this is comment spam from a fellow MVP.
it was posted by an automated bot i wrote.
it uses AI to beat the CAPTCHA test.
i have written an article about it here.
http://www.brains-N-brawn.com/aiCAPTCHA
# January 30, 2005 6:25 PM

jonathangreensted said:

Nice one, Casey.

Your article was very interesting.

For those who didnt know CAPTCHA stands for "Completely Automated Public Turing test to tell Computers and Humans Apart"

Please don't do this again!!!

Jonathan
# February 8, 2005 11:36 AM

jonathangreensted said:

Nice. However, the overhead for using System.XML is very large, especially for the small amount of actual XML you are generating. You could do a lot better with a String Builder using a standard template and the Replace method. The resizing issue is because Word embeds the picture inside of a VML bounding box, and you (and the rest of the web community) need to figure out how that works. That is the BIG undocumented area of XML images in WordML.

Since you are automating Word, you could also use the Clipboard to get the image into Word quickly.

Try this code instead:

private void buttonInsertNormal_Click(object sender, System.EventArgs e)
{
object oCollapseEnd = Word.WdCollapseDirection.wdCollapseEnd;

_wordApp.ActiveDocument.Content.Collapse(ref oCollapseEnd);

// Copy the Image onto the clipboard
Clipboard.SetDataObject(pictureBox1.Image, true);

// Paste the image into Word
_wordApp.Selection.Paste();
}


# May 11, 2005 9:22 PM

TrackBack said:

^_~,pretty good!csharpsseeoo
# May 17, 2005 9:36 PM

TrackBack said:

Inserting Pictures using WordMLooeess
# July 17, 2005 2:27 AM

TrackBack said:

Inserting Pictures using WordMLooeess
# July 31, 2005 10:26 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)