O, I didn't realize TStringGrid supported copy/paste. After playing with the multi-selection, it looks like the issue will be "how to handle sparse selections using CSV tables?". And then, what happens when you paste sparse selections back?
Anyway, here is a function to return the union of the current multi-selection.
function StringGridSelection(const AStringGrid: TStringGrid): TRect;
var
LIndex: Integer;
begin
Result := AStringGrid.Selection;
for LIndex := 0 to AStringGrid.SelectedRangeCount - 1 do begin
with AStringGrid.SelectedRange[LIndex] do begin
Result.Left := Min(Result.Left, Left);
Result.Top := Min(Result.Top, Top);
Result.Right := Max(Result.Right, Right);
Result.Bottom := Max(Result.Bottom, Bottom);
end;
end;
end;
And, if you intercept CTRL-C in OnKeyDown, then the union of the selections will be copied to the clipboard. Plus, the visible selection will be grown to the union of the multi-selections, so, there is a visible indicator to the user, that selection has changed.
procedure TForm1.StringGrid1KeyDown(ASender: TObject; var AKey: word; AShift: TShiftState);
begin
if (AKey = VK_C) and (AShift = [ssModifier]) then begin
(ASender as TStringGrid).Selection := StringGridSelection(ASender as TStringGrid);
end;
end;
That would be the "simple" solution, since it works with the current copy/paste system.
But, if you wanted to only copy the selected cells using CSV, then you would need something like:
function StringGridSelectionAsCSV(const AStringGrid: TStringGrid): String;
var
LCol, LRow: Integer;
LRect: TRect;
LString: String;
begin
Result := EmptyStr;
LRect := StringGridSelection(AStringGrid);
for LRow := LRect.Top to LRect.Bottom do begin
if Result <> EmptyStr then begin
Result += LineEnding;
end;
LString := EmptyStr;
for LCol := LRect.Left to LRect.Right do begin
if LString <> EmptyStr then begin
LString += #9;
end;
if AStringGrid.IsCellSelected[LCol, LRow] then begin
LString += AStringGrid.Cells[LCol, LRow];
end else begin
LString += '#';
end;
end;
if LString <> EmptyStr then begin
Result += LString;
end;
end;
end;
procedure StringGridLoadSelected(const AStringGrid: TStringGrid; const AStrings: TStrings);
var
LCol, LRow: Integer;
LSelection: TRect;
begin
LSelection := StringGridSelection(AStringGrid);
LSelection.BottomRight := LSelection.TopLeft;
with TStringList.Create do begin
try
for LRow := 0 to AStrings.Count - 1 do begin
Text := AnsiReplaceStr(AStrings[LRow], #9, LineEnding);
for LCol := 0 to Count - 1 do begin
if Strings[LCol]<> '#' then begin
AStringGrid.Cells[LSelection.Left + LCol, LSelection.Top + LRow] := Strings[LCol];
end;
LSelection.Right := Max(LSelection.Right, LSelection.Left + LCol);
LSelection.Bottom := Max(LSelection.Bottom, LSelection.Top + LRow);
end;
end;
finally
Free;
end;
end;
AStringGrid.Selection := LSelection;
end;
procedure StringGridLoadSelected(const AStringGrid: TStringGrid; const AText: String);
var
LStrings: TStrings;
begin
LStrings := TStringList.Create;
try
LStrings.Text := AText;
StringGridLoadSelected(AStringGrid, LStrings);
finally
FreeAndNil(LStrings);
end;
end;
Note: the '#' used for cells that are not selected in the union selection rectangle.
And then intercept Copy/Paste using:
procedure TForm1.StringGrid1KeyDown(ASender: TObject; var AKey: word; AShift: TShiftState);
begin
if (AKey = VK_C) and (AShift = [ssModifier]) then begin
ClipBoard.AsText := StringGridSelectionAsCSV(ASender as TStringGrid);
AKey := 0;
end;
if (AKey = VK_V) and (AShift = [ssModifier]) then begin
StringGridLoadSelected( ASender as TStringGrid, ClipBoard.AsText);
AKey := 0;
end;
end;
That code works for sparse selections, but, doesn't handle pasting outside the string grid range. Which means, you have to decide if you disallow, trim, or expand the string grid.
All are application specific choices.
I'd go with the union rectangle selection option.