Excel DNA 垂直调整返回数组的大小

Posted

技术标签:

【中文标题】Excel DNA 垂直调整返回数组的大小【英文标题】:Excel DNA resize return array vertically 【发布时间】:2014-05-02 16:40:14 【问题描述】:

我正在尝试在 Excel-DNA api 的帮助下返回动态数组。我能够产生所需的输出。但我想产生垂直输出。

这是我的 UDF 的 c# 代码。我已使用此链接制作我的样品。 http://excel-dna.net/2011/01/30/resizing-excel-udf-result-arrays/

下面的代码产生这个输出:

(0,0) (0,1) (0,2) (0,3)

我想要以这种格式输出:

(0,0)
(0,1)
(0,2)
(0,3)

代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using CometCollaborator;
using ExcelDna.Integration;



namespace ExcelUDFs

    public class UDFs
    


        [ExcelFunction(Description = "Make Array")]
        public static object MakeArrayAndResize(int columns)
        

            object result = MakeArray(columns);
            // Call Resize via Excel - so if the Resize add-in is not part of this code, it should still work.
            var a = XlCall.Excel(XlCall.xlUDF, "Resize", result);
            return a;
        

         [ExcelFunction(Description = "Make Array")]
        public static object MakeArray( int columns)
        
            object[,] result = new string[1, columns];
            for (int i = 0; i < 1; i++)
            
                for (int j = 0; j < columns; j++)
                
                    result[i, j] = string.Format("(0,1)", i, j);
                
            

            return result;
        




         static Queue<ExcelReference> ResizeJobs = new Queue<ExcelReference>();

        // This function will run in the UDF context.
        // Needs extra protection to allow multithreaded use.
        public static object Resize(object[,] array)
        
            ExcelReference caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference;
            if (caller == null)
                return array;

            int rows = array.GetLength(0);
            int columns = array.GetLength(1);

            if ((caller.RowLast - caller.RowFirst + 1 != rows) ||
                (caller.ColumnLast - caller.ColumnFirst + 1 != columns))
            
                // Size problem: enqueue job, call async update and return #N/A
                // TODO: Add guard for ever-changing result?
                EnqueueResize(caller, rows, columns);
                AsyncRunMacro("DoResizing");
                return ExcelError.ExcelErrorNA;
            

            // Size is already OK - just return result
            return array;
            //object[,] retArray = new object[columns, rows];
            //for(int i=0;i<array.Length;i++)
            //
            //    retArray[i, 0] = array[0, i];
            //
            //return retArray;
        

        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;
            
        
    

【问题讨论】:

你的数组函数的返回值可以是一个二维的object[,]数组,并且可以包含你想要的任何形状的数据。从你的问题中不清楚为什么你不能从你的函数中返回一个 4 x 1 的数组。 我刚刚将 1*4 数组转换为 4*1 并提供了所需的输出。谢谢。 【参考方案1】:

将一维数组转换为二维数组并调用 resize 方法。

object[,] retArray = new object[a.Length, 1];
for (int i = 0; i < a.Length; i++)

    retArray[i, 0] = a[i];

var ab = XlCall.Excel(XlCall.xlUDF, "Resize", retArray);
return ab;

【讨论】:

以上是关于Excel DNA 垂直调整返回数组的大小的主要内容,如果未能解决你的问题,请参考以下文章

DNA Pairing

Python使用numpy函数vsplit垂直(行角度)拆分numpy数组(返回拆分后的numpy数组列表)实战:垂直拆分二维numpy数组split函数垂直拆分二维numpy数组

Realloc 没有调整指针数组的大小

[经典] 在未排序数组中返回topK大的数

高手来,excel在一组数组中查找值,并返回对应的某一列的值,怎么实现

DNA Pairing-freecodecamp算法题目