Import Plugins
Import behavior is the data-rich half of the combined CSV starter. The plugin registers .csv, identifies supported files cheaply, then translates rows into TonCut projects, materials, pieces, and stock. Delphi and native C++ own that host-facing workflow; the C# project supplies CSV parsing and serialization behind a project-specific native bridge.
Discovery flow
TonCut builds import handlers by calling EnumExtensions(etImport, callback) on every active plugin. In the CSV starter, the same plugin also handles export and therefore answers both and .
EnumExtensions
function TCsvImportExportPlugin.EnumExtensions(
aType: TTcpExtensionType;
aEnumCb: ITcEnumExtensionsCallback
): TTcpResult;
begin
if aType in [etImport, etExport] then
aEnumCb.Invoke(aType, '.csv', 'CSV Import/Export Template');
Result := cprOK;
end;
The C# managed library does not implement EnumExtensions. The native shim registers extensions and maps host callbacks to any project-specific managed bridge.
File matching
IsMyFile should stay cheap and conservative. In the CSV starter, the matcher checks:
- the
.csvextension - file readability
- the required shared header row
That is enough to make the starter practical without pushing expensive parsing into the ownership path. If your real format needs deeper heuristics later, keep them separate from full import population.
Validate the CSV header
function TCsvImportExportPlugin.ValidateHeader(
const AFileName: WideString
): TTcpResult;
begin
if not SameText(ExtractFileExt(AFileName), '.csv') then
Exit(cprNo);
if not FileExists(AFileName) then
Exit(cprFailed);
if HasRequiredHeader(ParseCsvText(TFile.ReadAllText(AFileName, TEncoding.UTF8))[0]) then
Exit(cprOK);
Result := cprNo;
end;
The native shim converts headerIsValid into the appropriate value; the managed project publishes no direct TonCut callback interface.
Populating data
LoadFile receives host-owned collections. Treat them as the destination for imported data, not scratch buffers.
| Collection | CSV starter responsibility |
|---|---|
| IMaterials | Reuse an existing material by title, or create it once when the title is new |
| IProjects | Reuse or create projects by title for FORMAT rows |
| IFormats | Create one format per FORMAT row |
| ISheets | Create one sheet per SHEET row |
The important design rule is that the importer stays close to public host objects like ITonCut, IMaterial, IProject, IFormat, and ISheet. The starter does not depend on hidden helper layers.
Use BeginUpdate and EndUpdate on destination collections while importing a file with many rows. That keeps change notifications and UI refreshes under control.
LoadFile core pattern
for i := 1 to Length(table) - 1 do
begin
row := table[i];
recordType := UpperCase(CellByName(table[0], row, 'RecordType'));
if recordType = 'FORMAT' then
begin
project := EnsureProject(aProjects, CellByName(table[0], row, 'ProjectTitle'));
end;
material := EnsureMaterial(
aMaterials,
CellByName(table[0], row, 'MaterialTitle'),
materialKind,
materialWidth,
materialThickness
);
if recordType = 'FORMAT' then
begin
format := aFormats.CreateFormat();
format.Project := project;
format.Material := material;
format.Length := itemLength;
format.Width := itemWidth;
format.Quantity := quantity;
format.Identifier := CellByName(table[0], row, 'Identifier');
format.Description := CellByName(table[0], row, 'Description');
aFormats.Add(format);
end
else if recordType = 'SHEET' then
begin
sheet := aSheets.CreateSheet();
sheet.Material := material;
sheet.Length := itemLength;
sheet.Width := itemWidth;
sheet.Quantity := Integer(quantity);
sheet.Identifier := CellByName(table[0], row, 'Identifier');
sheet.Description := CellByName(table[0], row, 'Description');
aSheets.Add(sheet);
end;
end;
The full multi-language SDK examples live under SDK/examples/, and the overview page for them is /sdk.