我创建了一个 Excel 功能区插件,它需要在 Excel 会话之间保留用户选择。使用自定义 XML 部件似乎是最好的选择。但是,如果没有获得 COMExceptions,我将无法使其正常工作。
MSDN 文档不是很有用(http://msdn.microsoft.com/en-us/library/bb608612.aspx)。有人可以给我一个在 Excel 功能区插件中进行这项工作的例子吗?
我知道三种不同的方法:
自定义 XML 部件 对于应用程序级别的插件,这是我存储任何需要保存在已保存的 xls 文件中的任何应用程序数据的首选方法,而用户永远不会看到这些数据。
http://msdn.microsoft.com/en-us/library/bb608612.aspx
缓存数据孤岛 这仅适用于文档级插件。如果您尝试在应用程序级插件中使用它,您将遇到异常。
http://blogs.msdn.com/b/eric_carter/archive/2004/04/23/119294.aspx
隐藏的工作表 使用 VSTO,您可以创建用户看不到的不可见工作表。这很好用,但会导致很多笨拙的编码来转换您的数据以适合 Excel 工作表。
更新(2014 年): 所以最终使用自定义 XML 部件变成了性能问题,所以我的应用程序必须改回使用隐藏的工作表。显然,一旦 XML 达到一定大小,Excel 就会变得非常缓慢。我的加载项使自定义部件达到数千个节点,并且 XML 增长得越大,Excel 中的所有内容都变得越慢。例如,简单地单击任何单元格会导致非常明显的延迟。
如果您想将来自应用程序级插件的任何类型的元数据与特定文档一起存储,您可以将数据序列化为某种字符串(base64、xml 等),并将其保存在“非常隐藏”的工作表中。可见性设置为“非常隐藏”的工作表只能通过编程 API 访问,因此即使用户发现隐藏的工作表,他们仍然无法访问它,甚至无法知道它的存在。
// create sheet for this save
workbook.Sheets.Add();
newSettingsWorksheet = workbook.ActiveSheet;
newSettingsWorksheet.Name = hiddenSheetName;
newSettingsWorksheet.Visible = Excel.XlSheetVisibility.xlSheetVeryHidden;
需要注意的一件重要事情是,如果您存储的字符串长度超过 32767 个字符(一个单元格中的最大字符数),那么您必须将其切成块并将其分布在多个单元格中。
关于您遇到的 COM 异常,您应该知道,如果 Excel 正忙于处理另一个 COM 对象(例如工作表、单元格或属于 Excel 的任何东西),则 Excel 可以随时为任何触及 COM 对象(例如工作表、单元格或任何属于 Excel)的请求引发 COM 异常请求(例如用户正在输入,它正在重新计算公式)。您可以预期的例外情况是:
HRESULT:0x800AC472(忽略)
HRESULT:0x8000101A(稍后重试)
我猜 Excel 应用程序开发人员会这样做,因此加载项不会使 Excel 本身看起来很糟糕/无响应。
您应该使用注册表来存储一些信息,例如用户偏好和历史记录,这些信息在应用程序关闭后需要保留或需要在多个实例之间共享。
用户的配置单元 (HKEY_CURRENT_USER) 永远不会出现权限问题。只需参考 .NET 注册表类:http: //msdn.microsoft.com/en-us/library/microsoft.win32.registry.aspx
考虑使用自定义属性。每个 Excel 工作表都在后台维护程序员易于使用的属性列表。例如,我使用自定义属性来“记住”功能区下拉列表中的哪些项目是为特定工作表选择的;当工作表更改时,拉起该工作表的自定义属性,以找出上次活动时选择了哪些下拉项目。
每个工作表和文档都保留自定义属性。
using System;
using Microsoft.Office.Interop.Excel;
public partial class CustPropExample
{
/// <summary>
/// delete and then store the custom property by passed key and value
/// </summary>
bool bExcelCustProp_Replace(Worksheet wkSheet,
string custPropKey,
string custPropVal)
{
if (!ExcelCustProp_DeleteByKey(wkSheet, custPropKey))
return (false);
if (!ExcelCustProp_Add(wkSheet, custPropKey, custPropVal))
return (false);
return (true);
}
/// <summary>
/// return the custom property value of passed key
/// </summary>
string ExcelCustProp_Get(Worksheet wkSheet,
string key)
{
try
{
for (int i = 1; i <= wkSheet.CustomProperties.Count; i++) // NOTE: 1-based !!!!!!!!
{
if (wkSheet.CustomProperties.get_Item(i).Name == key)
return (wkSheet.CustomProperties.get_Item(i).Value);
}
}
catch (Exception ex)
{
ShowErrorMsg("Error with getting cust prop; key [" + key + "], exc: " + ex.Message, false);
}
return (string.Empty);
}
/// <summary>
/// add cust prop
/// </summary>
bool ExcelCustProp_Add(Worksheet wkSheet,
string key,
string custPropVal)
{
try
{
wkSheet.CustomProperties.Add(key, custPropVal);
}
catch (Exception ex)
{
return(ShowErrorMsg("Error in adding cust prop: " + ex.Message, false));
}
return (true);
}
/// <summary>
/// if passed key exists, delete it
/// </summary>
bool ExcelCustProp_DeleteByKey(Worksheet wkSheet,
string key)
{
try
{
for (int i = 1; i <= wkSheet.CustomProperties.Count; i++) // NOTE: 1-based !!!!!!!!
{
if (wkSheet.CustomProperties.Item[i].Name == key)
{
wkSheet.CustomProperties.Item[i].Delete();
break;
}
}
}
catch (Exception ex)
{
return(ShowErrorMsg("Error deleting cust prop (key='" + key + "') - " + ex.Message, false));
}
return (true);
}
/// <summary>
/// stub for error handling
/// </summary>
bool ShowErrorMsg(string msg,
bool retval)
{
System.Windows.Forms.MessageBox.Show(msg);
return (retval);
}
}