Skip to content

Commit 18f231b

Browse files
authoredMar 20, 2024
Update accepting-range-parameters-in-udfs.md
1 parent 1e88990 commit 18f231b

File tree

1 file changed

+64
-14
lines changed

1 file changed

+64
-14
lines changed
 

‎docs/guides-basic/accepting-range-parameters-in-udfs.md

+64-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,50 @@ title: "Accepting Range Parameters in UDFs"
33
---
44
Parameters with the type of Excel's Range COM object are not directly supported by Excel-DNA. There is a list of allowed parameter types here: [Reference for data types in UDFs](../../../reference-data-type-marshalling)
55

6-
If you want the function to also accept a sheet reference, your parameter should be of type 'object' and marked with an <ExcelArgument(AllowReference:=true)> attribute. In that case you'll get an object of type ExcelDna.Integration.ExcelReference if the function is called with a sheet reference.
6+
You should prefer to get the values directly from the input parameter, without getting the Range COM object. It's also much more efficient doing it that way.
7+
8+
Your simple function that takes a single value or a range of values converted to an array, might then look like this:
9+
10+
```c#
11+
public static object Concat2(object[,] values)
12+
{
13+
string result = "";
14+
int rows = values.GetLength(0);
15+
int cols = values.GetLength(1);
16+
for (int i = 0; i < rows; i++)
17+
{
18+
for (int j = 0; j < cols; j++)
19+
{
20+
object value = values[i, j];
21+
result += value.ToString();
22+
}
23+
}
24+
return result;
25+
}
26+
```
27+
28+
Typically you'd want to check the type of the value object, and do something different based on that. The `object[,]` array passed from Excel-DNA could have items of the following types:
29+
30+
```
31+
double
32+
string
33+
bool
34+
ExcelDna.Integration.ExcelError
35+
ExcelDna.Integration.ExcelEmpty
36+
ExcelDna.Integration.ExcelMissing (if the function is called with no parameter, as `=Concat2()`).
37+
```
38+
39+
If you change the signature to have a single parameter of type `object` (instead of `object[,]`), like this:
40+
```
41+
public static object Concat2(object value)
42+
```
43+
then, depending on how the function is called, you might get one of the above types as the value or you might get an object[,] array as the value, so your type checks would look a bit different before you do the iteration.
44+
45+
46+
If you really want information about the calling range, like the address, you need to
47+
* Apply a `[ExcelArgument(AllowReference=true)]` attribute to the `object input` parameter (in VB `<ExcelArgument(AllowReference:=true)>` )
48+
* Check whether the object you receive is of type `ExcelReference` (this is a thin wrapper over the C API reference information).
49+
* Either use the C API calls to get information about the `ExcelReference` (like a sheet name), or create a `Range` COM object from the information in the `ExcelReference`. This `Range` object needs to be used with care when called from a function.
750

851
ExcelReference is not the same as the COM Range type, it is a small wrapper type for the Excel C API reference structure. From the ExcelReference it is possible to get a COM Range -
952

@@ -21,20 +64,27 @@ Private Function ReferenceToRange(ByVal xlRef As ExcelReference) As Object
2164
End Function
2265
```
2366

67+
or in C#:
2468

25-
The internal xlfReftext call in ReferenceToRange can only be made from functions that are registered as a macro-sheet functions. For this the exported function will need to be marked as IsMacroType:=True.
69+
```c#
70+
static Range ReferenceToRange(object xlInput)
71+
{
72+
ExcelReference reference = (ExcelReference)xlInput;
73+
Application app = (Application)ExcelDnaUtil.Application;
2674

27-
So a function that can accept a sheet reference, and process these as a COM Range object, might look like this:
75+
string sheetName = (string)XlCall.Excel(XlCall.xlSheetNm, reference);
76+
int index = sheetName.LastIndexOf("]");
77+
sheetName = sheetName.Substring(index + 1);
78+
Worksheet ws = (Worksheet)app.Sheets[sheetName];
79+
Range target = app.Range[ws.Cells[reference.RowFirst + 1, reference.ColumnFirst + 1], ws.Cells[reference.RowLast + 1, reference.ColumnLast + 1]];
2880

29-
```vb
30-
<ExcelFunction(IsMacroType:=True)> _
31-
Public Shared Function GetAddress(<ExcelArgument(AllowReference:=true)> ByVal arg As Object) As String
32-
Dim range As Object
33-
If TypeOf arg Is ExcelReference Then
34-
range = ReferenceToRange(arg)
35-
Return range.Address(False, False)
36-
Else
37-
Return "!!! Not a sheet reference"
38-
End If
39-
End Function
81+
for (int iInnerRef = 1; iInnerRef < reference.InnerReferences.Count; iInnerRef++)
82+
{
83+
ExcelReference innerRef = reference.InnerReferences[iInnerRef];
84+
Range innerTarget = app.Range[ws.Cells[innerRef.RowFirst + 1, innerRef.ColumnFirst + 1], ws.Cells[innerRef.RowLast + 1, innerRef.ColumnLast + 1]];
85+
target = app.Union(target, innerTarget);
86+
}
87+
88+
return target;
89+
}
4090
```

0 commit comments

Comments
 (0)