65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 从 Visual Studio 2022 自动化 OpenOffice Calc

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (3投票s)

2023 年 4 月 3 日

CPOL

15分钟阅读

viewsIcon

13251

downloadIcon

361

一份关于如何使用 Visual Studio 2022 和 C# 语言自动化 Apache OpenOffice Calc 的分步指南

Open Office Test Run

引言

Apache OpenOffice 是一款免费开源的办公软件套件,在很大程度上可与 Microsoft Office 相媲美。虽然存在有关自动化各种应用程序的文档,但关于让 Visual Studio 应用程序正常工作的完整“操作方法”指南却很少。本文将提供分步说明,介绍如何安装 OpenOffice 和 SDK,以及如何设置一个测试应用程序来自动化 OpenOffice Calc - Microsoft Excel 的替代方案。本教程将提供足够的详细信息,包括设置具有各种值和属性的单元格,以便您开始进行 OpenOffice 自动化任务。特别是,我将描述如何配置测试应用程序来处理混合模式程序集,因为演示应用程序是用 .NET 4.x 编写的,而 OpenOffice 是使用 .NET 2.x 开发的。我们假设您对 Visual Studio、C# 和应用程序开发有合理的了解。

安装 OpenOffice 和 SDK

在开始开发之前,必须在您的系统上安装 OpenOffice。OpenOffice 套件安装程序可以从 Apache OpenOffice - Official Download 网站下载。对于 Windows,只有 32 位安装程序可用,因此开发的应用程序也必须是 32 位。本文的代码是针对 OpenOffice 4.1.14 版本编写的。我使用了默认的安装位置和选项进行安装。在我的系统(Windows 10)上,文件安装在 C:\Program Files (x86)\OpenOffice 4。请注意,如果您为此项目全新安装 OpenOffice,则需要运行一次,以便它完成一些基本配置,然后才能通过您的应用程序进行连接。否则,在运行您的应用程序时,自动化过程可能会意外挂起,因为一些后台配置尝试不成功。

除了基本的 OpenOffice 套件外,您还必须下载并安装 OpenOffice SDK。有关 SDK 的一般信息可以在 Apache OpenOffice SDK 网站 上阅读。当前版本的 SDK 应该可以在 Apache OpenOffice - Downloads 页面找到。还有一个 开发者指南。SDK 版本必须与安装的 OpenOffice 套件版本匹配 - 在我的情况下是 4.1.14。同样,我使用了默认位置和默认选项安装了 SDK。SDK 文件安装在 C:\Program Files (x86)\OpenOffice 4\sdk。通用语言接口 DLL 安装在此目录的 cli 子文件夹中。请记下这些文件的位置 - 您稍后将需要它们。

安装 Visual Studio 2022 Community

本文假设您对基本 C# 开发有一定的了解。对于这个测试应用程序,我下载并安装了免费的 Visual Studio Community 版本。Visual Studio 安装中唯一需要的组件是 .NET 桌面开发组件。

VS Install Components

创建 C# OpenOffice Calc 项目

创建 Windows Forms 应用程序

启动 VS2022 时,选择 **创建新项目** 项。

Create a new project.

从右上角的下拉筛选器列表中,选择 **C#**、**Windows** 和 **Desktop** 筛选器。然后找到 **Windows Forms App (.NET Framework)** 选项,然后单击 **Next**。

Create a new C# Win Forms project.

为您的解决方案起一个合适的名字,我选择了 **Open Office Test**,然后单击 **Create** 按钮。

Name your app.

将为您创建一个基本的 Windows 窗体。如果需要,可以通过单击 **Solution Explorer** 中的窗体名称来重命名窗体。在窗体上添加一个按钮,并为其命名和添加标签。

Rename form and add button.

添加 .NET Framework 类库

现在,我们将向解决方案添加一个 .NET Framework 类库。右键单击 **Open Office Calc** 解决方案,然后选择 **Add -> New Project...**。

Add New Project

出于某种奇怪的原因,Visual Studio 在添加使用 .NET Framework 的 .NET 类库时并不方便。即使选择了 **C#, Windows** 和 **Desktop** 筛选器,.NET Framework 类库选项默认也不会显示。在 **Add a new project** 窗体的顶部搜索框中输入 **class library framework**,然后向下滚动到 **Other results based on your search** 部分,找到 **Class Library (.NET Framework)** 选项,然后单击 **Next**。

Add Class Library NET Framework

将类库命名为 OOCalc,然后按 **Create** 按钮。

Configure your new project

右键单击文件 Class1.cs,然后选择 **Rename**。将文件重命名为 OOCalc,并接受将项目中的 Class1 引用重命名。

配置 OpenOffice

在我们可以继续与 OpenOffice 交互之前,我们需要配置我们的项目以访问 OpenOffice 组件。配置项目以访问 OpenOffice 需要两个步骤:

  • 添加对 OpenOffice CLI DLL 的引用
  • 配置您的系统以访问混合模式程序集

添加对 OpenOffice 通用语言接口 DLL 的引用

要向项目中添加对通用语言接口 (CLI) DLL 的引用,请在 Solution Explorer 中右键单击 OOCalc 项目的 **References** 节点,然后选择 **Add Reference...**。

Right click and add reference

在 **Reference Manager** 窗体中,选择左侧的 **Browse** 节点,然后按 **Browse** 按钮。

Browse to OO CLI DLLs

浏览到您的 OpenOffice 安装文件夹,例如 C:\Program Files (x86)\OpenOffice 4,找到 sdk\cli 文件夹,然后选择该文件夹中的所有 DLL。

Add OO CLI References

按 **Add** 按钮会将它们添加到您的项目中。

Reference Manager with CLI dlls added

在添加引用的同时,右键单击 **Open Office Calc** 项目,然后再次选择 **Add Reference...**。选择左侧的 **Projects** 节点,然后按 **OK** 添加对 OOCalc 项目的引用。这使您的 Windows 窗体能够访问 OOCalc 类库。

Add Reference to OOCalc

如果现在构建应用程序,很可能会遇到一个生成警告:

Warning MSB3270: The processor architecture of the project being built "MSIL" and the processor architecture of the reference "cli_cppuhelper, ..." are mismatched. This mismatch may cause runtime failures. ...

这是一个已知问题,不影响系统运行,可以忽略。

配置混合模式程序集访问

如果现在我们尝试编写代码来访问我们的 OpenOffice CLI 对象,我们将很快遇到此异常:

Mixed Mode Assembly Exception

这里的问题是,您当前的程序集(使用 .NET 4.x 构建)正在尝试访问一个使用 .NET 2.x 构建的 OpenOffice 子程序集。虽然 .NET 允许不同 .NET 版本之间进行并行执行,但默认情况下并未启用。要在我们的 OpenOffice 应用程序中启用此功能,请编辑项目中的 app.config 文件,将 <startup> 标签替换为以下内容:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <!-- Replace <startup> tag -->
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <!-- Your application may differ in the runtime version being used -->
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>

Use Legacy V2 Runtime

一些背景和示例

在深入研究应用程序和电子表格代码之前,对 API 架构有一些了解会有所帮助。下面提供了指向更详细解释每个术语或概念的链接。

极度简化地说,CLI DLL 暴露了许多 模块,您在每个代码文件的顶部使用 using 语句包含的项。每个模块提供对 一系列服务和对象 的访问。例如,unoidl.com.sun.star.frame 模块提供了一个服务来加载 XSpreadsheetDocument 对象,从中可以获得 XSpreadsheet 对象和接口。

这些对象公开了 多重继承接口,这些接口提供了对对象功能的访问。每个对象的**方法**通常非常具体、有限且简单,要访问相关功能,必须将对象**转换为**另一个暴露一组操作的接口。通过 XSpreadsheet 对象,例如,您最终可以访问 XCell 对象 - 工作表中的单个单元格。请注意接口有多简单。

// pulled from metadata
namespace unoidl.com.sun.star.table
{
	public enum CellContentType
	{
		EMPTY,
		VALUE,
		TEXT,
		FORMULA
	}
	public interface XCell
	{
		string getFormula();
		void setFormula( [In] string aFormula );
		double getValue();
		void setValue( [In] double nValue );
		CellContentType getType();
		int getError();
	}
}

使用 XCell 接口,我们可以获取或设置单元格中的公式,获取或设置单元格的浮点值,确定单元格类型(空、值、文本或公式)或获取错误状态。这是一个设置单元格浮点值的代码片段。

using unoidl.com.sun.star.sheet;  // for XSpreadsheet
using unoidl.com.sun.star.table;  // for XCellRange and XCell

public void SetValue(String strCellName, double dValue)
{
	// m_xSpreadsheet is XSpreadsheet interface, defined elsewhere
	XCellRange xCellRange = m_xSpreadsheet.getCellRangeByName( strCellName );
	XCell xCell = xCellRange.getCellByPosition( 0, 0 );
	xCell.setValue( dValue );
}

XCell 接口专注于单元格的数值功能。如果您希望将单元格作为文本类型操作,可以将接口转换为 unoidl.com.sun.star.text 模块提供的 XSimpleText 接口。

namespace unoidl.com.sun.star.text
{
	public interface XSimpleText : XTextRange
	{
		XTextCursor createTextCursor();
		XTextCursor createTextCursorByRange( [In] XTextRange aTextPosition );
		void insertString( [In] XTextRange xRange, 
                           [In] string aString, [In] bool bAbsorb );
		void insertControlCharacter( [In] XTextRange xRange, 
             [In] short nControlCharacter, [In] bool bAbsorb );
	}
}  

此接口允许您设置一些文本,创建文本光标或插入控制字符,仅此而已。这里,我们在一个命名单元格中设置了一些文本。

using unoidl.com.sun.star.spreadsheet;  // for XSpreadsheet
using unoidl.com.sun.star.table;        // for XCell
using unoidl.com.sun.star.text;         // for XSimpleText and XTextCursor

public void SetText(String strCellName, String strText)
{
	// m_xSpreadsheet is a previously opened XSpreadsheet object
	XCellRange xCR = m_xSpreadsheet.getCellRangeByName(strCellName);
	XCell xCell = xCR.getCellByPosition(0, 0);
	XSimpleText xSimpleText = (XSimpleText)xCell;
	XTextCursor xCursor = xSimpleText.createTextCursor();
	xSimpleText.insertString(xCursor, strText, bAbsorb: false);
}

对象还可以包含许多 属性,这些属性是确定对象特征的键值对。例如,XCell 对象有一个“CellBackColor”属性,用于确定单元格的背景颜色。

public void SetBackgroundColor( String strCellName, Color clr )
{
	// m_xSpreadsheet is a previously opened XSpreadsheet object
	XCellRange xCellRange = m_xSpreadsheet.getCellRangeByName( strCellName );
	XCell xCell = xCellRange.getCellByPosition( 0, 0 );
	XPropertySet xPropSet = (XPropertySet)xCell;
	UInt32 unClr = (UInt32)clr.R << 16 | (UInt32)clr.G << 8 | (UInt32)clr.B;
	xPropSet.setPropertyValue( "CellBackColor", new uno.Any( unClr ) );
}

开发中的挑战在于找到执行所需任务的合适接口或属性以及如何访问该接口或属性。熟悉文档至关重要。

现在开始 OpenOffice 编码!

我们终于能够开始与 OpenOffice 交互了。我们将:

  • 确定 OpenOffice 是否安装在我们的系统上
  • 设置一些有用的类成员
  • 启动 OpenOffice 桌面应用程序
  • 创建一个电子表格文档
  • 在某些单元格中输入一些数据、一个公式和文本
  • 保存文件
  • 演示一些 Excel 兼容性

在向 OOCalc 类添加项之前,您需要在类文件的顶部包含以下大部分 using 语句:

using uno;
using unoidl.com.sun.star.container;
using unoidl.com.sun.star.lang;
using unoidl.com.sun.star.uno;
using unoidl.com.sun.star.frame;
using unoidl.com.sun.star.sheet;
using unoidl.com.sun.star.beans;
using unoidl.com.sun.star.table;
using unoidl.com.sun.star.text;
using unoidl.com.sun.star.util;

这些是项目中将使用的模块。随着新功能的探索,确定所需模块的最简单方法是浏览对象的定义(右键单击符号并选择 **Go To** definition),例如 XStorable,并观察其所属的命名空间。命名空间通常与模块名称匹配。

// namespace here should be added with using statement
namespace unoidl.com.sun.star.frame
{
	public interface XStorable
	{
		bool hasLocation();
		string getLocation();
		...
  }
}

OpenOffice 已安装?

要确定 OpenOffice 是否已安装,请检查注册表项:Computer\HKEY_CURRENT_USER\SOFTWARE\OpenOffice

using Microsoft.Win32;
public bool bOpenOfficeInstalled
{
	get
	{
		RegistryKey RegKey = Registry.CurrentUser.OpenSubKey
                             ( @"SOFTWARE\OpenOffice", writable: false );
		return RegKey != null;
	}
}

设置有用的类成员

类中的以下成员将在您与 OpenOffice 的整个交互过程中非常有用:

	// These don't really need to be saved, but can come in useful 
	// as more extensive OpenOffice features are accessed.
	private XComponentContext m_xComponentContext = null;
	private XMultiServiceFactory m_xMSFactory = null;
	private XComponentLoader m_xLoader = null;

	// You'll need these ones for many of your basic operations
	private XSpreadsheetDocument m_xDocument;
	private XSpreadsheet m_xSpreadsheet;

启动 OpenOffice 并创建 Calc 文档

我将使用 Excel 中的术语 **workbook** 来表示电子表格文档,它可能包含多个工作表,我将把它们称为 **spreadsheets**。在打开工作簿之前,您需要启动 OpenOffice 组件加载程序,以引导 OO 系统。

public bool bStartOpenOfficeLoader()
{
	try
	{
		if (m_xComponentContext == null)
			m_xComponentContext = uno.util.Bootstrap.bootstrap();
		if (m_xMSFactory == null)
			m_xMSFactory = 
            (XMultiServiceFactory)m_xComponentContext.getServiceManager();
		if (m_xLoader == null)
			m_xLoader = (XComponentLoader)m_xMSFactory.createInstance
                        ( "com.sun.star.frame.Desktop" );
		return true;
	}
	catch (unoidl.com.sun.star.uno.Exception e1)
	{
		Debug.Print( e1.Message );
	}
	return false;
}

引导 (Bootstrapping) 指的是创建一个服务管理器,您可以使用它按需创建 OpenOffice 对象。使用此代码,您可以打开任何一个 OpenOffice 应用程序 - WriterCalcBase 等。但要打开 Calc 并创建工作簿,请使用以下代码。

public bool bCreateWorkbook()
{
	try
	{
		XComponent xComponent =
			m_xLoader.loadComponentFromURL
            ( "private:factory/scalc", "_blank", 0, new PropertyValue[0] );

		m_xDocument = (XSpreadsheetDocument)xComponent;
		XSpreadsheets xSheets = m_xDocument.getSheets();
		XIndexAccess xIndexAccess = (XIndexAccess)xSheets;
		Any any = xIndexAccess.getByIndex( 0 );
		m_xSpreadsheet = (XSpreadsheet)any.Value;
		return true;
	}
	catch (unoidl.com.sun.star.uno.Exception e)
	{
		Debug.Print( e.Message );
	}
	return false;
}

组件加载程序方法 loadComponentFromURL 的参数 "private:factory/scalc" 指示加载程序构造一个 Calc 应用程序对象。"_blank" 参数指示系统打开一个新的空工作簿。new PropertyValue[0] 参数创建一个空的 PropertyValue 对象数组。许多 OpenOffice 调用可以选择需要属性参数,但如果未使用,有时必须提供一个空参数。loadComponentFromURL 调用返回一个通用的 XComponent 对象。xComponent 对象是一个多重继承接口对象,它暴露了许多接口。我们想要的是 XSpreadsheetDocument,所以我们将 xComponent 对象转换为该接口。getSheets() 调用返回一个 XSpreadsheets 对象,乍一看,浏览其定义似乎并不包含电子表格的集合。

public interface XSpreadsheets : XNameContainer
{
	void insertNewByName( [In] string aName, [In] short nPosition );
	void moveByName( [In] string aName, [In] short nDestination );
	void copyByName( [In] string aName, [In] string aCopy, [In] short nDestination );
}

public interface XNameContainer : XNameReplace
{
	void insertByName( [In] string aName, [In] Any aElement );
	void removeByName( [In] string Name );
}

但是,通过探索 XSpreadsheetDocument 的文档,我们看到可以使用接口 XIndexAccess 来访问工作表集合,并使用 getByIndex 方法获取第一个。这是多重继承接口方法的一个示例,其中一个通用对象可以转换为许多不同的接口以访问各种功能。

	XIndexAccess xIndexAccess = (XIndexAccess)xSheets;
	Any any = xIndexAccess.getByIndex( 0 );
	m_xSpreadsheet = (XSpreadsheet)any.Value;

XIndexAccess 对象不直接返回 spreadsheet 对象,而是返回一个 uno.Any 类型的对象。Any objectOpenOffice 系统中经常被用来泛泛地表示几乎任何对象,因此得名。它具有表示其类型的 Type 成员和包含值的 Value 成员,该值必须转换为特定类型才能使用。在这种情况下,我们将其转换为 XSpreadsheet 类型,该类型可用于许多电子表格操作。

将此代码集成到我们的窗体按钮处理程序中,我们现在可以看到一些成果:

private void btnRunOpenOfficeCalcTests_Click(object sender, EventArgs e)
{
	// Create a new Calc instance
	OOCalc.OOCalc ooCalc = new OOCalc.OOCalc();
	if (ooCalc.bOpenOfficeInstalled == false)
		return;
	ooCalc.bStartOpenOfficeLoader();
	ooCalc.bCreateWorkbook();
}

运行代码应该会在一个单独的进程中启动一个 OpenOffice Calc 实例。

First OpenOffice Calc Instance

在电子表格中设置单元格数据

由于我们将主要演示设置电子表格单元格函数,让我们定义一个小函数来方便地获取 XCell 对象:

private XCell xGetXCellByName( string strCellName )
{
	XCellRange xCR = m_xSpreadsheet.getCellRangeByName( strCellName );
	XCell xCell = xCR.getCellByPosition( nColumn: 0, nRow: 0 );
	return xCell;
}

许多电子表格函数都使用单元格范围,即上面的函数中的 XCellRange 对象。传递给函数的名称可以是典型的按列和行表示的单元格位置,例如“A1”。如果已将标签分配给一个或多个单元格,它也可以是命名单元格,例如“InterestRate”。无论哪种方式,getCellByName 函数在成功时返回命名的单元格范围。

要访问特定的单个单元格,我们使用 getCellByPosition 函数调用,并使用基于零的索引来访问单元格的列和行。对于可能熟悉 Excel 自动化的人来说,这与 Excel 不同,因为 Excel 中的大多数索引是基于 1 的,而不是基于 0 的。移植或模仿 Excel 的代码必须处理这种差异。

如前所述,单元格通常包含一个双精度值、一个公式或文本。以下是一些设置这些内容的函数。请注意,为了简洁起见,从此处开始的代码通常不包括异常处理代码。

public void SetValue( string strCellName, double dValue )
{
	XCell xCell = xGetXCellByName( strCellName );
	xCell.setValue( dValue );
}
public void SetFormula( string strCellName, string strFormula )
{
	XCell xCell = xGetXCellByName( strCellName );
	xCell.setFormula( strFormula );
}
public void SetText( string strCellName, string strText )
{
	XCell xCell = xGetXCellByName( strCellName );
	XSimpleText xST = (XSimpleText)xCell;
	XTextCursor xCursor = xST.createTextCursor();
	xST.insertString( xCursor, strText, bAbsorb: false );
}

前两个方法都直接使用 XCell 接口的 setValuesetFormula 成员来设置数值和公式。实际上,文本也可以通过 setFormula 方法设置,但对于更高级的文本处理,XCell 对象会被转换为 XTextXSimpleText 接口,并使用这些接口的文本设置函数。

单元格还可以包含日期和时间,但要成功显示这些,必须将单元格的格式属性调整为显示 DATE 类型的值。

public bool bSetDate( string strCellName, int nYear, int nMonth, int nDay )
{
	// Set the date value.
	XCell xCell = xGetXCellByName( strCellName );
	System.DateTime dt = new System.DateTime(nYear, nMonth, nDay);
	string strDateStr = dt.ToString( "M/dd/yyyy" );

	// You can also set "text" using the setFormula method
	xCell.setFormula( strDateStr );

	// Set date format.
	XNumberFormatsSupplier xFormatsSupplier = (XNumberFormatsSupplier)m_xDocument;
	XNumberFormatTypes xFormatTypes = 
           (XNumberFormatTypes)xFormatsSupplier.getNumberFormats();
	int nFormat = xFormatTypes.getStandardFormat( NumberFormat.DATE, new Locale() );

	XPropertySet xPropSet = (XPropertySet)xCell;
	xPropSet.setPropertyValue( "NumberFormat", new Any( nFormat ) );
	return true;
}

此代码片段说明了如何访问更高级别的对象,在本例中是 XDocument 对象来访问格式设置功能。单元格的“NumberFormat”属性需要格式的数字标识符,但这些标识符并不特别透明。使用 XNumberFormatsSupplierXNumberFormatTypesNumberFormat 枚举类,可以确定数字格式,如所示。

我们还看到了 XPropertySet 接口的使用。OpenOffice 系统中的许多对象都有属性,这些属性使用此接口进行访问。有问题的对象,在本例中是 xCell 对象,被转换为 XPropertySet 接口,并调用 setPropertyValue 方法。该方法接收属性名称和一个包含要设置的值的 Any 对象。

设置单元格属性的另一个示例是在一个设置单元格背景颜色的方法中显示的。

public void SetBackgroundColor( string strCellName, Color clr )
{
	XCell xCell = xGetXCellByName( strCellName );
	XPropertySet xPropSet = (XPropertySet)xCell;
	UInt32 unClr = (UInt32)clr.R << 16 | (UInt32)clr.G << 8 | (UInt32)clr.B;
	xPropSet.setPropertyValue( "CellBackColor", new Any( unClr ) );
}

我们现在可以将对这些方法的调用添加到我们的窗体按钮点击处理程序中:

private void btnRunOpenOfficeCalcTests_Click(object sender, EventArgs e)
{
	// Create a new Calc instance
	OOCalc.OOCalc ooCalc = new OOCalc.OOCalc();

	// First check if OpenOffice is installed
	if (ooCalc.bOpenOfficeInstalled == false)
		return;

		ooCalc.bStartOpenOfficeLoader();
		ooCalc.bCreateWorkbook();
		ooCalc.bSetValue( "A1", 1234 );
		ooCalc.bSetValue( "B1", 2468 );
		ooCalc.bSetFormula( "C1", "=A1+B1" );
		ooCalc.bSetText("A2", "Text entered here.");
		ooCalc.bSetDate( "A3", 2023, 12, 25 );
		ooCalc.bSetBackgroundColor( "A3", Color.Red );
}

Calc 电子表格中的结果是:

OO Calc Values Set

保存 OpenOffice Calc 文档

可以使用以下代码保存 Calc 文档:

public string strSaveWorkbook( string strFilePath = null )
{
	XStorable xStorable = (XStorable)m_xDocument;
	string strFileURL = string.Empty;

	// If we don't have a file path passed in, check if the file has
	// been previously saved. If so, use that file name
	if (string.IsNullOrEmpty( strFilePath ))
	{
		// If we already have a file path, use it
		if (xStorable.hasLocation())
		{
			strFileURL = xStorable.getLocation();
			strFilePath = strUrlToPath( strFileURL );
		}
		// Otherwise prompt for one
		else
		{
			SaveFileDialog sfd = new SaveFileDialog();
			sfd.Filter = "ODF Spreadsheet (*.odf)|*.odf";
			DialogResult dr = sfd.ShowDialog();
			if (dr == DialogResult.Cancel)
				return String.Empty;
			strFilePath = sfd.FileName;
		}
	}
	if ( string.IsNullOrEmpty( strFileURL ) )
		strFileURL = strPathToURL( strFilePath );
	
	// Save with no args for default file format
	xStorable.storeAsURL( strFileURL, new PropertyValue[0] );
	return strFilePath;
}

保存文档需要从文档中访问 XStorable 接口。此接口允许我们使用 hasLocation() 方法确定工作簿是否已保存到文件,并使用 getLocation() 方法检索文件的位置(如果已保存)。

在上面的代码中,如果没有传递文件路径,将查询当前文档以确定是否已设置路径。如果已设置,则使用该路径。否则,将使用 .NET SaveFileDialog 提示用户输入文件路径。无论哪种情况,现在都应该确定一个文件路径。

但是,由于 OpenOffice 是一个跨平台应用程序,它不使用 Windows 文件路径格式,而是使用更通用的 URL 格式。如果从 XStorable 接口检索到文件名,则需要将其转换回 Windows 文件路径才能在 Windows 中使用。反之亦然,如果我们希望将 Calc 工作表保存到磁盘,我们将不得不将 Windows 路径转换为 URL 格式。

private string strPathToURL( string strFilePath )
{
	string strURL = "file:///" + strFilePath.Replace( "\\", "/" );
	return strURL;
}
private string strUrlToPath( string strFileURL )
{
	string strFilePath = strFileURL;
	if (strFileURL.Substring( 0, 8 ) == "file:///")
		strFilePath = strFileURL.Substring( 8 );
	strFilePath = strFilePath.Replace( "/", "\\" );
	return strFilePath;
}

最后,现在我们有了正确的 URL 格式的文件路径,我们调用 storeAsURL 方法来保存文件。请注意,如果同名文件已存在,该函数将失败并抛出异常。另请注意,本地 OpenOffice 格式是 **Open Document Format**,文件扩展名为 **odf**。

以 Excel 格式保存

有时需要 Excel 兼容性。OpenOffice 具有打开和保存为 Excel 格式的能力。Excel 有两种常见格式:Excel 97“.xls”格式和后来的“.xlsx”格式。虽然 OpenOffice Calc 在一定程度上可以打开这两种格式,但它只能保存为 Excel 97 格式,扩展名为“.xls”。如果您希望与较新的“.xlsx”格式更好地兼容,LibreOffice 产品是更好的选择,并且上述代码中的大部分内容都可以在 LibreOffice 中使用。

这是保存为 Excel 97 格式的代码。假设文件已以 .odf 格式保存,则以下代码将其保存为 Excel 97 格式,文件名相同但扩展名为 .xls

public string strSaveWorkbookAsExcel()
{
	XStorable xStorable = (XStorable)m_xDocument;

	if (xStorable.hasLocation() == false)
		return string.Empty;

	string strFileURL = xStorable.getLocation(),
		strFilePath = strUrlToPath( strFileURL );

	PropertyValue[] apv = new PropertyValue[1];
	apv[0] = new PropertyValue();
	apv[0].Name = "FilterName";
	apv[0].Value = new Any( "MS Excel 97" );
	strFilePath = Path.ChangeExtension( strFilePath, "xls" );
	strFileURL = strPathToURL(strFilePath );
	xStorable.storeAsURL( strFileURL, apv );
	return strFilePath;
}

此函数演示了传递一个有效的 PropertyValue 数组,该数组配置为传递“FilterName”属性,并将其设置为“MS Excel 97”。可以在 OpenOffice Wiki Filter Options 页面上找到其他支持格式的列表。

关闭 OpenOffice Calc

完成后,我们可以让文档保持打开状态,或者关闭 OpenOffice。这是一个简单关闭它的函数:

public void CloseWorkbook()
{
	if (m_xDocument != null)
	{
		XComponent xComponent = (XComponent)m_xDocument;
		xComponent.dispose();
	}
}

XDocument 没有 dispose 方法,因此必须将对象转换为 XComponent 接口才能处理它。简单调用 dispose 方法即可关闭文档。

我们完整的 OpenOffice 测试按钮处理程序现在看起来像这样:

private void btnRunOpenOfficeCalcTests_Click(object sender, EventArgs e)
{
	// Create a new Calc instance
	OOCalc.OOCalc ooCalc = new OOCalc.OOCalc();
	if (ooCalc.bOpenOfficeInstalled == false)
		return;
	ooCalc.bStartOpenOfficeLoader();
	ooCalc.bCreateWorkbook();
	ooCalc.bSetValue( "A1", 1234 );
	ooCalc.bSetValue( "B1", 2468 );
	ooCalc.bSetFormula( "C1", "=A1+B1" );
	ooCalc.bSetText("A2", "Text entered here.");
	ooCalc.bSetDate( "A3", 2023, 12, 25 );
	ooCalc.bSetBackgroundColor( "A3", Color.Red );
	string strFilePath = ooCalc.strSaveWorkbook();
	ooCalc.bCloseWorkbook();

	// Reopen it and save in Excel format
	ooCalc.bOpenWorkbook( strFilePath );
	strFilePath = ooCalc.strSaveWorkbookAsExcel();
	Console.WriteLine( "Saved as: " + strFilePath );

	ooCalc.bCloseWorkbook();
}

您的下一个挑战

SDK 中包含一个 examples 文件夹(我的位于 C:\Program Files (x86)\OpenOffice 4\sdk\examples\CLI\CSharp\Spreadsheet)。尝试添加几个按钮并将此代码放入您的项目中。示例代码提供了相当广泛的 Calc 示例,包括表格、图表、格式设置等,这将对您进一步学习 Calc 大有帮助。

总结

我发现以编程方式与 OpenOffice 交互的学习曲线相当艰辛。熟悉在线文档至关重要。此外,学习从 Java 和 C++ 代码示例中获取见解至少能帮助我找到解决方案的方向。希望本教程能为您提供一个良好的基础。

虽然我以 OpenOffice 开始我的旅程,但我很快就转向了 LibreOffice,它拥有几乎相同的 API,但开发更完善。例如,它支持导出较新的 Excel .xlsx 文件格式。我希望将来更新本教程并发布 LibreOffice 代码。

历史

  • 2023 年 4 月 3 日:初始版本
© . All rights reserved.