The goal of this exercise is to drive OpenOffice or LibreOffice through a C# application and to use a hidden spreadsheet as a back-end. Please Note: the following snippet/application is only a proof of concept. Information about OpenOffice automation is not all that easy to get. A good starting point though is located at OpenOffice.org: http://api.openoffice.org/docs/common/ref/com/sun/star/module-ix.html
This post is aimed at the intermediate programmer, who knows their way around the Visual Studio. If you are looking for some hints and pointers about OpenOffice or LibreOffice automation, I hope that you find some here.
I'll present three parts:
1) a base class responsible for opening, hiding or showing, and closing Open Office itself,
2) a spreadsheet class derived from that base class, for some spreadsheet specific tasks and
3) ideas for a simple wrapper application using those classes and some more hints about manipulating a spreadsheet.
This should send you on your way for your own experiments and designs.
Also please note: Since this is only a concept, I have omitted all error checking. I leave that to you, have fun. This post is about the mechanics, not a polished (and presentable) application! Look at this as my collection of notes.
I hope that the naming and commenting of this code explains the code. Now, let's get to it.
The Base Class
First task for the base class is to open or connect to an instance of OpenOffice and to show or hide it. I show or hide it with the parameter "show" after connection to an OpenOffice instance. I also provide a component loader, and of course a way of closing OpenOffice.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using unoidl.com.sun.star.lang; using unoidl.com.sun.star.uno; using unoidl.com.sun.star.frame; using System.Diagnostics; using System.Runtime.InteropServices; /** This is a base class for OpenOffice. * Class OfficeBase opens or connects to office * * Argument * param name="show" to show or hide openoffice; true = show, false = hide * * NOTE: At this point only a concept. No error checking. */ public class OfficeBase { // show Open office open-window if true public const bool showBootStrap = true; // SW_SHOWMAXIMIZED to maximize the window private const int SW_SHOWNORMAL = 1; // SW_SHOWMINIMIZED to minimize the window private const int SW_SHOWMINIMIZED = 2; // SW_SHOWNORMAL to make the window be normal size private const int SW_SHOWMAXIMIZED = 3; private XComponentContext HnComponentContext; private XMultiServiceFactory HnMultiServiceFactory; private XComponentLoader HnComponentLoader; [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow); // property read only public XComponentLoader ComponentLoader { get { return HnComponentLoader; } } /// <summary> /// start OpenOffice hidden or visible, initialise HnComponentLoader /// </summary> /// <param name="show"></param> public OfficeBase(bool show) { HnMultiServiceFactory = connect(); // hide app? if (!show) { // retrieve LibreOffice process Process[] processes = Process.GetProcessesByName("soffice.bin"); // minimize soffice foreach (Process p in processes) { // get mainwindow handle IntPtr pFoundWindow = p.MainWindowHandle; // Use Handle to minimize if (!pFoundWindow.Equals(IntPtr.Zero)) { ShowWindowAsync(pFoundWindow, SW_SHOWMINIMIZED); } } } // get a component loader HnComponentLoader = (XComponentLoader)HnMultiServiceFactory.createInstance("com.sun.star.frame.Desktop"); } /// <summary> /// Close main Open office app /// </summary> public void OfficeBaseStop() { //close xdesktop XDesktop xDesk = (XDesktop)HnComponentLoader; xDesk.terminate(); } // Connect to a running office that is accepting connections. // Return: The ServiceManager to instantiate office components. private XMultiServiceFactory connect() { HnComponentContext = uno.util.Bootstrap.bootstrap(); return (XMultiServiceFactory)HnComponentContext.getServiceManager(); } }Next up is the spreadsheet class, derived from the base class. Main purpose is to provide a spreadsheet.
using unoidl.com.sun.star.lang; using unoidl.com.sun.star.sheet; using unoidl.com.sun.star.beans; /** This is a helper class for spreadsheet operations. * Base class myOfficeBase connects to office * Child class creates a spreadsheet document. * * Argument * strFileName : Complete path and filename with extension * If argument strFileName is empty, new empty Spreadsheet is created. * * NOTE: At this point only a concept. No error checking. */ public class SheetHelperClass : OfficeBase { // show SpreadSheet if true public const bool showSpreadSheet = true; private XSpreadsheetDocument SpreadSheetDoc; /// <summary> /// strFileName: Complete path and Filename with extension. /// If strFileName is empty, new empty spreadsheet document is created /// </summary> /// <param name="FileName"></param> /// <param name="showBoot"></param> /// <param name="showSpreadSheet"></param> public SheetHelperClass(string FileName, bool showBoot, bool spreadSheetVisible) : base(showBoot) { // Create a new spreadsheet document SpreadSheetDoc = GetSpreadSheetDocument(FileName, spreadSheetVisible); } // Creates a spreadsheet document. // strFileName: Complete path and Filename with extension. // If strFileName is empty, new empty Spreadsheet is created // Return: The XSpreadsheetDocument interface of the document. private unoidl.com.sun.star.sheet.XSpreadsheetDocument GetSpreadSheetDocument(string SpreadSheetFileName, bool makeVisible) { // If filename is empty create a new sheet. string SpreadSheetUrl; if (string.IsNullOrEmpty(SpreadSheetFileName)) SpreadSheetUrl = "private:factory/scalc"; else SpreadSheetUrl = SpreadSheetFileName; // get a property PropertyValue SpreadSheetProperty = new unoidl.com.sun.star.beans.PropertyValue(); // use property to have spreadsheet hidden or shown SpreadSheetProperty.Name = "Hidden"; // false to see spreadsheet, true to hide if SpreadSheetProperty.Value = new uno.Any(!makeVisible); // make that an array PropertyValue[] SpreadSheetPropertyValue = new unoidl.com.sun.star.beans.PropertyValue[1]; SpreadSheetPropertyValue[0] = SpreadSheetProperty; // load sheet XComponent spreadSheetComponent = ComponentLoader.loadComponentFromURL( SpreadSheetUrl, "_blank", 0, SpreadSheetPropertyValue); // return Spreadsheet Document return (unoidl.com.sun.star.sheet.XSpreadsheetDocument)spreadSheetComponent; } /// <summary> /// Returns the spreadsheet with the specified index (0-based). /// </summary> /// <param name="nIndex">The index of the sheet.</param> /// <returns>spreadsheet with the specified index (0-based).</returns> public unoidl.com.sun.star.sheet.XSpreadsheet getSpreadsheet(int nIndex) { // Collection of sheets unoidl.com.sun.star.sheet.XSpreadsheets xSheets = SpreadSheetDoc.getSheets(); unoidl.com.sun.star.container.XIndexAccess xSheetsIA = (unoidl.com.sun.star.container.XIndexAccess)xSheets; unoidl.com.sun.star.sheet.XSpreadsheet xSheet = (unoidl.com.sun.star.sheet.XSpreadsheet) xSheetsIA.getByIndex(nIndex).Value; return xSheet; } /// <summary> /// Close Spreadsheet object and remove xDesktop /// </summary> public void Dispose() { //close xcomponent unoidl.com.sun.star.util.XCloseable sheetDocToClose = (unoidl.com.sun.star.util.XCloseable)SpreadSheetDoc; sheetDocToClose.close(false); //terminate openOffice OfficeBaseStop(); } }Just put these classes into your project and don't forget the right namespace:
namespace YourLibreOfficeSpreadSheetNameSpace {All that's left to do is building a user form to use these classes. Following are some ideas to do so. You will notice a lot of hard wired parts. That is to just have a quick playground...
public partial class frmMain : Form { public SheetHelperClass SpreadSheetHelper; public unoidl.com.sun.star.sheet.XSpreadsheet WorkingSpreadSheet;And some experimenting:
private void btnIncreaseValue_Click(object sender, EventArgs e) { // define a cell unoidl.com.sun.star.table.XCell xCell = WorkingSpreadSheet.getCellByPosition(1, 0); // Set cell value. Increase by one xCell.setValue(xCell.getValue() + 1); } private void btnOpenExistingSpreadSheet_Click(object sender, EventArgs e) { // Create Spreadsheet object, hide everything SpreadSheetHelper = new SheetHelperClass("file:///Z:\\H\\CSharp\\Sample\\SampleSheet.ods", !SheetHelperClass.showBootStrap, !SheetHelperClass.showSpreadSheet); // get Spreadsheet Sheet 1, index 0-based WorkingSpreadSheet = SpreadSheetHelper.getSpreadsheet(0); // populate TextBox on unser interface with formula // define a cell unoidl.com.sun.star.table.XCell xCell = null; // get cell xCell = WorkingSpreadSheet.getCellRangeByName("C1").getCellByPosition(0, 0); // display cell formula from sheet in textbox tbFormula.Text = xCell.getFormula().ToString(); } private void btnCloseSpreadSheet_Click(object sender, EventArgs e) { SpreadSheetHelper.Dispose(); tbFormula.Text = "Closed"; } private void btnSetValue_Click(object sender, EventArgs e) { // define a cell unoidl.com.sun.star.table.XCell xCell = null; //get a cell xCell = WorkingSpreadSheet.getCellByPosition(1, 0); // Set cell value. xCell.setValue(1234); } private void btnGetValue_Click(object sender, EventArgs e) { // define a cell unoidl.com.sun.star.table.XCell xCell = null; //get calculated cell xCell = WorkingSpreadSheet.getCellByPosition(2, 0); // Show cell value. MessageBox.Show(xCell.getValue().ToString()); } private void btnSetFormula_Click(object sender, EventArgs e) { // define a cell unoidl.com.sun.star.table.XCell xCell = null; //get calculated cell xCell = WorkingSpreadSheet.getCellRangeByName("C1").getCellByPosition(0, 0); if (!string.IsNullOrEmpty(tbFormula.Text.ToString())) xCell.setFormula(tbFormula.Text.ToString()); else MessageBox.Show("Input null or empty"); }Layout of loaded sample spread sheet: Cell position(2,0), or C1, is the result cell with an initial formula of =B1*10+B2. Cell B2 is a constant factor and B1 an input set by btnSetValue_Click.
This is written for Windows XP SP3, LibreOffice 3.4 using Visual C# 2010 Express. I hope you found some answers to your questions, and have fun with further experiments.
3 comments:
Thank you, especially since no one else has thanked you yet!
I have bookmarked your page and will come back to it when I get to starting creating the spreadsheet element of my program.
Thanks for this Elaborate explanation, just what I was looking for!
I love this post, and I was absolutely thrilled to see the other links that you published that go along with this topic. Thank you for sharing your thoughts. I am happy to be a new follower. :)
Count
www.imarksweb.org
Post a Comment