2015-06-17 29 views
2

我正在使用Excel-DNA在Excel中开发一些UDF。从Excel传递到我的UDF的一个参数是一个范围。使用特定范围时,UDF可正常工作,如“A1:C50”。下面是我的函数定义的一个示例:如何针对UsedRange剪辑ExcelReference?

[ExcelCommand()] 
public static object CalcSMA(object[,] range, int num_points) { 
    ... 
} 

不过,我得到时整列范围传递,如“走出内存”错误“A:C”。我可以通过设置参数属性AllowReference = TRUE避免错误和更改参数类型为对象,如下面的例子:

[ExcelCommand()] 
public static object CalcSMA([ExcelArgument("Range", AllowReference=true)]object range, int num_points) { 
    ExcelReference xref = (ExcelReference)range; 
    ... 
} 

但现在我坚持想多行如何实际需要的UDF。我可以尝试迭代工作表中的所有行,但这非常低效。有没有办法将ExcelReference(xref)与使用的范围进行剪辑?我想避免使函数volatile(IsMacroType = true),但如果需要的话会这样做。

回答

2

基于查尔斯和霍弗特的建议,我最终实现以下:

public class UsedRangeCache 
{ 
    protected static Dictionary<IntPtr, ExcelReference> _usedRanges = new Dictionary<IntPtr, ExcelReference>(); 
    protected static Application _app; 

    /// <summary> 
    /// Call this method when the XLL is initialized 
    /// </summary> 
    public static void Initialize(Application app) 
    { 
     _app = app; 
     for (int i = 0; i < app.Workbooks.Count; i++) 
     { 
      app_WorkbookOpen(app.Workbooks[i + 1]); 
     } 
     app.WorkbookOpen += app_WorkbookOpen; 
     app.WorkbookBeforeClose += app_WorkbookBeforeClose; 
     app.AfterCalculate += app_AfterCalculate; 
    } 

    // Refresh references 
    static void app_AfterCalculate() 
    { 
     for (int i = 0; i < _app.Workbooks.Count; i++) 
     { 
      UpdateCache(_app.Workbooks[i + 1]); 
     } 
    } 

    // Remove references 
    static void app_WorkbookBeforeClose(Workbook book, ref bool Cancel) 
    { 
     for (int i = 0; i < book.Worksheets.Count; i++) 
     { 
      Worksheet sheet = book.Worksheets[i + 1] as Worksheet; 
      if (sheet != null) 
      { 
       ExcelReference xref = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, sheet.Name); 
       if (_usedRanges.ContainsKey(xref.SheetId)) 
       { 
        _usedRanges.Remove(xref.SheetId); 
       } 
      } 
     } 
    } 

    // Create references 
    static void app_WorkbookOpen(Workbook book) 
    { 
     UpdateCache(book); 
    } 

    // Update cache 
    private static void UpdateCache(Workbook book) 
    { 
     for (int i = 0; i < book.Worksheets.Count; i++) 
     { 
      Worksheet sheet = book.Worksheets[i + 1] as Worksheet; 
      if (sheet != null) 
      { 
       ExcelReference xref = (ExcelReference)XlCall.Excel(XlCall.xlSheetId, sheet.Name); 
       ExcelReference xused = new ExcelReference(
        sheet.UsedRange.Row, 
        sheet.UsedRange.Row + sheet.UsedRange.Rows.Count, 
        sheet.UsedRange.Column, 
        sheet.UsedRange.Column + sheet.UsedRange.Columns.Count, 
        xref.SheetId); 

       if (_usedRanges.ContainsKey(xref.SheetId)) 
       { 
        _usedRanges.Remove(xref.SheetId); 
       } 
       _usedRanges.Add(xref.SheetId, xused); 
      } 
     } 
    } 


    /// <summary> 
    /// Get used range 
    /// </summary> 
    public static ExcelReference GetUsedRange(ExcelReference xref) 
    { 
     ExcelReference ret = null; 
     _usedRanges.TryGetValue(xref.SheetId, out ret); 
     return ret; 
    } 
} 
2

在VBA(或COM)中,可以将Range参数与Range参数的Parent的UsedRange相交。但是在XLL中,由于XLL接口没有为工作表提供UsedRange方法,因此获取使用的范围并不简单。所以你必须使用COM接口(这在XLL UDF中是有问题的)。我构建了一个使用AfterCalculate事件来缓存每个工作表使用范围的例程。

还有就是在这里做这个 https://fastexcel.wordpress.com/2014/09/26/getting-used-range-in-an-xll-udf-multi-threading-and-com/

注的方式进行一些讨论,如果你愿意让你的UDF,你可以使用GETDOCUMENT(10)XLL API单线程宏观型UDF。但这种痛苦可能并不值得。

+1

也看到https://groups.google.com/d/topic/exceldna/GXs6xbnSTKY/discussion - 使用UsedRange在Excel-DNA中更容易一些,因为您可以通过调用“ExcelDnaUtil.Application”来获得COM对象模型应用程序对象。 – Govert

+0

感谢您的快速响应。如果您按照您的建议使用了缓存后的AfterCalculate事件,那么在打开工作簿之后,是否还需要在第一次计算期间迭代行? –

+0

我陷阱工作簿打开事件最初填充缓存。 –