1
+ <DnaLibrary RuntimeVersion="v4.0" Language="CS">
2
+ <![CDATA[
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using ExcelDna.Integration;
6
+
7
+ namespace AsyncFunctions
8
+ {
9
+ // This class defines a few test functions that can be used to explore the automatic array resizing.
10
+ public static class ResizeTestFunctions
11
+ {
12
+ // Just returns an array of the given size
13
+ public static object[,] MakeArray(int rows, int columns)
14
+ {
15
+ object[,] result = new object [rows, columns];
16
+ for (int i = 0; i < rows; i++)
17
+ {
18
+ for (int j = 0; j < columns; j++)
19
+ {
20
+ result[i,j] = i + j;
21
+ }
22
+ }
23
+
24
+ return result;
25
+ }
26
+
27
+ public static double[,] MakeArrayDoubles(int rows, int columns)
28
+ {
29
+ double[,] result = new double[rows, columns];
30
+ for (int i = 0; i < rows; i++)
31
+ {
32
+ for (int j = 0; j < columns; j++)
33
+ {
34
+ result[i,j] = i + (j/1000.0);
35
+ }
36
+ }
37
+
38
+ return result;
39
+ }
40
+
41
+ public static object MakeMixedArrayAndResize(int rows, int columns)
42
+ {
43
+ object[,] result = new object [rows, columns];
44
+ for (int j = 0; j < columns; j++)
45
+ {
46
+ result[0,j] = "Col " + j;
47
+ }
48
+ for (int i = 1; i < rows; i++)
49
+ {
50
+ for (int j = 0; j < columns; j++)
51
+ {
52
+ result[i,j] = i + (j * 0.1);
53
+ }
54
+ }
55
+
56
+ return ArrayResizer.Resize(result);
57
+ }
58
+
59
+ // Makes an array, but automatically resizes the result
60
+ public static object MakeArrayAndResize(int rows, int columns, string unused, string unusedtoo)
61
+ {
62
+ object[,] result = MakeArray(rows, columns);
63
+ return ArrayResizer.Resize(result);
64
+
65
+ // Can also call Resize via Excel - so if the Resize add-in is not part of this code, it should still work
66
+ // (though calling direct is better for large arrays - it prevents extra marshaling).
67
+ // return XlCall.Excel(XlCall.xlUDF, "Resize", result);
68
+ }
69
+
70
+ public static double[,] MakeArrayAndResizeDoubles(int rows, int columns)
71
+ {
72
+ double[,] result = MakeArrayDoubles(rows, columns);
73
+ return ArrayResizer.ResizeDoubles(result);
74
+ }
75
+ }
76
+
77
+ public class ArrayResizer : XlCall
78
+ {
79
+ // This function will run in the UDF context.
80
+ // Needs extra protection to allow multithreaded use.
81
+ public static object Resize(object[,] array)
82
+ {
83
+ var caller = Excel(xlfCaller) as ExcelReference;
84
+ if (caller == null)
85
+ return array;
86
+
87
+ int rows = array.GetLength(0);
88
+ int columns = array.GetLength(1);
89
+
90
+ if (rows == 0 || columns == 0)
91
+ return array;
92
+
93
+ if ((caller.RowLast - caller.RowFirst + 1 == rows) &&
94
+ (caller.ColumnLast - caller.ColumnFirst + 1 == columns))
95
+ {
96
+ // Size is already OK - just return result
97
+ return array;
98
+ }
99
+
100
+ var rowLast = caller.RowFirst + rows - 1;
101
+ var columnLast = caller.ColumnFirst + columns - 1;
102
+
103
+ // Check for the sheet limits
104
+ if (rowLast > ExcelDnaUtil.ExcelLimits.MaxRows - 1 ||
105
+ columnLast > ExcelDnaUtil.ExcelLimits.MaxColumns - 1)
106
+ {
107
+ // Can't resize - goes beyond the end of the sheet - just return #VALUE
108
+ // (Can't give message here, or change cells)
109
+ return ExcelError.ExcelErrorValue;
110
+ }
111
+
112
+ // TODO: Add some kind of guard for ever-changing result?
113
+ ExcelAsyncUtil.QueueAsMacro(() =>
114
+ {
115
+ // Create a reference of the right size
116
+ var target = new ExcelReference(caller.RowFirst, rowLast, caller.ColumnFirst, columnLast, caller.SheetId);
117
+ DoResize(target); // Will trigger a recalc by writing formula
118
+ });
119
+ // Return what we have - to prevent flashing #N/A
120
+ return array;
121
+ }
122
+
123
+ public static double[,] ResizeDoubles(double[,] array)
124
+ {
125
+ var caller = Excel(xlfCaller) as ExcelReference;
126
+ if (caller == null)
127
+ return array;
128
+
129
+ int rows = array.GetLength(0);
130
+ int columns = array.GetLength(1);
131
+
132
+ if (rows == 0 || columns == 0)
133
+ return array;
134
+
135
+ if ((caller.RowLast - caller.RowFirst + 1 == rows) &&
136
+ (caller.ColumnLast - caller.ColumnFirst + 1 == columns))
137
+ {
138
+ // Size is already OK - just return result
139
+ return array;
140
+ }
141
+
142
+ var rowLast = caller.RowFirst + rows - 1;
143
+ var columnLast = caller.ColumnFirst + columns - 1;
144
+
145
+ if (rowLast > ExcelDnaUtil.ExcelLimits.MaxRows - 1 ||
146
+ columnLast > ExcelDnaUtil.ExcelLimits.MaxColumns - 1)
147
+ {
148
+ // Can't resize - goes beyond the end of the sheet - just return null (for #NUM!)
149
+ // (Can't give message here, or change cells)
150
+ return null;
151
+ }
152
+
153
+ // TODO: Add guard for ever-changing result?
154
+ ExcelAsyncUtil.QueueAsMacro(() =>
155
+ {
156
+ // Create a reference of the right size
157
+ var target = new ExcelReference(caller.RowFirst, rowLast, caller.ColumnFirst, columnLast, caller.SheetId);
158
+ DoResize(target); // Will trigger a recalc by writing formula
159
+ });
160
+ // Return what we have - to prevent flashing #N/A
161
+ return array;
162
+ }
163
+
164
+ static void DoResize(ExcelReference target)
165
+ {
166
+ // Get the current state for reset later
167
+ using (new ExcelEchoOffHelper())
168
+ using (new ExcelCalculationManualHelper())
169
+ {
170
+ ExcelReference firstCell = new ExcelReference(target.RowFirst, target.RowFirst, target.ColumnFirst, target.ColumnFirst, target.SheetId);
171
+
172
+ // Get the formula in the first cell of the target
173
+ string formula = (string)Excel(xlfGetCell, 41, firstCell);
174
+ bool isFormulaArray = (bool)Excel(xlfGetCell, 49, firstCell);
175
+ if (isFormulaArray)
176
+ {
177
+ // Select the sheet and firstCell - needed because we want to use SelectSpecial.
178
+ using (new ExcelSelectionHelper(firstCell))
179
+ {
180
+ // Extend the selection to the whole array and clear
181
+ Excel(xlcSelectSpecial, 6);
182
+ ExcelReference oldArray = (ExcelReference)Excel(xlfSelection);
183
+
184
+ oldArray.SetValue(ExcelEmpty.Value);
185
+ }
186
+ }
187
+ // Get the formula and convert to R1C1 mode
188
+ bool isR1C1Mode = (bool)Excel(xlfGetWorkspace, 4);
189
+ string formulaR1C1 = formula;
190
+ if (!isR1C1Mode)
191
+ {
192
+ object formulaR1C1Obj;
193
+ XlReturn formulaR1C1Return = TryExcel(xlfFormulaConvert, out formulaR1C1Obj, formula, true, false, ExcelMissing.Value, firstCell);
194
+ if (formulaR1C1Return != XlReturn.XlReturnSuccess || formulaR1C1Obj is ExcelError)
195
+ {
196
+ string firstCellAddress = (string)Excel(xlfReftext, firstCell, true);
197
+ Excel(xlcAlert, "Cannot resize array formula at " + firstCellAddress + " - formula might be too long when converted to R1C1 format.");
198
+ firstCell.SetValue("'" + formula);
199
+ return;
200
+ }
201
+ formulaR1C1 = (string)formulaR1C1Obj;
202
+ }
203
+ // Must be R1C1-style references
204
+ object ignoredResult;
205
+ //Debug.Print("Resizing START: " + target.RowLast);
206
+ XlReturn formulaArrayReturn = TryExcel(xlcFormulaArray, out ignoredResult, formulaR1C1, target);
207
+ //Debug.Print("Resizing FINISH");
208
+
209
+ // TODO: Find some dummy macro to clear the undo stack
210
+
211
+ if (formulaArrayReturn != XlReturn.XlReturnSuccess)
212
+ {
213
+ string firstCellAddress = (string)Excel(xlfReftext, firstCell, true);
214
+ Excel(xlcAlert, "Cannot resize array formula at " + firstCellAddress + " - result might overlap another array.");
215
+ // Might have failed due to array in the way.
216
+ firstCell.SetValue("'" + formula);
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ // RIIA-style helpers to deal with Excel selections
223
+ // Don't use if you agree with Eric Lippert here: http://stackoverflow.com/a/1757344/44264
224
+ public class ExcelEchoOffHelper : XlCall, IDisposable
225
+ {
226
+ object oldEcho;
227
+
228
+ public ExcelEchoOffHelper()
229
+ {
230
+ oldEcho = Excel(xlfGetWorkspace, 40);
231
+ Excel(xlcEcho, false);
232
+ }
233
+
234
+ public void Dispose()
235
+ {
236
+ Excel(xlcEcho, oldEcho);
237
+ }
238
+ }
239
+
240
+ public class ExcelCalculationManualHelper : XlCall, IDisposable
241
+ {
242
+ object oldCalculationMode;
243
+
244
+ public ExcelCalculationManualHelper()
245
+ {
246
+ oldCalculationMode = Excel(xlfGetDocument, 14);
247
+ Excel(xlcOptionsCalculation, 3);
248
+ }
249
+
250
+ public void Dispose()
251
+ {
252
+ Excel(xlcOptionsCalculation, oldCalculationMode);
253
+ }
254
+ }
255
+
256
+ // Select an ExcelReference (perhaps on another sheet) allowing changes to be made there.
257
+ // On clean-up, resets all the selections and the active sheet.
258
+ // Should not be used if the work you are going to do will switch sheets, amke new sheets etc.
259
+ public class ExcelSelectionHelper : XlCall, IDisposable
260
+ {
261
+ object oldSelectionOnActiveSheet;
262
+ object oldActiveCellOnActiveSheet;
263
+
264
+ object oldSelectionOnRefSheet;
265
+ object oldActiveCellOnRefSheet;
266
+
267
+ public ExcelSelectionHelper(ExcelReference refToSelect)
268
+ {
269
+ // Remember old selection state on the active sheet
270
+ oldSelectionOnActiveSheet = Excel(xlfSelection);
271
+ oldActiveCellOnActiveSheet = Excel(xlfActiveCell);
272
+
273
+ // Switch to the sheet we want to select
274
+ string refSheet = (string)Excel(xlSheetNm, refToSelect);
275
+ Excel(xlcWorkbookSelect, new object[] { refSheet });
276
+
277
+ // record selection and active cell on the sheet we want to select
278
+ oldSelectionOnRefSheet = Excel(xlfSelection);
279
+ oldActiveCellOnRefSheet = Excel(xlfActiveCell);
280
+
281
+ // make the selection
282
+ Excel(xlcFormulaGoto, refToSelect);
283
+ }
284
+
285
+ public void Dispose()
286
+ {
287
+ // Reset the selection on the target sheet
288
+ Excel(xlcSelect, oldSelectionOnRefSheet, oldActiveCellOnRefSheet);
289
+
290
+ // Reset the sheet originally selected
291
+ string oldActiveSheet = (string)Excel(xlSheetNm, oldSelectionOnActiveSheet);
292
+ Excel(xlcWorkbookSelect, new object[] { oldActiveSheet });
293
+
294
+ // Reset the selection in the active sheet (some bugs make this change sometimes too)
295
+ Excel(xlcSelect, oldSelectionOnActiveSheet, oldActiveCellOnActiveSheet);
296
+ }
297
+ }
298
+
299
+ }
300
+ ]]>
301
+ </DnaLibrary>
0 commit comments