Skip to content
chrisrolliston edited this page Jun 7, 2015 · 1 revision

'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.

Behaviour when no platform support

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.

Caching

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.

Flushing

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;

It's not working!

If using delayed rendering on a platform that supports it you find renderers being called immediately whatever you do, check the following:

  1. 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;

  2. 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).

  3. 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.