I have a method that reads the data in the cells of a row of a TStringGrid
, and copies it to the clipboard. And I have a corresponding method to paste the data from the clipboard into an empty row in the TStringGrid
.
These methods were written for D7, but are broken after migration to XE2.
procedure TfrmBaseRamEditor.CopyLine(Sender: TObject; StrGridTemp: TStringGrid;
Row, Column: Integer);
var
Stream: TMemoryStream;
MemHandle: THandle;
MemBlock: Pointer;
i, Len: Integer;
RowStr: String;
begin
Stream := nil;
try
Stream := TMemoryStream.Create;
// The intermediate format to write to the stream.
// Separate each item by horizontal tab character.
RowStr := '';
for i := 0 to (StrGridTemp.ColCount - 1) do
RowStr := RowStr + StrGridTemp.Cells[i, Row] + #9;
// Write all elements in a string.
Len := Length(RowStr);
Stream.Write(Len, SizeOf(Len));
Stream.Write(PChar(RowStr)^, Length(RowStr));
// Request Memory for the clipboard.
MemHandle := GlobalAlloc(GMEM_DDESHARE, Stream.SIZE);
MemBlock := GlobalLock(MemHandle);
try
// Copy the contents of the stream into memory.
Stream.Seek(0, soFromBeginning);
Stream.Read(MemBlock^, Stream.SIZE);
finally
GlobalUnlock(MemHandle);
end;
// Pass the memory to the clipboard in the correct format.
Clipboard.Open;
Clipboard.SetAsHandle(TClipboardFormat, MemHandle);
Clipboard.Close;
finally
Stream.Free;
end;
end;
procedure TfrmBaseRamEditor.PasteLine(Sender: TObject; StrGridTemp: TStringGrid;
Row, Column: Integer);
var
Stream: TMemoryStream;
MemHandle: THandle;
MemBlock: Pointer;
ASize, Len, i: Integer;
TempStr: String;
begin
Clipboard.Open;
try
// If something is in the clipboard in the correct format.
if Clipboard.HasFormat(TClipboardFormat) then
begin
MemHandle := Clipboard.GetAsHandle(TClipboardFormat);
if MemHandle <> 0 then
begin
// Detect size (number of bytes).
ASize := GlobalSize(MemHandle);
Stream := nil;
try
Stream := TMemoryStream.Create;
// Lock the contents of the clipboard.
MemBlock := GlobalLock(MemHandle);
try
// Copy the data into the stream.
Stream.Write(MemBlock^, ASize);
finally
GlobalUnlock(MemHandle);
end;
Stream.Seek(0, soFromBeginning);
Stream.Read(Len, SizeOf(Len));
SetLength(TempStr, Len);
Stream.Read(PChar(TempStr)^, Stream.SIZE);
for i := 0 to StrGridTemp.RowCount do
StrGridTemp.Cells[i, Row] := NextStr(TempStr, #9);
finally
Stream.Free;
end;
end;
end;
finally
Clipboard.Close;
end;
end;
The problem manifests when I copy a row with some values, then paste it into an empty row. The first cell is pasted correctly, but the second cell contains garbage characters (and nothing is pasted in the 3rd column onwards). I know why nothing is pasted in 3rd column onwards: because the "horizontal tab" character which separates the columns is corrupted along with the cell contents.
I've looked through "Delphi and Unicode" by Marco Cantu, but haven't been able to figure out where it's all going wrong.
Char
is an alias for WideChar
. So in CopyLine
Stream.Write(PChar(RowStr)^, Length(RowStr));
only writes half the string. It should be
Stream.Write(PChar(RowStr)^, Length(RowStr)*SizeOf(Char));
In PasteLine
I find this line odd:
Stream.Read(PChar(TempStr)^, Stream.SIZE);
Since you've already consumed some of the string you are attempting to read past the end. I'd write it like this:
Stream.Read(PChar(TempStr)^, Len*SizeOf(Char));
Note that if you use the same custom clipboard format identifier as your ANSI program then you'll have encoding mismatches if you copy from one and paste into the other. You might be wise to register under a different clipboard format for your new Unicode format.
Some other comments:
Stream := nil;
try
Stream := TMemoryStream.Create;
...
finally
Stream.Free;
end;
should be written as:
Stream := TMemoryStream.Create;
try
...
finally
Stream.Free;
end;
If the constructor raises an exception, the try
block will not be entered.
You don't really need to write out the string length. You can rely on the stream size when reading to know how long the string is.
In CopyLine
, the clipboard Open
and Close
calls should be protected by a try/finally block.