Excel数据导入转换器项目解析

在当前的项目开发中,团队正在致力于一个名为Converter的项目,该项目的核心功能是将Excel中的数据导入到应用程序中。用户需要从Excel中选择或复制/粘贴特定的单元格范围到Converter,然后设置一个数据传输方案。这个方案定义了数据从Excel的哪个单元格传输到应用程序的哪个目标位置,以及是否包含字段名等信息。最终,用户将得到一个可以用于从任意数量的Excel工作簿中传输数据的方案(当然,这些工作簿必须具有相同的布局)。创建这样的方案需要了解单元格范围布局、单元格名称等信息,因此需要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。

内部实现

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。

如果查看moniker的CLSID,将看到它是一个复合moniker。如果生活在一个完美的世界里,moniker.BindToObject()将给Range对象。但在现实生活中,来自Microsoft Office的moniker只能绑定到文件对象(在Excel的情况下是Workbook),所以需要分割复合moniker并做一些工作。

private static List SplitCompositeMoniker(IMoniker compositeMoniker) { if (compositeMoniker == null) throw new ArgumentNullException("compositeMoniker", "compositeMoniker is null."); List monikerList = new List(); 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)) == 0) { monikerList.Add(monikerArray[0]); } return monikerList; } else throw new ApplicationException("IMoniker is not composite"); }

现在得到了一个包含文件moniker和项目moniker的列表。要绑定到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会出错(实际上,可以成功调用BindToObject与IUnknown的IID,但返回的对象实际上是Workbook对象,这是令人悲伤但真实的事实)。游戏结束了吗?不是那么快。仍然可以从项目moniker的IMoniker.GetDisplayName()获取显示名称。对于Excel Range,它将是类似于"!blahblahblah!R1C1:R3C3"的东西,其中blahblahblah是工作表名称,R1C1:R3C3标识工作表内的区域。写了一个辅助类来解析DisplayName并从Workbook对象中获取Range。唯一有趣的事情是:

  • 用户可以复制整个行或整个列,分别被识别为"Rx:Ry"和"Cx:Cy"。
  • 必须将R1C1名称转换为当前Excel引用样式(大多数情况下是A1)以获取Range。
ExcelItemMonikerHelper helper = new ExcelItemMonikerHelper(monikers[1], bindctx); Range range = helper.GetRange(workbook);
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485