Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2017年02月24日] [更新 2018年02月21日] Delphi サンプルプログラム集
825_TClipboard.SetAsHandle を使用したファイルのコピー
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro 他
825_FilesCooyToClipboard.zip [1,109 KB] 2018年02月21日版 (EXE 同梱)







01_TClipboard.SetAsHandle を使用したファイルのコピー


文字列,テキストや画像は,TClipboard.Assign を使用すればクリップボードにコピーできますが,エクスプローラのようなファイルのコピーは TClipboard.Assign ではできません.
コンテキストメニューコンポーネントのように,シェルの機能を使用するとファイルのコピーが可能ですが,次のように,TClipboard.SetAsHandle を使用してもコピーできます.
Delphi 6 以降で使用可能なコードにしてみました.


[備考 1]
当サイトで提供しているコンテキストメニューコンポーネントを使用する場合は,コンテキストメニューを表示しないで [コピー] コマンドを実行します.コンテキストメニューコンポーネントを使用すると,コピー,貼り付け,切り取り等の操作も可能です.操作対象となるフォルダ名やファイル名は,Files プロパティにセットします.

ドラッグ・アンド・ドロップの記事にもコンポーネントを使用する例ですが,ファイルのコピーのサンプルコードがあります.


[備考 2]
本サンプルでは,文字列の格納に動的な文字列配列を使用していますが,このような場合,当サイトのほとんどのサンプルでは TStringList を使用しています.
「動的な配列を関数の引数として使うには」という質問を見かけたので使ってみただけです.



図1
設計時画面
図2
本サンプルでの操作対象のフォルダとファイル
  • [DDD] フォルダと AA1.txt ファイル
  • コピーすると,エクスプローラで任意の場所に貼り付けできる

リスト1
TClipboard.SetAsHandle を使用したファイルのコピー
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  ShlObj,
  ClipBrd;

{$R *.dfm}

//-----------------------------------------------------------------------------
//  引数の文字列配列(ファイル名のリスト)のファイルをクリップボードに送る
//  クリップボードの形式はCF_HDROP
//
//  usesにShlObj,ClipBrdが必要
//-----------------------------------------------------------------------------
procedure CopyFilesToClipboard(const FileNames: array of string);
var
  LFileListText : string;
  LuFlags       : Cardinal;
  LpDropFiles   : PDropFiles;
  LhGlobal      : THandle;
  LLen          : Integer;
  i             : Integer;
begin
  Clipboard.Open;
  Clipboard.Clear;

  //CF_HDROP形式のファイルのコピーは,ファイル名の末尾に#0を付加
  //最後は#0#0とする
  LFileListText := '';
  for i := 0 to Length(FileNames) - 1 do begin
    if Trim(FileNames[i]) <> '' then begin
      LFileListText := LFileListText + FileNames[i] + #0;
    end;
  end;
  LFileListText := LFileListText + #0;

  {$IFDEF UNICODE}
  LLen := ByteLength(LFileListText);
  {$ELSE}
  LLen := Length(LFileListText);
  {$ENDIF}
  if Length(Trim(LFileListText)) = 0 then exit;

  LuFlags  := GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT;
  LhGlobal := GlobalAlloc(LuFlags, SizeOf(TDropFiles) + LLen);

  if (LhGlobal = 0) then begin
    //エラー
  end else begin
    try
      LpDropFiles := GlobalLock(LhGlobal);
      if (LpDropFiles = nil) then begin
        //エラー
      end else begin
        try
          FillChar(LpDropFiles^, SizeOf(TDropFiles), #0);
          LpDropFiles^.pFiles := SizeOf(TDropFiles);
          {$IFDEF UNICODE}
          LpDropFiles^.fWide  := True;
          {$ENDIF}

         {$IF CompilerVersion >= 20.0}
          Move(LFileListText[1],
               (PByte(LpDropFiles) + SizeOf(TDropFiles))^,
               LLen);
          {$ELSE}
            //このコードは{$IF CompilerVersion >= 20.0}でも動作する
           Move(LFileListText[1],
                PByte(Integer(LpDropFiles) + SizeOf(TDropFiles))^,
                LLen);
          {$IFEND}
        finally
          GlobalUnlock(LhGlobal);
        end;
        Clipboard.SetAsHandle(CF_HDROP, LhGlobal);
        Clipboard.Close;
      end;
    except
      GlobalFree(LhGlobal);
    end;
  end;
end;

//=============================================================================
//  コピーするファイル名のセット
//  文字列配列は他にもいろいろあるので適当に
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LFileNameArray : array of string;
begin
  SetLength(LFileNameArray, 2);

  //ファイル名のチェックはなし
  //ファイルが1つでも存在しないとファイルを誤認識してコピーしない
  LFileNameArray[0] := ExpandFileName('..\CCC\DDD');       //フォルダ
  LFileNameArray[1] := ExpandFileName('..\CCC\AA1.txt');   //ファイル

  CopyFilesToClipboard(LFileNameArray);
end;

end.


Delphi XE の場合,TClipboard.SetAsHandle は以下のように Windows API の SetClipboardData 関数でクリップボードにデータを送っています.
同様に,TClipboard.Assign も,最終的には SetClipboardData 関数を使用してクリップボードにデータを送っています.


procedure TClipboard.SetAsHandle(Format: Word; Value: THandle);
begin
  Open;
  try
    Adding;
    SetClipboardData(Format, Value);
  finally
    Close;
  end;
end;




02_TClipboard.SetAsHandle を使用した文字列のコピー


TClipboard.SetAsHandle を使用してクリップボードに文字列をセットする際は注意が必要です.
Delphi 2009 以降の Delphi の String 型はユニコード文字列です.クリップボードのフォーマットとして CF_UNICODETEXT を使用して.バッファの領域として確保するバイト数にも注意します.
CF_TEXT で文字列をコピーする時は,文字列の型を AnsiString 型にします.


リスト2
TClipboard.SetAsHandle を使用した文字列のコピー
CF_TEXT は AnsiString 型の文字列用.ユニコードな文字列には CF_UNICODETEXT を使用する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls;

type
  TForm1 = class(TForm)
    Button2: TButton;
    Button3: TButton;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  Clipbrd;

{$R *.dfm}

//=============================================================================
//  TClipboard.AsTextはユニコード文字列を処理してくれる
//  ClipboardはTClipboardのインスタンス
//  usesにClipbrdを追加すると自動で生成される
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  LText := '日本語 ABC';

  Clipboard.Clear;
  Clipboard.AsText := LText;
end;

//=============================================================================
//  CF_TEXTはAnsiString用
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LhGlobale : HGLOBAL;
  LPChar    : PAnsiChar;
  LText     : Ansistring;
begin
  LText := '日本語 ABC' + #0;

  Clipboard.Open;
  Clipboard.Clear;

  LhGlobale := GlobalAlloc(GHND, Length(LText));
  LPChar    := GlobalLock(LhGlobale);
  StrPCopy(LPChar, LText);

  GlobalUnlock(LhGlobale);
  Clipboard.SetAsHandle(CF_TEXT, LhGlobale);
  GlobalFree(LhGlobale);

  Clipboard.Close;
end;

//=============================================================================
//  ユニコードな文字列にはCF_UNICODETEXTを使用する
//  クリップボードにセットするバイト数に注意
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  LhGlobale : HGLOBAL;
  LPChar    : PChar;
  LText     : string;
begin
  LText := '日本語 ABC' + #0;

  Clipboard.Open;
  Clipboard.Clear;

  //以下のようにByteLengthも使用可能
  //LhGlobale := GlobalAlloc(GHND, ByteLength(LText));
  LhGlobale := GlobalAlloc(GHND, Length(LText) * SizeOf(Char));
  LPChar    := GlobalLock(LhGlobale);
  StrPCopy(LPChar, LText);

  GlobalUnlock(LhGlobale);
  Clipboard.SetAsHandle(CF_UNICODETEXT, LhGlobale);
  GlobalFree(LhGlobale);

  Clipboard.Close;
end;

end.