Write a List<string> to an Excel.Range - c#

I have the below excel Range:
workSheet.Range[workSheet.Cells[12, 2], workSheet.Cells[5000, 2]]
I have converted this excel range to a List and performed some data manipulations on the list.
object[,] cellValues = (object[,])inputRng.Value2;
List<string> lst = cellValues.Cast<object>().ToList().ConvertAll(x => Convert.ToString(x));
I want to assign the list back to the Excel Range.

Here's some starting code for you that does what you want, it could probably be refined.
Your main "issue" is your conversion from the multidimensional array (object[,] cellValues = (object[,])excelRange.Value;) to the List<string> - if you could somehow keep this as a multidimensional array, then that would help your "setting" code - it depends what your other code is doing.
When you want to "set" your changes back, I've shown 2 ways to do it:
either iterate through the cells (ranges) and set each value
or provide a multi-dimensional array of values in advance and get the range to use them in one shot
Here:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.Office.Interop.Excel;
namespace WindowsFormsApplication10
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// Create starting spreadsheet
Microsoft.Office.Interop.Excel.Application excelApp;
Microsoft.Office.Interop.Excel.Workbook excelWorkbook;
Microsoft.Office.Interop.Excel.Worksheet excelWorksheet;
excelApp = new Microsoft.Office.Interop.Excel.Application();
excelApp.Visible = true;
excelWorkbook = excelApp.Workbooks.Add();
excelWorksheet = excelWorkbook.Sheets.Add();
excelWorksheet.Activate();
Microsoft.Office.Interop.Excel.Range excelRange = excelWorksheet.Range[excelWorksheet.Cells[12, 2], excelWorksheet.Cells[5000, 2]];
// Turn off updating to make it faster
excelApp.ScreenUpdating = false;
// Set some initial data
int i = 1;
foreach (Microsoft.Office.Interop.Excel.Range cell in excelRange.Cells)
{
cell.Value = i;
i++;
}
// Get the data from those cells as a list of strings
object[,] cellValues = (object[,])excelRange.Value;
List<string> lst = cellValues.Cast<object>().ToList().ConvertAll(x => Convert.ToString(x));
// Modify the strings in some way
for (int l = 0; l < lst.Count; l++)
{
lst[l] = lst[l] + "modified";
}
// Here are some different ways set the "cells" back
// Set the cells back with the changes
//------------------------------------
// Option 1: using a multidimensional array
/*
object[,] cellValuesToWrite = new string[excelRange.Rows.Count, excelRange.Columns.Count];
int z = 0;
foreach (string str in lst)
{
cellValuesToWrite[z,0] = lst[z];
z++;
}
excelRange.Value2 = cellValuesToWrite;
*/
// Option 2: iterating the range of cells and "setting" the value
/*
int z = 0;
foreach (Microsoft.Office.Interop.Excel.Range cell in excelRange.Cells)
{
cell.Value = lst[z];
z++;
}
excelRange.Value2 = lst;
*/
// Turn updating back on
excelApp.ScreenUpdating = true;
}
}
}

Related

Add an element/item to a multidimentional list

I am trying to create a multidimensional list filled with an employee and their information.
Ex: "Jane Smith" "Manager" "75,000" "Dallas"
the code I have right now is giving me an out of range exception.
This bigROW[i].Add(ownName); and bigROW[i][j+1] = newElement; gives me errors.
//Begin making rows
for (int i = 0; i < fileRowCount; i++ )
{
string findOwners = "";
findOwners = file5Data.Rows[i][0].ToString();
if(DISTINCTOppOwners.Contains(findOwners))
{
//Find index of where owner is
int useIndex = 0;
useIndex = DISTINCTOppOwners.IndexOf(findOwners);
//Add their name to Multidimensional list
string ownName = DISTINCTOppOwners[useIndex].ToString();
//This line give me the ERROR
bigROW[i].Add(ownName);
for (int j = 0; j < fileColCount; j++)
{
Add Employee information to Multidimensional list
string newElement = file5Data.Rows[i][j].ToString();
if(ownName != newElement)
{
if(j ==0)
{
//Avoid adding their names to the list twice
bigROW[i][j+1] = newElement;
}
bigROW[i][j] = newElement;
}
}
}
}
I tried adding the info to a list called "sublist" then adding it to the BigRow(multidimensional list),but when I cleared the sublist to add a new row it deleted the values from the BigRow.
I tried adding the info to a list called "sublist" then adding it to the BigRow(multidimensional list),but when I cleared the sublist to add a new row it deleted the values from the BigRow.
When you add an object to a list what is stored is a reference, not the contents of the object. Instead of clearing sublist you should create a new List each time. Otherwise you have an outer list that contains multiple copies of the same list inside.
Refer to jdweng's answer above for an example of this. In his code the ToList call creates an new numbers list for each line, so that each row has its own List of numbers.
Here is a simple example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication64
{
class Program
{
static void Main(string[] args)
{
string input =
"0,1,2,3,4,5,6,7,8,9\n" +
"10,11,12,13,14,15,16,17,18,19\n" +
"20,21,22,23,24,25,26,27,28,29\n" +
"30,31,32,33,34,35,36,37,38,39\n" +
"40,41,42,43,44,45,46,47,48,49\n";
List<List<int>> output = new List<List<int>>();
StringReader reader = new StringReader(input);
string inputline = "";
while ((inputline = reader.ReadLine()) != null)
{
List<int> numbers = inputline.Split(new char[] { ',' }).Select(x => int.Parse(x)).ToList();
output.Add(numbers);
}
}
}
}

c# Excel How to find a specific range without looping through every row/record

I'm making a tool that checks if an existing excel file (>20k records) contains a specific string in a specific column.
So far I've tried using a for-loop to check every single cell, but it took almost 2 minutes to find the the cell.
example:
row name price
-------------------------
7000 AAA 10
7001 AAA 5
7002 AAA 10
7003 AAA 5
7004 AAA 10
7005 AAA 10
7006 AAA 10
7007 BBB 5
7008 BBB 5
7009 AAA 10
7010 BBB 5
...
30000 AAA 10
my pseudo-code:
static void Main(string[] args) {
var xlApp = new Excel.Application();
var xlWorkbook = xlApp.Workbooks.Open(#"A_PATH");
Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
var xlRange = xlWorksheet.UsedRange;
int lastRow = xlWorksheet.Cells.SpecialCells(Excel.XlCellType.xlCellTypeLastCell).Row;
for (int i = 2; i < lastRow; i++) {
if(xlRange.Cells[i, 1].Value2 != null) {
string value = xlRange.Cells[i, 1].Value2.ToString();
if(value == "BBB") {
Console.WriteLine(((Excel.Range)xlRange.Cells[i, 3]).Value2.ToString());
}
}
}
Console.ReadLine();
}
So is there a way i can make the 'query' faster instead of reading every row?
I know in SQL there's something like index skip scan. Maybe I can achieve the same in c#.
Thanks in advance!
I'm not terribly good with Excel automation, but perhaps you could try using the built-in Excel filter function?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Excel;
namespace ExcelTest1
{
class Program
{
static void Main(string[] args)
{
var excel = new Microsoft.Office.Interop.Excel.Application();
excel.Visible = true;
var book = excel.Workbooks.Open(#"D:\test.xlsx");
var sheet = book.Sheets[1];
var range = sheet.UsedRange;
//Filter the sheet itself.
range.AutoFilter(Field: 2, Criteria1: "BBB");
//and get only visible cells after the filter.
var result = range.SpecialCells(XlCellType.xlCellTypeVisible, Type.Missing);
Console.WriteLine(result.Rows.Count);
foreach (Range row in result.Rows)
{
Console.WriteLine(row.Cells[1,3].Value2());
}
book.Close(SaveChanges:false);
excel.Quit();
Console.ReadLine();
}
}
}
On a modest system, this found "BBB" which was the last of 30,000 rows of test data, in under a second.

Trying to apply bold to a whole row but keep getting null reference - NPOI [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 8 years ago.
The 1st time I was applying bold to columns, but I only wanted a row to be bold not the whole column. So I decided to do the same thing but using row.RowStyle. Compiled without errors, but what I did get is a runtime error where is says r.RowStyle.SetFont(font);. I made a class that to do with everything related to excel and it's in this class I get this error(r.RowStyle.SetFont(font);):
NullReferenceException was handled
Object reference not set to an instance of an object.
Debugged the whole process and nothings null. I don't understand why I get this error when using RowStyle and when I use CellStyle I don't get that error.
This is my class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NPOI.HSSF.UserModel;
using NPOI.HPSF;
using NPOI.POIFS.FileSystem;
using NPOI.SS.UserModel;
using System.IO;
//works with everything related to excel
namespace takeMyTime_text
{
class exelSheet
{
string exelPath; // where it's being saved
HSSFWorkbook wb2 = new HSSFWorkbook();
ISheet sheet;
IRow r;
IFont font;
String[] headerTitles = { "Date", "In", "Out", "In", "Out", "Description" };
// assing values to class variables
public void getValues(string path, string worksheetName)
{
exelPath = path;
}
//excel header
public void header()
{
#region set bold properties
font = wb2.CreateFont();
font.FontHeightInPoints = 11;
font.FontName = "Arial";
font.Boldweight = (short)FontBoldWeight.Bold;
#endregion
sheet = wb2.CreateSheet("test sheet");
//se tiene que usar esto cada vez que vallas a escribir en el mismo row
r = sheet.CreateRow(0);
r.RowStyle.SetFont(font);
for (int i = 0; i < headerTitles.Length; i++)
{
r.CreateCell(i).SetCellValue(headerTitles[i]);
}
}
//excel footer
public void footer(int row, int col, string totalHours, int row2, int col2)
{
//ws.Cells[row, col] = new Cell("Worked hours:");
//ws.Cells[row2, col2] = new Cell(totalHours);
//wb.Worksheets.Add(ws);
//wb.Save(exelPath);
}
// write the date on the excel file
public void writeDate(DateTime dt, int col, int row)
{
r = sheet.CreateRow(row);
r.CreateCell(col).SetCellValue(dt.Month + "/" + dt.Day + "/" + dt.Year);
}
//write and value on a cel
public void writeValues(string text, int col, int row)
{
//r = sheet.CreateRow(row);
r.CreateCell(col).SetCellValue(text);
}
//guarda la info en un excel
public void writeToFile()
{
FileStream file = new FileStream(exelPath, FileMode.Create);
wb2.Write(file);
file.Close();
}
}
}
Looks like your RowStyle is null. Try the following:
var style = wb2.CreateCellStyle();
style.SetFont(font);
r = sheet.CreateRow(0);
r.RowStyle = style;

How do you set the value of a cell using Excel Dna?

I've got the following code in my Excel DNA plugin
In my AutoOpen method I put the following code:
ExcelIntegration.RegisterUnhandledExceptionHandler(ex => ex.ToString());
I've got the following function that gets called from my excel sheet.
[ExcelFunction(Category = "Foo", Description = "Sets value of cell")]
public static Foo(String idx)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
Excel.Workbook wb = app.Workbooks[1];
Excel.Worksheet ws = GetSheet("Main");
// This gives us the row
Excel.Name idxRange = wb.Names.Item("COL_Main_Index");
var row = (int)app.WorksheetFunction.Match(idx, idxRange.RefersToRange, 0);
// Get the Column
Excel.Name buyOrderRange = wb.Names.Item("COL_Main_BuyOrder");
var col = (int)buyOrderRange.RefersToRange.Cells.Column;
// create the range and update it
Excel.Range r = (Excel.Range)ws.Cells[row, col];
r.Value ="Foo";
}
The issue is that I can't actually set any cell values. When I call the method it causes an error on the last line.
My error handler gives me the followign error:
{System.Runtime.InteropServices.COMException (0x800A03EC)
I've also tried to set the cell value like so:
r = (Excel.Range)ws.Cells[12, 22];
const int nCells = 1;
Object[] args1 = new Object[1];
args1[0] = nCells;
r.GetType().InvokeMember("Value2", BindingFlags.SetProperty, null, r, args1);
With the same result.
Can anyone point to what I might be doing wrong here?
Actually, you can write in any cell if you do this in an async job as a macro.
Simple Example:
using ExcelDna.Integration;
using Excel = Microsoft.Office.Interop.Excel;
[ExcelFunction(Category = "Foo", Description = "Sets value of cell")]
public static Foo(String idx)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
Excel.Range range = app.ActiveCell;
object[2,2] dummyData = new object[2, 2] {
{ "foo", "bar" },
{ 2500, 7500 }
};
var reference = new ExcelReference(
range.Row, range.Row + 2 - 1, // from-to-Row
range.Column - 1, range.Column + 2 - 1); // from-to-Column
// Cells are written via this async task
ExcelAsyncUtil.QueueAsMacro(() => { reference.SetValue(dummyData); });
// Value displayed in the current cell.
// It still is a UDF and can be executed multiple times via F2, Return.
return "=Foo()";
}
Writing into a single Cell:
int row = 5;
int column = 6;
var reference = new ExcelReference(row - 1, column - 1);
ExcelAsyncUtil.QueueAsMacro(() => { reference.SetValue("Foobar"); });
// edit:
just fyi, you can also use:
private void WriteArray(object[,] data)
{
Excel.Application app = (Excel.Application)ExcelDnaUtil.Application;
Excel.Worksheet worksheet= (Excel.Worksheet)app.ActiveWorkbook.ActiveSheet;
Excel.Range startCell = app.ActiveCell;
Excel.Range endCell = (Excel.Range)worksheet.Cells[startCell.Row + data.GetLength(0) - 1, startCell.Column + data.GetLength(1) - 1];
var writeRange = worksheet.Range[startCell, endCell];
writeRange.Value2 = data;
}
And then:
object[,] data = ...;
ExcelAsyncUtil.QueueAsMacro(() =>
{
WriteArray();
});
Excel does not allow you to set other worksheet cells from within a user-defined worksheet function. This is to preserve the dependency tree Excel uses to manage the recalculation. This is true whether you are using VBA, the C API or Excel-DNA.
Best is to add a ribbon button, context menu or shortcut key to effect the changes via a macro.
There are some ugly workarounds, but I would not recommend it.
Here is the answer using .NET VB
Dim row As Integer = 7
Dim column As Integer = 7
Dim reference = New ExcelReference(row, column)
ExcelAsyncUtil.QueueAsMacro(Sub()
reference.SetValue("Test")
End Sub)

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

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
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.
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
<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>
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.
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

Categories