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

通过 IStream 接口从剪贴板获取 Excel Range 对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (6投票s)

2011 年 1 月 25 日

CPOL

3分钟阅读

viewsIcon

39405

downloadIcon

673

本文演示了如何使用 CF_LINKSOURCE 剪贴板格式从剪贴板获取 Excel Range 对象。

引言

当您将选定的单元格从 Excel 复制到剪贴板时,它会包含多种数据格式。 如果您只需要粘贴数据,那么即使 CF_CSV 也可以满足要求。 但是,如果您想要访问表示复制单元格的 Range 对象该怎么办? 使用 CF_LINKSOURCE

背景

在我们公司,我们目前正在开发一个名为 Converter 的项目,该项目将数据从 Excel 导入到我们的应用程序中。 用户必须将 Excel 中的一些单元格范围拖动或复制/粘贴到 Converter 中,并设置一个数据传输方案(从哪个单元格传输到哪个目标数据;第一行数据是否包含字段名称等)。 最后,用户得到一个可以用来传输来自任意数量 Excel 工作簿的数据的方案(当然,工作簿必须具有相同的布局)。 创建这样的方案需要一些关于 Range 布局、单元格名称等的信息,因此您需要 Range 对象来获取它。 本文介绍了我们如何通过使用 CF_LINKSOURCE 剪贴板格式中的 IStream 接口来做到这一点。

主要算法

主要算法与示例代码中的 GetRange 方法一样简单

public static Range GetRange(IDataObject dataObject)
{ 
    IStream iStream = IStreamFromDataObject(dataObject); 
    IMoniker compositeMoniker = IMonikerFromIStream(iStream); 
    return RangeFromCompositeMoniker(compositeMoniker); 
}

不要与 IDataObject 混淆 - 它是 System.Runtime.InteropServices.ComTypes.IDataObject 而不是 System.Windows.Forms.IDataObject

内部观察

获取 IMoniker

IStreamFromDataObject 很简单,我将跳过它。 要从 IStream 获取 IMoniker,我们需要来自 ole32.dll 的 P/Invoked 函数 OleLoadFromStream

[DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern HRESULT OleLoadFromStream( 
                        IStream pStm, 
                        [In] ref Guid riid, 
                        [MarshalAs(UnmanagedType.IUnknown)] out object ppvObj);

HRESULT 是一个从 pinvoke.net 获取的结构。

您将上一步的流传递给 OleLoadFromStream 并...得到一个错误! 需要一段时间才能弄清楚您需要重置流! 好的,让我们这样做。

iStream.Seek(0, 0, IntPtr.Zero);

瞧! 我们从 OleLoadFromStream 获取了可以成功转换为 IMoniker 的对象。

获取 Range

如果您查看 moniker 的 CLSID,您会发现它是一个复合 moniker。 如果我们生活在一个完美的世界里,moniker.BindToObject() 会给我们 Range 对象。 但在现实生活中,来自 Microsoft Office 的 moniker 只能绑定到文件对象(Excel 中的 Workbook),因此我们需要拆分复合 moniker 并为 Excel 做一些工作。

private static List<IMoniker> SplitCompositeMoniker(IMoniker compositeMoniker)
{
    if (compositeMoniker == null)
        throw new ArgumentNullException("compositeMoniker", 
                  "compositeMoniker is null.");

    List<IMoniker> monikerList = new List<IMoniker>();

    IEnumMoniker enumMoniker;
    compositeMoniker.Enum(true, out enumMoniker);
    if (enumMoniker != null)
    {
        IMoniker[] monikerArray = new IMoniker[1];
        IntPtr fetched = new IntPtr();
        HRESULT res;
        while (res = enumMoniker.Next(1, monikerArray, fetched))
        {
            monikerList.Add(monikerArray[0]);
        }
        return monikerList;
    }
    else
        throw new ApplicationException("IMoniker is not composite");
}

现在我们得到了包含文件 moniker 和项目 moniker 的 List。 要绑定到 Workbook,我们只需要调用 IMoniker.BindToObject

IBindCtx bindctx;
if (!ole32.CreateBindCtx(0, out bindctx) || bindctx == null)
    throw new ApplicationException("Can't create bindctx");
object obj;
Guid workbookGuid = Marshal.GenerateGuidForType(typeof(Workbook));
monikers[0].BindToObject(bindctx, null, ref workbookGuid, out obj);
Workbook workbook = obj as Workbook;

但是调用项目 moniker 的 BindToObject 会产生一个错误(实际上,我们可以使用 IUnknown 的 IID 成功调用 BindToObject,但返回的对象实际上将是 Workbook 对象,可悲但真实。) 游戏结束了吗? 没那么快。 我们仍然可以使用 IMoniker.GetDisplayName() 从项目 moniker 获取显示名称。 对于 Excel Range,它将类似于“!blahblahblah!R1C1:R3C3”,其中 blahblahblah 是工作表名称,R1C1:R3C3 标识工作表内的范围。 我编写了一个辅助类来解析 DisplayName 并从 Workbook 对象获取 Range。 唯一有趣的时刻是

  1. 用户可以复制整行或整列,分别标识为“Rx:Ry”和“Cx:Cy”。
  2. 您必须将 R1C1 名称转换为当前的 Excel 引用样式(主要是 A1)才能获取 Range。

辅助工具可以按如下方式使用

ExcelItemMonikerHelper helper = new ExcelItemMonikerHelper(monikers[1], bindctx);
Range range = helper.GetRange(workbook);

所以最后,我们获得了 Range,就像项目 moniker 的 BindToObject 真正起作用一样 :)

© . All rights reserved.