Home » excel » .net – Excel ExcelDNA C# / Try to copy Bloomberg BDH() behavior (writing Array after a web request)

.net – Excel ExcelDNA C# / Try to copy Bloomberg BDH() behavior (writing Array after a web request)

Posted by: admin March 7, 2020 Leave a comment

Questions:

I want to copy Bloomberg BDH behavior.

BDH makes a web request and write an array (but doesn’t return an array style). During this web request, the function returns “#N/A Requesting”.
When the web request finished, the BDH() function writes the array result in the worksheet.

For example, in ExcelDNA, I succeed to write in the worksheet with a thread.

The result if you use the code below in a DNA file, the result of

=WriteArray(2;2)

will be

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

Line 2 > (1,0) (1,1)

The last issue is to replace #N/A Requesting Data with the value and copy the formula.
When you uncomment //xlActiveCellType.InvokeMember(“FormulaR1C1Local”, you are near the result but you don’t have the right behavior

File .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 has become a standard in finance industry. People do not know how to manipulate an array (even the Ctrl+Shift+Enter).

BDH is the function that made Bloomberg so popular (to the disadvantage of Reuters).

However I will think of using your method or RTD.

Thanks for all your work in Excel DNA

How to&Answers:

I presume you have tried the Excel-DNA ArrayResizer sample, which carefully avoids many of the issue you are running into. I’d like to understand what you see as the disadvantages of the array-formula-writing approach.

Now, about your function:

Firstly, you can’t safely pass the ‘caller’ Range COM object to another thread – rather pass a string with the address, and get the COM object from the other thread (using a call to ExcelDnaUtil.Application on the worker thread). Most of the time you’ll get lucky, though.
The better way to do this is from the worker thread to get Excel to run a macro on the main thread – by calling Application.Run. The Excel-DNA ArrayResizer sample shows how this can be done.

Secondly, you almost certainly don’t want the ActiveCell, but rather Application.Caller. The ActiveCell might well have nothing to do with the cell where the formula is running from.

Next – Excel will recalculate your function every time you set the Formula again – hence putting you in an endless loop when you enable the Formula set in your finally clause. You cannot set both the Value and the Formula for a cell – if a cell has a Formula then Excel will use the formula to calculate the Value. If you set the Value, the Formula gets removed.
It’s not clear what you want to actually leave in the [0,0] cell – IIRC Bloomberg modifies the formula there in a way that makes it remember how large a range was written to. You could try to add some parameters to your function that tell your function whether to recalculate or whether to return an actual value as its result.

Finally, you might want to reconsider whether the Bloomberg BDH function is a good example for what you want to do. It breaks the dependency calculation of your sheet, which has implications both for performance and for maintaining consistency of the spreadsheet model.

Answer:

My issue was :

  • writing dynamic array

  • data are retrieved asynchronous via a webservice

After discussing with Govert, I chose to take a result as an array and not to copy Bloomberg functions (write an array but return a single value).

Finally, to solve my issue, I used http://excel-dna.net/2011/01/30/resizing-excel-udf-result-arrays/
and reshape the resize() function.

This code is not RTD.

The code belows works in a .dna file

Answer:

I think you need to implemented the request as a RTD server. Normal user defined functions will not update asynchronously.
Then you may hide the call of the RTD server via a user defined function, which can be done via Excel-DNA.

Answer:

So finally you use Array formula, right? As you said, users are not familiar with array formula, they do not know ctrl+shift+enter. I think array formula is a big problem for them.

For me, I have the same issue. I am trying to build a prototype for it. see
https://github.com/kchen0723/ExcelAsync.git