-
Notifications
You must be signed in to change notification settings - Fork 15
Delayed Rendering
'Delayed rendering' means promising data in a given format rather than giving data to the clipboard up front. This can be particularly useful if the same data is made available in several different formats, since instead of creating them all at the same time, the clipboard can just be told that the data in formats x, y and z can be provided on request.
To do this, callan appropriate AssignDelayed or AssignXXXDelayed overload on TClipboard, passing a callback method to provide the data when needed. For example, to assign delay-rendered custom data represented by TBytes, you can do this:
Clipboard.AssignDelayed(cfMyCustomFormat
function : TBytes
begin
Result := //...
end);
Delay-rendered text is done similarly:
Clipboard.AssignTextDelayed(
function : string
begin
Result := //...
end);
In contrast, a delay-rendered object takes a little more thought due to memory management issues under traditional, non-ARC compilers. One option is to return an object whose lifetime is managed by (say) a form:
type
TMyForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
strict private
FRenderedBitmap: TBitmap;
procedure CopyBitmap;
end;
procedure TMyForm.FormCreate(Sender: TObject);
begin
FRenderedBitmap := TBitmap.Create;
end;
procedure TMyForm.FormDestroy(Sender: TObject);
begin
FRenderedBitmap.Free;
end;
procedure TMyForm.CopyBitmap;
begin
Clipboard.AssignDelayed<TBitmap>(
function : TBitmap
begin
FRenderedBitmap.SetSize({ ... });
//... drawing stuff
Result := FRenderedBitmap;
end;
end);
end;
Another option is to call an AssignDelayed overload that takes a procedural method that passes your callback an object, managed by TClipboard, to render:
procedure TMyForm.CopyBitmap;
begin
Clipboard.AssignDelayed<TBitmap>(
procedure (Bitmap: TBitmap)
begin
Bitmap.SetSize({ ... });
//... drawing stuff
end);
end;
This second way internally uses the TRenderedObject<T> helper type that the CCR.Clipboard unit defines. This type can also be used directly, for example when the same delay-rendered object is assigned to the clipboard both as itself and a virtual file source:
procedure TMyForm.CopyBitmap;
var
BitmapGetter: TFunc<TBitmap>;
begin
BitmapGetter := TRenderedObject<TBitmap>.Create(
procedure (Bitmap: TBitmap)
begin
Bitmap.SetSize({ ... });
//... drawing stuff
end);
Clipboard.Open;
try
Clipboard.AssignDelayed<TBitmap>(BitmapGetter);
Clipboard.AssignVirtualFileDelayed<TBitmap>('Example.png', BitmapGetter);
finally
Clipboard.Close;
end;
end;
If both a bitmap and virtual file end up getting pasted, the drawing code will only execute once; conversely, if neither get pasted, then it won't ever be called.
If the platform does not support delay rendering, either in general (iOS) or for specific formats, the AssignDelayed methods can still be called - the callbacks passed will just get invoked immediately. This shouldn't affect program logic since such behaviour is always possible anyhow, given a second application may request data immediately after the first has put it on the clipboard.
On OS X the system will only ever ask for delay rendered data once. As such, were you to put (say) delay rendered plain text on the clipboard and two applications come to ask for it, your rendering method will be called on the first request, the system will cache the result, then on the second request call on this cache rather than your callback method.
On Windows, in contrast, the system performs no special caching. Nonetheless, where it can, the Windows specific code for TClipboard does so instead. The only cases where it won't is where you call an overload for either AssignDelayed or AssignVirtualFileDelayed that takes a TStream renderer for input:
Clipboard.AssignVirtualFileDelayed('Test.log',
function : TBytes
begin
Result := BytesOf('I will be called a maximum of one time');
end);
Clipboard.AssignVirtualFileDelayed('Test.log',
procedure (Stream: TStream)
var
Bytes: TBytes;
begin
Bytes := BytesOf('I may be called any number of times');
Stream.WriteBuffer(Bytes[0], Length(Bytes));
end);
This is due to Windows supporting streamed data natively (in API terms, IDataObject may provide data in the form of an IStream), which the TStream overloads in TClipboard provide a direct abstraction from.
By default, when a TClipboard instance is destroyed any outstanding delay renders will be cancelled. This is due to the danger of stale references by the time destruction happens, which is particularly acute for the singleton returned by the Clipboard global function. Because of this, when delay rendering is used, you may wish to prompt the user to flush the clipboard when the application closes, or alternatively, just flush without prompting at a point when stale references won't yet be an issue:
procedure TMyForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if Clipboard.HasOutstandingPromisesToOS then
if FAlwaysResolvePromisesOnExit then
Clipboard.ResolveOutstandingPromisesToOS
else
case MessageDlg('Flush pending items?',
TMsgDlgType.mtConfirmation, mbYesNoCancel, 0) of
mrYes: Clipboard.ResolveOutstandingPromisesToOS;
mrNo: Clipboard.CancelOutstandingPromisesToOS;
else
CanClose := False;
end;
end;
If using delayed rendering on a platform that supports it you find renderers being called immediately whatever you do, check the following:
-
Check there are no action OnUpdate handlers that test for the existence of data by attempting to read it and seeing if anything is returned:
procedure TMyForm.actFooUpdate(Sender: TObject); begin actFoo.Enabled := Clipboard.AsText <> '' //!!!should use Clipboard.HasText end;
-
If running in (or with) a VM, the VM software's clipboard interchange functionality may call on certain formats immediately on their being set in order to put the data on the host or guest's clipboard too. E.g., VirtualBox supports clipboard interchange for plain text, so for delay- rendered text assignments to work as intended this needs to be turned off (Devices|Shared Clipboard|Disabled).
-
File name renderers will be called immediately if you have an OnUpdate handler that tests for whether a TBitmap (or in the VCL, other graphic class) can be read from the clipboard. This is due to the standard 'clippers' for these classes looking for readable files on the clipboard as well as readable in-memory formats.