Domanda

I need to create the following formats all together on the clipboard:

CF_BITMAP
CF_DIB
CF_DIB5
HTML Format

This is a console program which can create either the picture formats OR the HTML Format, but not all together on the clipboard:

program CopyImageFromFile;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Winapi.Windows,
  Vcl.Clipbrd,
  Vcl.ExtCtrls,
  Vcl.Imaging.pngimage,
  System.SysUtils;

function FormatHTMLClipboardHeader(HTMLText: string): string;
const
  CrLf = #13#10;
begin
  Result := 'Version:0.9' + CrLf;
  Result := Result + 'StartHTML:-1' + CrLf;
  Result := Result + 'EndHTML:-1' + CrLf;
  Result := Result + 'StartFragment:000081' + CrLf;
  Result := Result + 'EndFragment:°°°°°°' + CrLf;
  Result := Result + HTMLText + CrLf;
  Result := StringReplace(Result, '°°°°°°', Format('%.6d', [Length(Result)]), []);
end;

procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
  gMem: HGLOBAL;
  lp: PChar;
  Strings: array[0..1] of AnsiString;
  Formats: array[0..1] of UINT;
  i: Integer;

  ThisImage: TImage;
  MyFormat: Word;
  Bitmap: TBitMap;
  AData: THandle;
  APalette: HPALETTE;
begin
  gMem := 0;
  //{$IFNDEF USEVCLCLIPBOARD}
  //Win32Check(OpenClipBoard(0));
  //{$ENDIF}
  Clipboard.Open;
  try
    //most descriptive first as per api docs
    Strings[0] := FormatHTMLClipboardHeader(htmlStr);
    Strings[1] := str;
    Formats[0] := RegisterClipboardFormat('HTML Format');
    Formats[1] := CF_TEXT;
    {$IFNDEF USEVCLCLIPBOARD}
    Win32Check(EmptyClipBoard);
    {$ENDIF}
    for i := 0 to High(Strings) do
    begin
      if Strings[i] = '' then Continue;
      //an extra "1" for the null terminator
      gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(Strings[i]) + 1);
      {Succeeded, now read the stream contents into the memory the pointer points at}
      try
        Win32Check(gmem <> 0);
        lp := GlobalLock(gMem);
        Win32Check(lp <> nil);
        CopyMemory(lp, PChar(Strings[i]), Length(Strings[i]) + 1);
      finally
        GlobalUnlock(gMem);
      end;
      Win32Check(gmem <> 0);
      SetClipboardData(Formats[i], gMEm);
      Win32Check(gmem <> 0);
      gmem := 0;
    end;

    ThisImage := TImage.Create(nil);
    try
      ThisImage.Picture.LoadFromFile(APngFile);
      // Comment this out to copy only the HTML Format:
      Clipboard.Assign(ThisImage.Picture);
      {MyFormat := CF_PICTURE;
      ThisImage.Picture.SaveToClipBoardFormat(MyFormat, AData, APalette);
      ClipBoard.SetAsHandle(MyFormat, AData);}
    finally
      ThisImage.Free;
    end;
  finally
    //{$IFNDEF USEVCLCLIPBOARD}
    //Win32Check(CloseClipBoard);
    //{$ENDIF}
    Clipboard.Close;
  end;
end;

var
  HTML: string;

begin
  try
    // Usage: CopyImageFromFile.exe test.png
    // test.png is 32 bit with alpha channel
    if ParamCount = 1 then
    begin
      if FileExists(ParamStr(1)) then
      begin
        if LowerCase(ExtractFileExt(ParamStr(1))) = '.png' then
        begin
          HTML := '<img border="0" src="file:///' + ParamStr(1) + '">';
          CopyHTMLAndImageToClipBoard('test', ParamStr(1), HTML);
        end;
      end;
    end;
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      Readln;
    end;
  end;

end.  

So how can I create all these formats together on the clipboard?

È stato utile?

Soluzione

TClipboard empties the clipboard the first time you use a TClipboard method to put data on the clipboard (TClipboard.Assign(), TClipboard.SetBuffer(), TClipboard.SetAsHandle(), etc) after calling Open(). TClipboard expects you to use only its methods for accessing the clipboard, so your use of SetClpboardData() directly to store your string data is bypassing TClipboard's internal logic, thus your call to Assign() is seen as the first clipboard write and TClipboard wipes out any data you stored with SetClipboardData().

To avoid that, you have a few choices:

  1. Assign() your image to the clipboard first, then save your string items with SetClipboardData() afterwards.

  2. don't use Assign() at all. Use TPicture.SaveToClipboardFormat() directly and then call SetClipboardData().

  3. don't use SetClipboardData() directly unless USEVCLCLIPBOARD is not defined. Use TClipboard.SetAsHandle() instead.

I would suggest #3. Let TClipboard do all of the work:

var
  CF_HTML: UINT = 0;

// TClipboard.SetBuffer() allows a format and an arbitrary buffer
// to be specified and handles the global memory allocation.
// However, it is protected, so using an accessor class to reach it.
//
// TClipboard.AsText and TClipboard.SetTextBuf() always use
// CF_(UNICODE)TEXT, and TClipboard.SetAsHandle() requires manual
// allocation...
//
type
  TClipboardAccess = class(TClipboard)
  end;

procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
  TmpHtmlStr: AnsiString;
  ThisImage: TPicture;
begin
  Clipboard.Open;
  try
    //most descriptive first as per api docs

    TmpHtmlStr := FormatHTMLClipboardHeader(htmlStr);
    TClipboardAccess(Clipboard).SetBuffer(CF_HTML, PAnsiChar(TmpHtmlStr)^, Length(TmpHtmlStr) + 1);
    TClipboardAccess(Clipboard).SetBuffer(CF_TEXT, PAnsiChar(Str)^, Length(Str) + 1);

    ThisImage := TPicture.Create;
    try
      ThisImage.LoadFromFile(APngFile);
      Clipboard.Assign(ThisImage);
    finally
      ThisImage.Free;
    end;
  finally
    Clipboard.Close;
  end;
end;

initialization
  CF_HTML := RegisterClipboardFormat('HTML Format');

If you really need to support {$IFNDEF USEVCLCLIPBOARD} then you cannot use TClipboard at all, eg:

var
  CF_HTML: UINT = 0;

{$IFDEF USEVCLCLIPBOARD}
// TClipboard.SetBuffer() allows a format and an arbitrary buffer
// to be specified and handles the global memory allocation.
// However, it is protected, so using an accessor class to reach it.
//
// TClipboard.AsText and TClipboard.SetTextBuf() always use
// CF_(UNICODE)TEXT, and TClipboard.SetAsHandle() requires manual
// allocation...
//
type
  TClipboardAccess = class(TClipboard)
  end;
{$ENDIF}

procedure CopyHTMLAndImageToClipBoard(const str, APngFile: AnsiString; const htmlStr: AnsiString = '');
var
  ThisImage: TPicture;
  {$IFNDEF USEVCLCLIPBOARD}
  ImgData: THandle;
  ImgFormat: Word;
  ImgPalette: HPALETTE;
  {$ENDIF}

  procedure SetAsText(Format: UINT; const S: AnsiString);
  {$IFNDEF USEVCLCLIPBOARD}
  var
    gMem: HGLOBAL;
    lp: PAnsiChar;
  {$ENDIF}
  begin
    {$IFDEF USEVCLCLIPBOARD}
    TClipboardAccess(Clipboard).SetBuffer(Format, PAnsiChar(S)^, Length(S) + 1);
    {$ELSE}
    //an extra "1" for the null terminator
    gMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, Length(S) + 1);
    Win32Check(gmem <> 0);
    try
      {Succeeded, now read the stream contents into the memory the pointer points at}
      lp := GlobalLock(gMem);
      Win32Check(lp <> nil);
      try
        CopyMemory(lp, PAnsiChar(S), Length(S) + 1);
      finally
        GlobalUnlock(gMem);
      end;
    except
      GlobalFree(gMem);
      raise;
    end;
    SetClipboardData(Format, gMem);
    {$ENDIF}
  end;

begin
  {$IFDEF USEVCLCLIPBOARD}
  Clipboard.Open;
  {$ELSE}
  Win32Check(OpenClipBoard(0));
  {$ENDIF}
  try
    //most descriptive first as per api docs
    SetAsText(CF_HTML, FormatHTMLClipboardHeader(htmlStr));
    SetAsText(CF_TEXT, Str);

    ThisImage := TPicture.Create;
    try
      ThisImage.LoadFromFile(APngFile);

      {$IFDEF USEVCLCLIPBOARD}
      Clipboard.Assign(ThisImage);
      {$ELSE}
      ImgPalette := 0;
      ThisImage.SaveToClipboardFormat(ImgFormat, ImgData, ImgPalette);
      SetClipboardData(ImgFormat, ImgData);
      if ImgPalette <> 0 then
        SetClipboardData(CF_PALETTE, ImgPalette);
      {$ENDIF}
    finally
      ThisImage.Free;
    end;
  finally
    {$IFDEF USEVCLCLIPBOARD}
    Clipboard.Close;
    {$ELSE}
    Win32Check(CloseClipBoard);
    {$ENDIF}
  end;
end;

initialization
  CF_HTML := RegisterClipboardFormat('HTML Format');

Altri suggerimenti

David is right. You need to have one pair of open/close, and only one EmptyClipboard. You need to iterate through your formats and call SetClipboardData for each one. RegisterClipboardFormat should only be called once, so do that in some initialization routine.
I would also try to avoid doing any file I/O once you've opened the clipboard, as you don't want to hold it open longer than necessary. i.e. read your pictures from disk first, if possible.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top