How to add multiple lookup control to Form Grid

In some scenarios, we may need to allow users to select multiple values from a lookup within a grid on a form—for example, assigning multiple categories or tags to a line-level record. Out of the box, Dynamics 365 Finance and Operations does not support multi-select lookups directly on grid controls. However, we can achieve this behavior by extending the SysLookupMultiSelectGrid framework.

Step 1: Create a Custom Class Based on SysLookupMultiSelectGrid

To begin, we need to create a custom class that extends SysLookupMultiSelectGrid. This class will encapsulate the logic for initializing the lookup, storing selected values, and writing them back to the grid’s data source.

Here are the key methods you should implement:

  • init(): Register the lookup override and capture context such as the caller data source and field.

  • lookup(FormStringControl _callerControl): Open the lookup form and load existing values into the selection. After the user makes a selection, update the data source field.

  • getSelected(): Return the container of selected record IDs.

  • setSelected(): Persist the selected values into a specific field (usually a semicolon-delimited string field).

  • construct(…): A static method to initialize the custom multi-select lookup instance.

By implementing these methods, your custom control can support seamless multi-selection directly from the form grid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/// <summary>
/// Willie Yao - 07/07/2025
/// HMLookupMultiSelectGridOrderCategory
/// </summary>
class HMLookupMultiSelectGridOrderCategory extends SysLookupMultiSelectGrid
{
FormDataSource callerDataSource;
FieldId callerFieldId;
#define.Spliter(';')

/// <summary>
/// Willie Yao - 07/07/2025
/// getSelected
/// </summary>
/// <returns>container</returns>
public container getSelected()
{
Array markedRecords;
Common common;
int i;
int recordsMarked = 0;

formDS = formRun.dataSource();
selectedId = conNull();
selectedStr = conNull();

for (common = getFirstSelection(formDS); common; common = formDS.getNext())
{
recordsMarked++;
}

if (recordsMarked > 0)
{
markedRecords = formDS.recordsMarked();

for (i = 1; i <= markedRecords.lastIndex(); i++)
{
formDS = formRun.dataSource();
formDS.setPosition(markedRecords.value(i));
common = formDS.cursor();
selectedId += common.RecId;
selectedStr += common.(defaultSelectFieldId);
}
}

return selectedId;
}

/// <summary>
/// Willie Yao - 07/07/2025
/// init
/// </summary>
public void init()
{
callingControlId.registerOverrideMethod(methodStr(FormStringControl, lookup), methodStr(HMLookupMultiSelectGridOrderCategory, lookup), this);
callerDataSource = callingControlId.formRun().dataSource(callingControlId.fieldBinding().tableName());
callerFieldId = callingControlId.fieldBinding().fieldId();
}

/// <summary>
/// Willie Yao - 07/07/2025
/// lookup
/// </summary>
/// <param name="_callerControl">FormStringControl</param>
public void lookup(FormStringControl _callerControl)
{
FormDataSource formDataSource;
Common callerRecord;

if (callerDataSource)
{
selectedId = conNull();
selectedStr = conNull();
callerRecord = callerDataSource.cursor();

if (callerRecord.RecId)
{
HMSpiffs spiffs = callerRecord;
container con = str2con(spiffs.OrderCategory, ';', false);
int i;
for (i = 1; i <= conLen(con); i++)
{
sunTAFSalesCategory salesCategory = sunTAFSalesCategory::find(conPeek(con, i));

selectedId += salesCategory.RecId;
selectedStr += salesCategory.SalesCategoryID;
}
}
else
{
if (callerRecord.validateWrite())
{
callerRecord.write();
}
else
{
return;
}
}
}

this.run();

if (callerDataSource)
{
callerRecord.(callerFieldId) = con2Str(selectedStr, #Spliter);
formDataSource = callerRecord.dataSource();
formDataSource.refresh();
}

_callerControl.modified();
}

/// <summary>
/// Willie Yao - 07/07/2025
/// setSelected
/// </summary>
public void setSelected()
{
Common callerRecord;

if (callerDataSource)
{
callerRecord = callerDataSource.cursor();

if (callerRecord)
{
HMSpiffs spiffs = callerRecord;
spiffs.OrderCategory = con2Str(selectedStr, ';');
}
}
}

/// <summary>
/// Willie Yao - 07/07/2025
/// construct
/// </summary>
/// <param name="_ctrlId">ctrlId</param>
/// <param name="_query">query</param>
/// <param name="_selectField">selectField</param>
/// <returns>HMLookupMultiSelectGridOrderCategory</returns>
public static HMLookupMultiSelectGridOrderCategory construct(
FormControl _ctrlId,
Query _query,
container _selectField = conNull())
{
HMLookupMultiSelectGridOrderCategory lookupMultiSelectControl;

lookupMultiSelectControl = new HMLookupMultiSelectGridOrderCategory();
lookupMultiSelectControl.parmCallingControl(_ctrlId);
lookupMultiSelectControl.parmCallingControlId(_ctrlId);
lookupMultiSelectControl.parmQuery(_query);
lookupMultiSelectControl.parmSelectField(_selectField);
lookupMultiSelectControl.init();

return lookupMultiSelectControl;
}

}

Step 2: Use the Lookup Control in the Form Grid

Once the custom lookup class is ready, the next step is to integrate it into the form grid where multi-selection is required.

In the FormControl method override (usually on the lookup() method of the relevant control), you can instantiate your custom lookup using the construct() method.

For example:

1
HMLookupMultiSelectGridOrderCategory::construct(MainGrid_OrderCategory, query, con);
  • MainGrid_OrderCategory is the name of the string control (usually bound to a field like OrderCategory) on the form grid.

  • query is a standard Query object used to define the records shown in the lookup.

  • con is a container specifying which field(s) to select and return from the lookup records.

This setup allows the form to display a multi-select lookup when users interact with the field in the grid, enabling them to choose multiple entries and store them in a semicolon-delimited format (or another structure as needed).