Excel的ExcelDNA C#/尝试复制彭博BDH()的行为(在networking请求后写入数组)

我想复制彭博BDH的行为。

BDH发出Web请求并写入数组(但不返回数组样式)。 在此Web请求期间,该函数返回“#N / A请求”。 当Web请求完成时,BDH()函数将数组结果写入工作表。

例如,在ExcelDNA中,我成功地在工作表中写入了一个线程。

结果如果你在DNA文件中使用下面的代码,结果如下

= WriteArray(2; 2)

将会

第1行> #N/A Requesting Data (0,1)

第2行> (1,0) (1,1)

最后一个问题是用值replace#N/A Requesting Data并复制公式。 当您取消注释//xlActiveCellType.InvokeMember(“FormF1R1C1Local”)时,您接近结果但您没有正确的行为

文件.dna

  <DnaLibrary Language="CS" RuntimeVersion="v4.0"> <![CDATA[ using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using ExcelDna.Integration; public static class WriteForXL { public static object[,] MakeArray(int rows, int columns) { if (rows == 0 && columns == 0) { rows = 1; columns = 1; } object[,] result = new string[rows, columns]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { result[i, j] = string.Format("({0},{1})", i, j); } } return result; } public static object WriteArray(int rows, int columns) { if (ExcelDnaUtil.IsInFunctionWizard()) return "Waiting for click on wizard ok button to calculate."; object[,] result = MakeArray(rows, columns); var xlApp = ExcelDnaUtil.Application; Type xlAppType = xlApp.GetType(); object caller = xlAppType.InvokeMember("ActiveCell", BindingFlags.GetProperty, null, xlApp, null); object formula = xlAppType.InvokeMember("FormulaR1C1Local", BindingFlags.GetProperty, null, caller, null); ObjectForThread q = new ObjectForThread() { xlRef = caller, value = result, FormulaR1C1Local = formula }; Thread t = new Thread(WriteFromThread); t.Start(q); return "#N/A Requesting Data"; } private static void WriteFromThread(Object o) { ObjectForThread q = (ObjectForThread) o; Type xlActiveCellType = q.xlRef.GetType(); try { for (int i = 0; i < q.value.GetLength(0); i++) { for (int j = 0; j < q.value.GetLength(1); j++) { if (i == 0 && j == 0) continue; Object cellBelow = xlActiveCellType.InvokeMember("Offset", BindingFlags.GetProperty, null, q.xlRef, new object[] { i, j }); xlActiveCellType.InvokeMember("Value", BindingFlags.SetProperty, null, cellBelow, new[] { Type.Missing, q.value[i, j] }); } } } catch(Exception e) { } finally { //xlActiveCellType.InvokeMember("Value", BindingFlags.SetProperty, null, q.xlRef, new[] { Type.Missing, q.value[0, 0] }); //xlActiveCellType.InvokeMember("FormulaR1C1Local", BindingFlags.SetProperty, null, q.xlRef, new [] { q.FormulaR1C1Local }); } } public class ObjectForThread { public object xlRef { get; set; } public object[,] value { get; set; } public object FormulaR1C1Local { get; set; } } } ]]> </DnaLibrary> 

@ To Govert

BDH已经成为金融行业的一个标准。 人们不知道如何操作数组(甚至是Ctrl + Shift + Enter)。

BDH是使彭博如此受欢迎的function(对路透社不利)。

但是我会考虑使用你的方法或RTD。

感谢您在Excel DNA中的所有工作

我推测你已经尝试了Excel-DNA ArrayResizer示例,它仔细避免了许多您遇到的问题。 我想了解你所看到的数组公式写法的缺点。

现在,关于你的function:

首先,您不能安全地将“调用者”范围COM对象传递给另一个线程,而是将一个string传递给该地址,并从另一个线程(使用对工作线程上的ExcelDnaUtil.Application的调用)获取COM对象。 不过大多数时候你会幸运的。 更好的方法是从工作者线程获取Excel以在主线程上运行macros – 通过调用Application.Run。 Excel-DNA ArrayResizer示例显示了如何完成此操作。

其次,你几乎肯定不想要ActiveCell,而是Application.Caller。 ActiveCell很可能与公式所在的单元格无关。

接下来,每当您再次设置公式时,Excel将重新计算您的函数 – 因此,当您在finally子句中启用公式集时,将使您处于无限循环状态。 您不能为单元格设置值和公式 – 如果单元格有公式,则Excel将使用公式计算值。 如果您设置值,公式将被删除。 目前还不清楚你想要在[0,0]单元中实际留下什么 – IIRC彭博社用一种方式修改了公式,使其记住了写入的范围有多大。 你可以尝试添加一些参数给你的函数,告诉你的函数是重新计算还是返回一个实际值作为结果。

最后,你可能想重新考虑彭博BDH函数是否是你想要做的一个很好的例子。 它打破了工作表的依赖性计算,这既影响了性能,也影响了电子表格模型的一致性。

我的问题是:

  • 写dynamic数组

  • 数据通过web服务asynchronous检索

在和Govert讨论后,我select了一个数组作为结果,而不是复制Bloomberg函数(写入一个数组,但是返回一个值)。

最后,为了解决我的问题,我使用了http://excel-dna.net/2011/01/30/resizing-excel-udf-result-arrays/并重新设置&#x4E86;resize()函数。

此代码不是RTD。

下面的代码在.dna文件中工作

 <DnaLibrary RuntimeVersion="v4.0" Language="C#"> <![CDATA[ using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.ComponentModel; using ExcelDna.Integration; public static class ResizeTest { public static object[,] MakeArray(int rows, int columns) { object[,] result = new string[rows, columns]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { result[i,j] = string.Format("({0},{1})", i, j); } } return result; } public static object MakeArrayAndResize() { // Call Resize via Excel - so if the Resize add-in is not part of this code, it should still work. return XlCall.Excel(XlCall.xlUDF, "Resize", null); } } public class Resizer { static Queue<ExcelReference> ResizeJobs = new Queue<ExcelReference>(); static Dictionary<string, object> JobIsDone = new Dictionary<string, object>(); // This function will run in the UDF context. // Needs extra protection to allow multithreaded use. public static object Resize(object args) { ExcelReference caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference; if (caller == null) return ExcelError.ExcelErrorNA; if (!JobIsDone.ContainsKey(GetHashcode(caller))) { BackgroundWorker(caller); return ExcelError.ExcelErrorNA; } else { // Size is already OK - just return result object[,] array = (object[,])JobIsDone[GetHashcode(caller)]; JobIsDone.Remove(GetHashcode(caller)); return array; } } /// <summary> /// Simulate WebServiceRequest /// </summary> /// <param name="caller"></param> /// <param name="rows"></param> /// <param name="columns"></param> static void BackgroundWorker(ExcelReference caller) { BackgroundWorker bw = new BackgroundWorker(); bw.DoWork += (sender, args) => { Thread.Sleep(3000); }; bw.RunWorkerCompleted += (sender, args) => { // La requete Random r = new Random(); object[,] array = ResizeTest.MakeArray(r.Next(10), r.Next(10)); JobIsDone[GetHashcode(caller)] = array; int rows = array.GetLength(0); int columns = array.GetLength(1); EnqueueResize(caller, rows, columns); AsyncRunMacro("DoResizing"); }; bw.RunWorkerAsync(); } static string GetHashcode(ExcelReference caller) { return caller.SheetId + ":L" + caller.RowFirst + "C" + caller.ColumnFirst; } static void EnqueueResize(ExcelReference caller, int rows, int columns) { ExcelReference target = new ExcelReference(caller.RowFirst, caller.RowFirst + rows - 1, caller.ColumnFirst, caller.ColumnFirst + columns - 1, caller.SheetId); ResizeJobs.Enqueue(target); } public static void DoResizing() { while (ResizeJobs.Count > 0) { DoResize(ResizeJobs.Dequeue()); } } static void DoResize(ExcelReference target) { try { // Get the current state for reset later XlCall.Excel(XlCall.xlcEcho, false); // Get the formula in the first cell of the target string formula = (string)XlCall.Excel(XlCall.xlfGetCell, 41, target); ExcelReference firstCell = new ExcelReference(target.RowFirst, target.RowFirst, target.ColumnFirst, target.ColumnFirst, target.SheetId); bool isFormulaArray = (bool)XlCall.Excel(XlCall.xlfGetCell, 49, target); if (isFormulaArray) { object oldSelectionOnActiveSheet = XlCall.Excel(XlCall.xlfSelection); object oldActiveCell = XlCall.Excel(XlCall.xlfActiveCell); // Remember old selection and select the first cell of the target string firstCellSheet = (string)XlCall.Excel(XlCall.xlSheetNm, firstCell); XlCall.Excel(XlCall.xlcWorkbookSelect, new object[] {firstCellSheet}); object oldSelectionOnArraySheet = XlCall.Excel(XlCall.xlfSelection); XlCall.Excel(XlCall.xlcFormulaGoto, firstCell); // Extend the selection to the whole array and clear XlCall.Excel(XlCall.xlcSelectSpecial, 6); ExcelReference oldArray = (ExcelReference)XlCall.Excel(XlCall.xlfSelection); oldArray.SetValue(ExcelEmpty.Value); XlCall.Excel(XlCall.xlcSelect, oldSelectionOnArraySheet); XlCall.Excel(XlCall.xlcFormulaGoto, oldSelectionOnActiveSheet); } // Get the formula and convert to R1C1 mode bool isR1C1Mode = (bool)XlCall.Excel(XlCall.xlfGetWorkspace, 4); string formulaR1C1 = formula; if (!isR1C1Mode) { // Set the formula into the whole target formulaR1C1 = (string)XlCall.Excel(XlCall.xlfFormulaConvert, formula, true, false, ExcelMissing.Value, firstCell); } // Must be R1C1-style references object ignoredResult; XlCall.XlReturn retval = XlCall.TryExcel(XlCall.xlcFormulaArray, out ignoredResult, formulaR1C1, target); if (retval != XlCall.XlReturn.XlReturnSuccess) { // TODO: Consider what to do now!? // Might have failed due to array in the way. firstCell.SetValue("'" + formula); } } finally { XlCall.Excel(XlCall.xlcEcho, true); } } // Most of this from the newsgroup: http://groups.google.com/group/exceldna/browse_thread/thread/a72c9b9f49523fc9/4577cd6840c7f195 private static readonly TimeSpan BackoffTime = TimeSpan.FromSeconds(1); static void AsyncRunMacro(string macroName) { // Do this on a new thread.... Thread newThread = new Thread( delegate () { while(true) { try { RunMacro(macroName); break; } catch(COMException cex) { if(IsRetry(cex)) { Thread.Sleep(BackoffTime); continue; } // TODO: Handle unexpected error return; } catch(Exception ex) { // TODO: Handle unexpected error return; } } }); newThread.Start(); } static void RunMacro(string macroName) { object xlApp = null; try { xlApp = ExcelDnaUtil.Application; xlApp.GetType().InvokeMember("Run", BindingFlags.InvokeMethod, null, xlApp, new object[] {macroName}); } catch (TargetInvocationException tie) { throw tie.InnerException; } finally { Marshal.ReleaseComObject(xlApp); } } const uint RPC_E_SERVERCALL_RETRYLATER = 0x8001010A; const uint VBA_E_IGNORE = 0x800AC472; static bool IsRetry(COMException e) { uint errorCode = (uint)e.ErrorCode; switch(errorCode) { case RPC_E_SERVERCALL_RETRYLATER: case VBA_E_IGNORE: return true; default: return false; } } } ]]> </DnaLibrary> 

我认为你需要将这个请求作为一个RTD服务器来实现。 普通用户定义的函数不会asynchronous更新。
然后,您可以通过用户定义的函数来隐藏RTD服务器的调用,这可以通过Excel-DNA完成。

所以最后你使用Array公式,对吧? 正如你所说,用户不熟悉数组公式,他们不知道ctrl + shift + enter。 我认为arrays公式对他们来说是个大问题。

对我而言,我有同样的问题。 我正在试图为它build立一个原型。 请参阅https://github.com/kchen0723/ExcelAsync.git