Search code examples
c#imagesdklibreoffice-calc

LibreOffice Calc C# SDK: program to insert images into cells, stuck trying to create XGraphic


Background: I'm trying to write a program to insert an image into a cell of a spreadsheet. LibreOffice recently changed how this is done, and all the samples I could find use the old method which no longer works.

Technically I know that you can't "insert" an image into a cell and that such an image is an overlay on a DrawPage that sits on top of the spreadsheet to "decorate" it.

One of the first steps in doing this (the new way) is to create an XGraphic object which contains the image. The process is to create an XGraphicProvider and call it with MediaProperties that specify the image file URL to be loaded. I have a program that is supposed to do this but the resulting XGraphic is null. The LO SDK gives pretty much no information when you do something wrong; it just doesn't work.

Here is the code I have, with all the headers removed:

// 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();
  }
}

I get a null for the XGraphic, in the place indicated in the comments. I don't know if the call to create it is failing, or if one of the earlier steps of the process are incorrect.

My goal here, in addition to getting my program working, is to create a sample program showing how to add an image to a Calc spreadsheet cell, and to manipulate such images. There are a fair number of people asking questions about this and none of the examples I've found will work. I think a good working sample will be of value.

I've spent a lot of time searching for information and code samples for this, with nothing that helps. I've tried to find ways to verify the validity of the XGraphicProvider interface with no luck. I've run out of things to try.

I'm hoping someone who knows about the LibreOffice SDK can take a look and maybe see what I'm doing wrong.

Update: I figured out what I was doing wrong: I was passing a bare filename in the "URL" property to XGraphicProvider. It has to be the same format (starting with "file:///") as the spreadsheet's file name specification.

Now I'm stuck with another property problem. The XGraphic has to be specified as a parameter to the GraphicObjectShape's Graphic property, but the setPropertyValue() function requires that it be a uno.Any type. I can't figure out how to specify an interface name like XGraphic as a uno.Any.

Here is the piece of code that won't compile, complaining that it can't convert an XGraphic to a uno.Any, in the first setPropertyValue call:

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

XG is an XGraphic type. Using "new uno.Any(XG)" doesn't work either, giving a similar compiler error.


Solution

  • After trying unsuccessfully for a few hours to get the latest LO SDK up and running, let me offer some untested ideas.

    First of all, here is some working Basic code, no doubt similar to what you're translating from. The important line is oShape.Graphic = oProvider.queryGraphic(Props()).

    oDoc = ThisComponent
    oSheet = oDoc.CurrentController.ActiveSheet
    pqURL = "file:///C:/Users/JimK/Desktop/addpic.jpg"
    oProvider = createUnoService("com.sun.star.graphic.GraphicProvider")
    oShape = oDoc.createInstance("com.sun.star.drawing.GraphicObjectShape")
    Dim Props(0) as new com.sun.star.beans.PropertyValue
    Props(0).Name= "URL"
    Props(0).Value = pqURL
    oShape.Graphic = oProvider.queryGraphic(Props())
    oCell = oSheet.getCellByPosition(5,5)
    oShape.Name = oCell.AbsoluteName + "##" + Props(0).Value
    oShape.Anchor = oCell
    oSheet.DrawPage.add(oShape)
    'Resize
    w = oShape.Graphic.Size.Width
    h = oShape.Graphic.Size.Height
    wcl = oCell.Size.Width
    hcl = oCell.Size.Height
    If w<>0 and h<>0 then
        oCell.String=""
        Dim Size as new com.sun.star.awt.Size
        Size.Width = wcl
        Size.Height = h*wcl/w
        If Size.Height > hcl then
            Size.Width = hcl*w/h
            Size.Height = hcl
        Endif
        oShape.setSize(Size)
        oShape.setPosition(oCell.Position)
        erase oShape
    Else
        oShape.dispose()
    Endif
    

    Now, how to translate this to C#? It looks like you may need to explicitly specify the type. In the SDK example, there are calls like this.

        xFieldProp.setPropertyValue(
            "Orientation",
            new uno.Any(
                typeof (unoidl.com.sun.star.sheet.DataPilotFieldOrientation),
                unoidl.com.sun.star.sheet.DataPilotFieldOrientation.DATA ) );
    

    So in your case, something like this:

    XPS.setPropertyValue(
        "Graphic"
        new uno.Any(
            typeof(unoidl.com.sun.star.graphic.XGraphic),
            XG));
    

    Alternatively, follow the suggestion here: set GraphicURL, which should load the image and set Graphic for you.