Thursday, February 24, 2005

Delphi: Get the RTF for a paragraph in a rich edit

To the best of my knowledge, the only way to get the RTF for a portion of the contents in a rich edit control---without changing the selection---is as follows:
  1. Obtain an ITextRange as described in a previous post.

  2. Call ITextRange.Copy(), passing it a pointer to a variant. (Otherwise it will copy the text to the clipboard.)

  3. The variant should now contain an IDataObject interface pointer.

  4. Call IDataObject.GetData() requesting CF_RTF.

I tried to follow this plan, passing an Unassigned Variant to ITextRange.Copy(), but found that it was still Unassigned afterwards. I decided to get hold of proper declarations for ITextRange so that I need not go via Variant IDispatch. I found tom_TLB.pas on a Japanese site and took a copy. The file had obviously been produced by some automatic means. It declared ITextRange.Copy() as:

procedure Copy(out pVar: OleVariant); safecall;

Sadly, using tom_TLB.pas did not help: my variant was still Unassigned. I noticed in the documentation for ITextRange.Copy() has some strange requirements for its argument:
pVar->vt = (VT_UNKNOWN | VT_BYREF)
pVar is not null
pVar->ppunkVal is not null

By tracing into Delphi's call I found that Delphi was clearing my Variant and setting it to Unassigned on the way in. Clearly it could never satisfy the requirement that pVar->vt = (VT_UNKNOWN | VT_BYREF). To work around this, I build my variant argument manually:
  1. Change the declaration of ITextRange.Copy to:
    procedure Copy(pVar: PVariantArg); safecall;

  2. Construct the argument manually:
    var
    data_var: IUnknown;
    data_var_arg: TVariantArg;
    begin
    data_var := range.FormattedText; // Copy range to ensure no interference.
    data_var_arg.vt := VT_UNKNOWN or VT_BYREF;
    data_var_arg.wReserved1 := 0;
    data_var_arg.wReserved2 := 0;
    data_var_arg.wReserved2 := 0;
    data_var_arg.punkVal := @data_var;
    range.Copy(@data_var_arg);

  3. Your IDataObject will now be in data_var:
    OleCheck(IUnknown(data_var).QueryInterface(IDataObject, data));

Id love to know of a simpler way.

4 Comments:

Anonymous vitos said...

Here you go:
function GetDataObjectForRange( Range : ITextRange; var ADataObject : IDataObject) : HRESULT;
var
AIn : IUnknown;
AInVar : OleVariant;
begin
with TVarData(AInVar) do begin
VType := VT_UNKNOWN or VT_BYREF;
VPointer := @AIn;
end;
Range.Copy(AInVar);
Result := AIn.QueryInterface(IDataObject, ADataObject);
end;

3:49 am  
Anonymous Anonymous said...

This theme is simply matchless :), it is interesting to me)))

4:31 am  
Anonymous Anonymous said...

It agree, a remarkable idea

4:08 am  
Anonymous Anonymous said...

Hi,
procedure Copy(out pVar: OleVariant); safecall; does not work
but
procedure Copy(var pVar: OleVariant); safecall; works.

So,
var
pp : Olevariant;
begin
...
data_var := textRange.FormattedText;
TVariantArg(pp).vt := VT_UNKNOWN or VT_BYREF;
TVariantArg(pp).punkVal := @data_var;
textRange.Copy(pp);

Its all right.

3:21 pm  

Post a Comment

<< Home