C# SDK: trying to write a program to insert an image into a cell

I don’t know many details about myself exactly, but I know my age, and that I won’t start to study “C sharp” and its bridge to the LibO API therefore (among other reasons related to my tendency to think differently), This may also have prevented me from reading the many program lines including the comments carefully enough. In any case, my desire to clearly understand what you eventually want to achieve with your code is unfulfilled.
In addition I would want to know what happens if you FIRST insert (add) the “empty shell” of a newly created graphic shape into(to) the DrawPage, then set the Anchor, and finally insert the image and set the Size.
See demo in Basic:
insertImage.ods (12.6 KB)
Sorry. Only 24 lines.

One of theese questions, where the answer is yes, but not useful at all.
.
This site does not see many people actually using the SDK. Developers use other sites, this is mostly users helping users.
.
As long as you need the API search for solutions in Basic or Python and translate from there.

Lupp: Thanks for your reply. C# for our purposes here is quite close to C++ and Java. If you can read either of those languages you can read and understand code in C#. My code doesn’t use any of the fancy stuff in C# that would make it hard to understand. It’s simple, linear, procedural code.

I do appreciate your response, especially for the link to the Basic program.

Wanderer: Thanks for your reply, and for the suggestion that I post my request on one of the developers’ sites. I will do so.

CLI Code generated by MRI based on Lupp’s macro.
The Size setting is missing.
And the GraphicURL assignment is missing.
I can’t test this code since I’m running Linux.

using System;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.container;
using unoidl.com.sun.star.drawing;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.sheet;
using unoidl.com.sun.star.table;
using unoidl.com.sun.star.uno;

public class Snippet {
public void snippet(XComponentContext xContext, object oInitialTarget)
{
	try
	{
		XSpreadsheetDocument xSpreadsheetDocument = (XSpreadsheetDocument) oInitialTarget;
		XSpreadsheets xSpreadsheets = xSpreadsheetDocument.getSheets();
		
		XIndexAccess xIndexAccess = (XIndexAccess) xSpreadsheets;
		XSpreadsheet xSpreadsheet = (XSpreadsheet) xIndexAccess.getByIndex(0).Value;
		XCellRange xCellRange = (XCellRange) xSpreadsheet;
		XCell xCell = xCellRange.getCellByPosition(3, 5);
		
		XDrawPageSupplier xDrawPageSupplier = (XDrawPageSupplier) xSpreadsheet;
		XDrawPage xDrawPage = xDrawPageSupplier.getDrawPage();
		
		XMultiServiceFactory xMultiServiceFactory = (XMultiServiceFactory) oInitialTarget;
		object oObj1 = xMultiServiceFactory.createInstance("com.sun.star.drawing.GraphicObjectShape");
		
		XPropertySet xPropSet = (XPropertySet)oObj1;
		
		object oObj2 = (XInterface) xPropSet.getPropertyValue("Anchor").Value;
		
		XShapes xShapes = (XShapes) xDrawPage;
		
		/ xShapes.add(null);
		xShapes.add(oObj1);
	}
	catch (IndexOutOfBoundsException e)
	{
		// getByIndex, getCellByPosition
		Console.WriteLine(e.Message);
	}
	catch (WrappedTargetException e)
	{
		// getByIndex, getPropertyValue
		Console.WriteLine(e.Message);
	}
	catch (Exception e)
	{
		// createInstance
		Console.WriteLine(e.Message);
	}
	catch (RuntimeException e)
	{
		// createInstance
		Console.WriteLine(e.Message);
	}
	catch (UnknownPropertyException e)
	{
		// getPropertyValue
		Console.WriteLine(e.Message);
	}
}
}

Thanks for the code, Villeroy,

Just a note to all: my mother passed away this past Tuesday and I am understandably quite busy with all the resulting tasks. I would be examining all your suggestions in detail if I had the time. But for now it will have to wait until late this week. Thanks for your understanding.

Yes. I saw. And on a certain level I could read and understand the code. Therefore I also could tell you (just did not explicitly enough, obviously) that I think you chose the wrong order of the statements adding the new graphic shape to the DrawPage and setting its properties including the image. “Add the empty shell first. Then assign everything.”
I even had been able to rectify the C# code regarding my suggestion, but not to test it. Therefore I preferred to give a working example in the correct order in Basic.

(In fact I simply dislike C, C++, C#, Java, and to some degree Python. All these languages mimic an “efficient style” while encouraging/requiring long programs. The need to get access to the elements of LibO API emphasizes that effect, imo. I always preferred languages nearer to teaching - like what Wirth and others developed for the purpose. I started with “Algol 60” in 1964. My first “lecture” I gave in 1968. And I also disliked FORTRAN at that time though I had to study and understand programs written in that slang. This, of course, doesn’t mean that I regard LibO Basic a seriouis programming language. I just use it because of the short and wide bridge to the API it offers.)

Hi, I strongly recommend first to test your code in Basic, and then translate it to CSharp. If you want to insert linked pics the approach from Lupp is fine. If you want to embed the pics into your document you have to use the BitmapTable as in your c# code. Things are quite tricky and not well documented, though.

Some comments on your code:
You need to store the document
You should remove the BitmapTableEntry after using it, otherwise there remain orphan data in the document (see the doc size)
The sequence is important: I just succeeded when setting the GraphicURL after the shape was inserted into the drawpage. I also only succeeded when giving the shape a name
The below code was tested (and worked) on AO 4.1.6.

Good luck,
ms777

Basic

Sub Main
urlPicFile = "file:///C:/Users/Martin/Downloads/pic.jpg"
urlCalcFile = "file:///C:/Users/Martin/Desktop/InsertPicture2.ods"
oDoc = StarDesktop.loadComponentFromURL(urlCalcFile, "_blank", 0, Array())

sName = "jpg2" 'the name in the BitmapTable. Choose any.

bmt = oDoc.createInstance("com.sun.star.drawing.BitmapTable") 

if bmt.hasByName(sName) then
	bmt.replaceByName(sName, urlPicFile)
else
	bmt.insertByName(sName, urlPicFile)
endif

oG = bmt.getByName(sName)

oShape = oDoc.createInstance("com.sun.star.drawing.GraphicObjectShape")

oDrawPage = oDoc.getDrawPages().getByIndex(0)


oSize = oShape.Size
oSize.Width = 10000
oSize.Height = 10000
oShape.Size = oSize
oShape.Name = "My Shape" 'seems like you have to give it a name to be stored
oDrawPage.add(oShape)

oShape.GraphicUrl = oG ' must be after the shape has been added to the drawpage

bmt.removeByName(sName) 'this is required. Otherwise, some orphan data remain in the document. Test by checking the size of the document after manually removing the pic. 

oDoc.setModified(true) 'not sure if this is required
oDoc.store() 'this is required
oDoc.close(true)
End Sub

Here in C#:

        public static void Main() {

	        string urlPicFile  = @"file:///C:/Users/Martin/Downloads/pic.jpg";
	        string urlCalcFile = @"file:///C:/Users/Martin/Desktop/InsertPicture2.ods";

            Console.WriteLine("urlPicFile: "+urlPicFile);
            Console.WriteLine("urlCalcFile: "+urlCalcFile);

    // create the desktop
            XComponentContext XCC = uno.util.Bootstrap.bootstrap();
            XMultiComponentFactory XMCF = (XMultiComponentFactory)XCC.getServiceManager();
            XMultiServiceFactory XMSF1 = (XMultiServiceFactory)XCC.getServiceManager();
            XComponentLoader XCL = (XComponentLoader)XMSF1.createInstance("com.sun.star.frame.Desktop");

    // open the spreadsheet document
            PropertyValue[] pPV = new PropertyValue[2];
            pPV[0] = new PropertyValue();
            pPV[0].Name = "Hidden";
            pPV[0].Value = new uno.Any(false);
            pPV[1] = new PropertyValue();
            pPV[1].Name = "ReadOnly";
            pPV[1].Value = new uno.Any(false);
            XComponent XCo = XCL.loadComponentFromURL(urlCalcFile,"_blank",0,pPV);

            XMultiServiceFactory XMSF2 = (XMultiServiceFactory)XCo;


    // create bitmap container containing image
	        String sName = "jpg2";
            XNameContainer XNC = (XNameContainer) XMSF2.createInstance("com.sun.star.drawing.BitmapTable");
            if (XNC == null) Console.WriteLine("XNC is null");

            if (XNC.hasByName(sName)) {
                Console.WriteLine("already existing");
                XNC.replaceByName(sName, new uno.Any(urlPicFile));
            } else {
                XNC.insertByName(sName, new uno.Any(urlPicFile));
            }
            String oG = XNC.getByName(sName).Value.ToString();
            Console.WriteLine("finished creating BitmapTable entry, oG: " + oG);


     // create graphic shape object
            XShape XS = (XShape) XMSF2.createInstance("com.sun.star.drawing.GraphicObjectShape");
            if (XS == null) {
              Console.WriteLine("XS is null");
              return;
            }

     // get the drawpage (slightly different from the Basic solution)

            XSpreadsheets XSSs = ((XSpreadsheetDocument)XCo).getSheets();
            XIndexAccess XNA = (XIndexAccess)XSSs;
            XSpreadsheet XSS = (XSpreadsheet)XNA.getByIndex(0).Value;
            XDrawPageSupplier XDPS = (XDrawPageSupplier)XSS;
            XDrawPage XDP = XDPS.getDrawPage();
            if (XDP == null) {
              Console.WriteLine("XDP is null");
              return;
            }

    // set the shape size, add it to the drawpage

            unoidl.com.sun.star.awt.Size Sc = XS.getSize();
            Sc.Width = 10000;
            Sc.Height = 10000;

            XS.setSize(Sc);

            XShapes XSs = (XShapes)XDP;
            try {
              XSs.add(XS);
            }
            catch (System.Exception E) {
              Console.WriteLine("Add: "+E);
            }

    // set the shape GraphicUrl and Name
            XPropertySet XPS = (XPropertySet)XS;
            XPS.setPropertyValue("GraphicURL",new uno.Any(oG));
            XPS.setPropertyValue("Name",new uno.Any("My Shape"));


            XNC.removeByName(sName);

    // finish up
            XModifiable XM = (XModifiable)XCo;
            XM.setModified(true); 


            XStorable XSto = (XStorable)XCo;
            XSto.store();

            XCloseable XCl = (XCloseable)XCo;
            XCl.close(true);

            XDesktop XD = (XDesktop)XCL;
            if (XD != null) XD.terminate();
        }

I’ve been plugging away at this and have made a lot of progress. I’m quite sure that I can do what I want. However I’ve been stymied by the fact that LibreOffice has changed the way images are specified, and all the examples I could find use the old way, which doesn’t work any more.

Specifically, what I understand is that now you need to specify your image as an XGraphic (instead of a GraphicURL) using the XGraphicProvider interface, which gets a MediaProperties service specifying the source image file. I’ve been studying all the documentation trying to grasp what all these things are, and I’ve made a lot of progress but it’s hard for me to hold it all in my head at once. I also have gotten fairly good at using MRI but don’t know how to get it to tell me about XGraphicProvider and related things.

Below is a very stripped down version of what I’ve done with respect to creating the XGraphic that needs to be specified to the GraphicObjectShape which will ultimately contain the image. It returns a null when I make the call that should create the XGraphic I need. I’ve spent a couple of days trying to get past this obstacle with no progress, and I’ve run out of things to try.

using  System;
using  System.Collections.Generic;
using  System.Drawing;
using  System.IO;
using  System.Threading;
using unoidl.com.sun.star.awt;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;
using unoidl.com.sun.star.bridge;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.sheet;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.container;
using unoidl.com.sun.star.drawing;
using unoidl.com.sun.star.graphic;
using unoidl.com.sun.star.table;
using unoidl.com.sun.star.text;
using unoidl.com.sun.star.util;

// addpic
// add picture to spreadsheet - debug version

class OpenOfficeApp {

  [STAThread]
  static void Main(string[] args) {

    bool lreadonly;
    string pqfile;
    string pqURL;
    string pqpic;

    pqfile = "file:///D:/Documents/NSexeye/ODS%20File%20Access/"+
                                                     "addpix/addpic.ods";
    pqpic = "addpic2";
    pqURL = pqpic+".jpg";
    lreadonly = false;

    Console.WriteLine("Using: "+pqfile);

    // get the desktop
    XComponentContext XCC = uno.util.Bootstrap.bootstrap();
    XMultiComponentFactory XMCF =
                              (XMultiComponentFactory)XCC.getServiceManager();
    XMultiServiceFactory XMSF = (XMultiServiceFactory)XCC.getServiceManager();
    XComponentLoader XCL =
        (XComponentLoader)XMSF.createInstance("com.sun.star.frame.Desktop");

    // open the spreadsheet
    PropertyValue[] pPV = new PropertyValue[2];
    pPV[0] = new PropertyValue();
    pPV[0].Name = "Hidden";
    pPV[0].Value = new uno.Any(true);
    pPV[1] = new PropertyValue();
    pPV[1].Name = "ReadOnly";
    if (lreadonly) pPV[1].Value = new uno.Any(true);
    else pPV[1].Value = new uno.Any(false);
    XComponent XCo = XCL.loadComponentFromURL(pqfile,"_blank",0,pPV);

    // create graphic object containing image
    object oGP = XMCF.createInstanceWithContext(
                              "com.sun.star.graphic.GraphicProvider",XCC);
    if (oGP == null) {
      Console.WriteLine("oGP is null.  Aborting.");
      return;
    }
    XGraphicProvider XGP = (XGraphicProvider)oGP;
    if (XGP == null) {
      Console.WriteLine("XGP is null.  Aborting.");
      return;
    }
    pPV = new PropertyValue[1];
    pPV[0] = new PropertyValue();
    pPV[0].Name = "URL";
    pPV[0].Value = new uno.Any(pqURL);
    Console.WriteLine("Creating XGraphic containing "+pqURL);
    XGraphic XG = XGP.queryGraphic(pPV);

    // *** XG is null here
    if (XG == null) {
      Console.WriteLine("XG is null.  Aborting.");
      return;
    }

    // ... lots of stuff to be added here

    // save and close the spreadsheet
    XModifiable XM = (XModifiable)XCo;
    XM.setModified(true);
    XStorable XSt = (XStorable)XCo;
    XSt.store();
    XCloseable XCl = (XCloseable)XCo;
    XCl.close(true);

    // terminate LibreOffice
    // *** I want this to not terminate it if something else is open
    XDesktop XD = (XDesktop)XCL;
    if (XD != null) XD.terminate();
  }
}

This is a complete program and should compile and run. I’d very much appreciate it if someone could look at it and try to figure out where I’m going wrong. Everyone’s help has been invaluable in getting me this far, and it would be great if you could get me past this obstacle.

My goal here, besides getting my program to work, is to end up with a working sample program that shows how to add images to a spreadsheet and how to manipulate them. There seems to be quite a bit of interest in doing this, and such a sample would be invaluable to a small but significant number of users.

Thanks!

Hi, if you want to store the picture in the document you have to follow the way I outlined here in this thread C# SDK: trying to write a program to insert an image into a cell - #14 by ms777

Good luck,
ms777

Thank you ms777, but I spent days trying to get your code to work and it doesn’t. I’m also not going to learn Basic to do this.

Here is the SDK documentation for the GraphicURL property of the GraphicObjectShape service:

This is a url to the source bitmap for this graphic shape.

**[Deprecated:](https://api.libreoffice.org/docs/idl/ref/deprecated.html#_deprecated000105)**

as of LibreOffice 6.1 - use Graphic instead

I tried to do it the way you suggested and it doesn’t work as of the current version of LibreOffice. That’s why I’m trying to do it using XGraphic.

Thanks for taking the time to reply. If I’m doing something wrong or misunderstanding your reply, please enlighten me.

… works like a charm on LO 7.4.3.2
TestInsertPicture.ods (8.5 KB)
You have to modify the paths in the BASIC function.
Generally, I recommend to develop the API calls in LO Basic. Once it is working there, it is easy to translate to C# or python or whatever

Edit: works also on LO 7.6.0.0.alpha0

Thanks. I decided to just try to compile and run your (C#) code rather than trying to merge what it does into my program. It compiled (once I got the paths straightened out) but throws an exception, in the same place it did when I tried it in my code: where it’s trying to set the “GraphicURL” property.

It also gets partway through inserting the image in the spreadsheet, but doesn’t. It shows


(I resized the cells and manually added the same picture in C2 to show what it’s supposed to look like.) Interestingly it looks like it got the size right, because it sets that before loading the image (which fails).

Doing this has given me a couple of new things to try to get my program working however. If I get it working I’ll post it.

Thanks again!

I got the XGraphic created; the trouble was that I was using a plain file path as the URL property, instead of the Uno path style (“file:///…”). Dumb mistake. Now I get a non-null XGraphic value, but of course I don’t know if it has my image in it.

It is supremely annoying that the LibreOffice C# SDK gives few clues when something is wrong, it just doesn’t work. I know the tradeoff when writing production code between having many error messages to help diagnose problems, and the desire to have the program (appear to) keep running regardless of what is going on. I’m strongly in favor of extensive error checking and reporting, even in production programs.

I’m now stuck on a new problem. I have my XGraphic, but I have to supply it to my GraphicObjectShape as the “URL” property, which is supposed to be an XGraphic. However setPropertyValue requires that it be supplied as a uno.Any value, and I can’t figure out how to do that. How do I convert XGraphic to a uno.Any? I’ve tried everything I could think of, and read all the seemingly relevant parts of the SDK documentation, without success.

Thank you!

Please post the code

Sorry. It’s getting pretty long so I’m just posting the part that doesn’t work:

    // set image XGraphic
    XPropertySet XPS = (XPropertySet)XS;
    XPS.setPropertyValue("Graphic",XG);
    XPS.setPropertyValue("Name",new uno.Any(pqpic));

It’s the line that is trying to set the “Graphic” property that fails. It gives a compiler error “addpic.cs(145,36): error CS1503: Argument 2: cannot convert from ‘unoidl.com.sun.star.graphic.XGraphic’ to ‘uno.Any’”. I also tried using “new uno.Any(XG)” and it gives a similar error.

This is a very simple question: how do I pass an object of type XGraphic in an argument that requires a uno.Any type.

I’ve tried everything I could think of. There must be something similar to uno.Any() that accepts an XGraphic object or something that an XGraphic object can be cast into. The specification for that property says its value should be an XGraphic, so there must be a way to pass an XGraphic as a uno.Any.

Thanks!

I also asked my question on stackoverflow.com (per a suggestion made earlier) and someone there gave me the answer. The second argument of the call to setPropertyValue (“Graphic”,uno.Any type) needs to include another argument to uno.Any() specifying the type of XGraphic as follows: “new uno.Any(typeof(XGraphic),XG)”. That’s how to get an argument of type uno.Any that specifies an XGraphic instance.

After making that change, my program compiled, ran, and inserted an image! I am hopeful that this is the last roadblock in getting my program working, and I look forward to posting a complete, running sample program soon.

Thanks to everyone who helped me! Every suggestion helped in some way, usually by pointing me in a new direction that led me to a solution to one of my many difficulties.

Well it’s five months later and I’m still plugging away at this in my spare time. Yes it’s turning out to be possible to do, and I’m very close to having a working program. I’m now stuck on cleaning up the details of getting the sizes and positions associated with the new shapes I’m adding to be correct.

[problem solved - see next post…]

Where I’m stuck now is something that is probably simple: finding out the size and position of a cell with nothing in it. These values are undefined, and putting text in the cell doesn’t seem to define them. I’m referring to these values:

 // get cell size and position
      XC = (XCell)XCR.getCellByPosition(Im6.ncol,Im6.nrow);
      XTR = (XTextRange)XC;
      Im6.Szc = (unoidl.com.sun.star.awt.Size)
                       ((XPropertySet)XC).getPropertyValue("Size").Value;
      Im6.Ptc = (unoidl.com.sun.star.awt.Point)
                   ((XPropertySet)XC).getPropertyValue("Position").Value;

For cells which have images inserted manually using LibreOffice (“Insert->Image…”) the values are correct. Cells which don’t have anything have Size and Position values which are garbage like (-1073710966,-1073551852). Note that I’m trying to get the values which are properties of the XCell, not of the XShape (there isn’t one) or of an Anchor (ditto).

Where I’m going with this is that I want to set these values in the graphic image shapes (XShape) I am inserting in the DrawPage, and probably also in Anchors I need to create and add, I think to the image’s XShape. I’m getting there.

As I’ve mentioned before, what I’m going to end up with is a working sample C# program that inserts images into cells the same way as ("Insert->Image…) does, and which scales and positions all images so they fit and are centered in the associated cells (something you can’t do directly with LibreOffice). The sample program will be well commented to make it easy for others to pull out what they need to do similar things.

As always, thanks!

-jimc

I found that one. A really dumb mistake. The casts in the last two statements were being applied to the wrong thing. I needed another set of parentheses around everything after the casts. Like I said, really dumb.

Sorry for the bother. I’ll update again soon…

-jimc

It’s working!!!

Lots of cleanup and probably a few minor bugs, but with C# when a program stops producing garbage output it’s usually pretty much done.

Thanks to everyone for all the help, and I hope my work proves useful to others.

-jimc