locked
Changing/adding image to existing InLineShape via COM/interop RRS feed

  • Question

  • I'm re-writing an old VFP DLL in c# which pulls information from a SQL db and updates bookmarks and existing inLineShapes in a Word Template (essentially adding signature and initials images).  I've got it pretty much done except where it is pulling the images from the db and storing them in the inLineShapes.  The old application used this code (shortened for clarity):

    Note: strFileName is the name of the blob image written to a disk file (I can confirm the image file is ok, because I can get it to load as a new InlIneShape)

    With objImage
        If .Type = wdInlineShapeOLEControlObject Then
            If .OLEFormat.ClassType = "Forms.image.1" Then
                If .OLEFormat.object.Name = "the correctname" Then
                    .OLEFormat.object.Picture = LoadPicture(strFileName)
                End If
            End If
        End If
    End With

    The issue is that the OLEFormat.Object now does not expose any properties to modify/compare and converting it to a dynamic object doesn't help.  I've been able to work-around the .name issue by going to the .AlternativeText property, but have hit a wall at the actual image.  I can add the image as a new InLineShape to the InLineShapes collection, but this will cause many downstream issues when the document is re-edited, etc.  So far my code is (again, shortened for clarity):

    foreach (Word.InlineShape objImage in marrImages)
        {
            if (objImage.Type == Word.WdInlineShapeType.wdInlineShapeOLEControlObject)
                {
                if (objImage.OLEFormat.ClassType == "Forms.Image.1")
                    {
                        if (objImage.AlternativeText == "the correctname")
                            {
                                dynamic oObj = objImage.OLEFormat.Object;
                                oObj.Picture = System.Drawing.Image.FromFile(strFileName);
                            }
                        }
                    }
                }
            }
        }

    This code results in a "System.ArgumentException: 'Could not convert argument 0 for call to Picture.'" exception, and other variations will result in similar or null reference exceptions.  I've scoured on-line resources for a solution and have so far drawn a blank

    Can anyone tell me how to accomplish the task of adding/updating an image in an existing InLineShape object?

    Thanks

    Rob

    Thursday, September 6, 2018 7:31 PM

Answers

  • Interesting how when asking a question and putting the info down in front of you, it raises a new clue.  Doing some research into the IPictureDisp that I only just noticed this morning, I found this article (https://blogs.msdn.microsoft.com/andreww/2007/07/30/converting-between-ipicturedisp-and-system-drawing-image/), so I added the AxHost subclass:
        internal class AxHostConverter : AxHost
    
        {
    
            private AxHostConverter() : base("") { }
    
    
    
            static public stdole.IPictureDisp ImageToPictureDisp(System.Drawing.Image image)
    
            {
    
                return (stdole.IPictureDisp)GetIPictureDispFromPicture(image);
    
            }
    
    
    
            static public System.Drawing.Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    
            {
    
                return GetPictureFromIPicture(pictureDisp);
    
            }
    
        }
    
    ... and then changed the addition of the image to the InLineShape to:
    // put the shape into the InlIneShape object that matches the queried field
    dynamic oObj = objImage.OLEFormat.Object;
    using (System.Drawing.Image oImage = System.Drawing.Image.FromFile(strFileName))
    {
    oObj.Picture = AxHostConverter.ImageToPictureDisp(oImage);
    }
    and it works!



    • Marked as answer by rrgrant Friday, September 7, 2018 6:29 PM
    • Edited by rrgrant Friday, September 7, 2018 9:03 PM
    Friday, September 7, 2018 6:28 PM

All replies

  • Hi Rob,

    Thanks for visiting Word IT pro forum. Then here we mainly focus on general issues about Word user interface. Since your query is related to developing issues involve Word, I'll move your thread to the dedicated MSDN forum for Word for better response:

    https://social.msdn.microsoft.com/Forums/office/en-US/home?forum=worddev

    The reason why we recommend posting appropriately is you will get the most qualified pool of respondents, and other partners who read the forums regularly can either share their knowledge or learn from your interaction with us. Thank you for your understanding.

    Regards,

    Yuki Sun


    Please remember to mark the replies as answers if they helped. If you have feedback for TechNet Subscriber Support, contact tnsf@microsoft.com.

    Click here to learn more. Visit the dedicated forum to share, explore and talk to experts about Microsoft Teams.

    Friday, September 7, 2018 1:41 AM
  • Hi rrgrant

    Please provide more detail code which can be open source from your side, so we can help to reproduce and research your issue.


    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread.

    Friday, September 7, 2018 12:45 PM
  • Thanks for moving it, Yuki.  I simply missed the Word Developers forum in the long list.

    Rob

    Friday, September 7, 2018 3:06 PM
  • Here is digested code for the image processing.  There is a lot more for bookmarks and the actual db queries that would only clutter things up. As mentioned, it is only the updating the image into the InLineShape that is posing the issue.

    (Edit) I should mention that the VFP version of the DLL which uses the .OLEFormat.object.Picture = LoadPicture(strFileName) call still works (the DLL is being re-written to provide 64bit support).  We located the LoadPicture function in the VFP application in the Microsoft Office 12.0 Object Library as a member of the VB.Global class (as IPictureDisp), but I cannot seem to locate this function in the current Office 16.0 Object Library, hence the change to trying to use System.Drawing.Image.FromFile(strFileName).I can find reference to IPictureDisp (https://msdn.microsoft.com/en-us/library/windows/desktop/ms680762%28v=vs.85%29.aspx), so maybe it's me just not adding the correct reference?

    private List<Word.InlineShape> arrImages = new List<Word.InlineShape>();

    private Boolean GetImageArray(Word.Document wordDocument, List<Word.InlineShape> arrImages) { // images that are out in the open foreach (Word.InlineShape objImage in wordDocument.InlineShapes) { if (objImage.Type == Word.WdInlineShapeType.wdInlineShapeOLEControlObject) { if (objImage.OLEFormat.ClassType == "Forms.Image.1") { arrImages.Add(objImage); } } } // loop thru each of the sections to get to the various headers and footers foreach (Word.Section objSection in wordDocument.Sections) { // loop the headers collections which are off the sections. foreach (Word.HeaderFooter objHeader in objSection.Headers) { objHeader.Range.Select(); foreach (Word.InlineShape objImage in wordActiveDocument.Application.Selection.InlineShapes) { if (objImage.Type == Word.WdInlineShapeType.wdInlineShapeOLEControlObject) { if (objImage.OLEFormat.ClassType == "Forms.Image.1") { arrImages.Add(objImage); } } } } // loop the footers collection in each section foreach (Word.HeaderFooter objFooter in objSection.Footers) { objFooter.Range.Select(); foreach (Word.InlineShape objImage in wordActiveDocument.Application.Selection.InlineShapes) { if (objImage.Type == Word.WdInlineShapeType.wdInlineShapeOLEControlObject) { if (objImage.OLEFormat.ClassType == "Forms.Image.1") { arrImages.Add(objImage); } } } } } return true; } private Boolean ProcessDocumentElements(Word.Document wordDocument, ADODB.Recordset rsResults) { rsResults.MoveFirst(); foreach (ADODB.Field objField in rsResults.Fields) { ProcessImage(objField, wordDocument); } return true; } private Boolean ProcessImage(ADODB.Field objField, Word.Document objDocument) { int intCounter = arrImages.Count; int intPositionFinal = 0; Boolean blnArrayIsEmpty = false; string strFileName = ""; ADODB.Stream stm = new ADODB.Stream(); ADODB.Stream stm1 = new ADODB.Stream(); byte[] strPicture; if (blnArrayIsEmpty == false) { // this loop can populate multiple images from the same database field foreach (Word.InlineShape objImage in arrImages) { if (objImage.Type == Word.WdInlineShapeType.wdInlineShapeOLEControlObject) { if (objImage.OLEFormat.ClassType == "Forms.Image.1") { if (objImage.AlternativeText == objField.Name) { // BLOB from the db if (objField.Type == ADODB.DataTypeEnum.adLongVarBinary) { // save it to a disk on file in the temp directory strFileName = System.Environment.GetEnvironmentVariable("TEMP") + "\\test1234.bmp"; strPicture = objField.Value; // signatures saved by our VFP application were stored as general OLE objects, i.e. they have server information // as a header ( Like PBrush). Bitmap itself will start with chars "BM". This has only been tested for bitmaps for (int intPosition = 0; intPosition <= strPicture.GetUpperBound(0); intPosition++) { char test = System.Convert.ToChar(strPicture[intPosition]); char test1 = System.Convert.ToChar(strPicture[intPosition+1]); if ((test == 'B') && (test1 =='M')) { intPositionFinal = intPosition; break; } intPositionFinal = intPosition; } if (intPositionFinal >= strPicture.GetUpperBound(0)) { intPositionFinal = 0; } stm.Type = ADODB.StreamTypeEnum.adTypeBinary; stm.Open(); stm.Write(objField.Value); stm.Position = intPositionFinal; stm1.Type = ADODB.StreamTypeEnum.adTypeBinary; stm1.Open(); stm.CopyTo(stm1, -1); stm1.SaveToFile(strFileName, ADODB.SaveOptionsEnum.adSaveCreateOverWrite); stm.Close(); stm1.Close(); if (objField.Value != null) { // put the shape into the InlIneShape object that matches the queried field dynamic oObj = objImage.OLEFormat.Object; oObj.Picture = System.Drawing.Image.FromFile(strFileName); // the following works to add a new InLineShape - we don't want to do that, but it serves to test the image file //Object linkToFile = false; //Object saveWithDocument = true; //Microsoft.Office.Interop.Word.Range rng = objImage.Range; //Object range = rng; //Word.InlineShape newObjImage = objDocument.InlineShapes.AddPicture(strFileName, ref linkToFile, ref saveWithDocument, ref range); //newObjImage.AlternativeText = objImage.AlternativeText; //newObjImage.Height = objImage.Height; //newObjImage.Width = objImage.Width; System.IO.File.Delete(strFileName); break; } } } } } } } return true; }



    • Edited by rrgrant Friday, September 7, 2018 5:32 PM
    Friday, September 7, 2018 4:17 PM
  • Interesting how when asking a question and putting the info down in front of you, it raises a new clue.  Doing some research into the IPictureDisp that I only just noticed this morning, I found this article (https://blogs.msdn.microsoft.com/andreww/2007/07/30/converting-between-ipicturedisp-and-system-drawing-image/), so I added the AxHost subclass:
        internal class AxHostConverter : AxHost
    
        {
    
            private AxHostConverter() : base("") { }
    
    
    
            static public stdole.IPictureDisp ImageToPictureDisp(System.Drawing.Image image)
    
            {
    
                return (stdole.IPictureDisp)GetIPictureDispFromPicture(image);
    
            }
    
    
    
            static public System.Drawing.Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    
            {
    
                return GetPictureFromIPicture(pictureDisp);
    
            }
    
        }
    
    ... and then changed the addition of the image to the InLineShape to:
    // put the shape into the InlIneShape object that matches the queried field
    dynamic oObj = objImage.OLEFormat.Object;
    using (System.Drawing.Image oImage = System.Drawing.Image.FromFile(strFileName))
    {
    oObj.Picture = AxHostConverter.ImageToPictureDisp(oImage);
    }
    and it works!



    • Marked as answer by rrgrant Friday, September 7, 2018 6:29 PM
    • Edited by rrgrant Friday, September 7, 2018 9:03 PM
    Friday, September 7, 2018 6:28 PM