6

我正在为 excel 创建一个 VSTO 功能区插件,并将一些工作簿状态信息存储在我的应用程序中,用于更新已启用的可视按钮。考虑到可能有多个工作簿,我将此状态对象存储在 ThisAddIn 类的字典中。我的问题是我不知道如何为工作簿获取唯一的哈希/密钥/Guid,因为我得到的只是一个不断更改哈希的 COM 包装器。很公平,我完全理解这一点。

我使用了很长时间的一种解决方案是创建一个 guid 并将其存储在工作簿的 CustomDocumentProperties 中,并基于它作为键映射状态。这至少有效,但是如果我创建工作簿的副本并在同一个应用程序实例中打开它并且现在有多个具有相同 guid 的工作簿,它将失败。

我现在有了一个想法,我想我可以在 Workbook_Open 事件上刷​​新这个 Guid。但这似乎仍然是一个狡猾的解决方案。

我在这里找到的第二个解决方案:http: //social.msdn.microsoft.com/Forums/en-US/vsto/thread/04efa74d-83bd-434d-ab07-36742fd8410e/

所以我使用了那些人的代码并创建了这个:

public static class WorkbookExtensions
{
    public static IntPtr GetHashery(this msExcel.Workbook workbook)
    {
        IntPtr punk = IntPtr.Zero;
        try
        {
            punk = Marshal.GetIUnknownForObject(workbook);
            return punk;
        }
        finally
        {
            //Release to decrease ref count
            Marshal.Release(punk);
        }
    }
}

它在几分钟内运行良好,直到它在访问 Application.ActiveWorkbook 时开始给我一个臭名昭著的错误“无法使用已与其底层 RCW 分离的 COM 对象”。

这是引用 Workbook COM 对象的安全方式吗?如果我有两个功能区应用程序都使用此方法来获取单个工作簿 GUID,该怎么办?如果其中一个应用程序在我的状态对象上运行垃圾收集器,它调用终结器来调用 Marshal.FinalReleaseComObject(workbook),该怎么办?有什么方法可以让我获得工作簿的 Ref Count,这样我就不会在其他功能区应用程序完成之前调用 FinalRelease?清理 VSTO 中的 Workbook COM 对象以保持与这些其他应用程序公平竞争的最佳实践是什么?

当然,我不是第一个想要根据工作簿状态启用按钮的人,其他人是如何做到这一点的?我在 Stack Overflow 上查看了其他几篇文章,但没有一篇对 Workbook Guid 解决方案有帮助。

我正在使用功能区设计器,并连接到工作簿加载和停用事件。

在此先感谢,希望我包括了所有细节。

4

2 回答 2

6

我最终通过简单地将 IntPtr 转换为 long 来解决这个问题,然后处理 IntPtr 不会影响我。我不需要保留 IntPtr,因为我真正需要的只是工作簿的独特之处。

以下代码允许我存储特定于工作簿的状态信息,因此我可以根据自定义对象工作簿状态更新功能区中按钮的视觉状态。您可以在自定义 WorkbookState 类中存储您想要的任何信息,但通常它是您不希望保留在电子表格本身中的特定于会话的信息。

单独的工作簿扩展:

public static class WorkbookExtensions
{
    public static long GetHashery(this msExcel.Workbook workbook)
    {
        if (workbook == null)
        {
            throw new ArgumentNullException("workbook");
        }

        IntPtr pUnknown = IntPtr.Zero;
        try
        {
            pUnknown = Marshal.GetIUnknownForObject(workbook);
            return pUnknown.ToInt64();
        }
        finally
        {
            // GetIUnknownForObject causes AddRef.
            if (pUnknown != IntPtr.Zero)
            {
                Marshal.Release(pUnknown);
            }
        }
    }
}

然后在我的 VSTO/ExcelDna ThisAddIn 类中,我使用上述方法存储所有工作簿状态的映射,以查找唯一的工作簿哈希键:

private Dictionary<long, WorkbookState> _workbookStates = new Dictionary<long, WorkbookState>();
public WorkbookState WorkbookState
{
    get
    {
        long hash = Application.ActiveWorkbook.GetHashery();
        WorkbookState state;
        if (!_workbookStates.TryGetValue(hash, out state))
        {
            state = _workbookStates[hash] = new WorkbookState();
        }
        return state;
    }
}

当然,现在我可以从功能区应用程序的任何位置访问我的 WorkbookState,只需调用ThisAddIn.WorkbookState

于 2014-02-06T16:32:29.687 回答
0

您也可以使用 workbook.Application.Hwnd 但这只会在您使用 Excel 2013/2016 时对您有所帮助,它会为每个工作簿打开新窗口。

Excel 2010 在同一窗口下打开所有工作簿。

于 2019-01-02T13:27:06.607 回答