juhara.com
Language : English Indonesia

Copy paste data from clipboard

Zamrony P Juhara
13 September 2006 11:43:00
 (2847 views)
This article explains how to copy custom data to or from clipboard

Introduction

This class I wrote to encapsulate copying custom data from or to clipboard. I was in a situation where, software I develop must have feature to copy or paste data in particular format to clipboard. After googling around, I found solution and started writing its implementation and encapsulated it into a class.

Copy data from or to clipboard.

Design

This class is derived from TPersistent and maintaining an internal buffer where its lifetime is managed by class. To make it flexible, this class is only responsible to copy data. Data format and how to interpret data is defined and implemented by application.

We need following units : classes, windows, clipbrd. We name class that we are going to develop TClipboardCopier. This class will have following methods:

  • Create (constructor)
  • Destroy (destructor)
Constructor and destructor contain internal buffer initialization code.
  • AssignTo(Dst:TPersistent)
  • Assign(src:TPersistent)
Assign and AssignTo of TPersistent is overriden so this class has ability to do assignment from and to clipboard. Data that is copied is taken from internal buffer. Assume clipCopier is instance of TClipboardCopier:
clipCopier.Assign(Clipboard);
Code above will copy clipboard content to clipCopier (paste operation). Or,
clipboard.Assign(clipCopier);
will copy content in clipCopier into clipboard (copy operation). AssignTo, just like in TPersistent, is protected, Assign is public.
  • AssignToClipboard(Dst:TClipboard)
  • AssignClipboard(src:TClipboard);
AssignToClipboard, AssignClipboard are procedures that handle actual data copy. Respectively, called by AssignTo and Assign.
  • procedure SaveToStream(Stream:TStream)
  • procedure LoadFromStream(Stream:TStream)

LoadFromStream and SaveToStream is used to copy data from and to internal buffer of instance of TClipboardCopier class.

We will define a property:

  • Format :word

Holds clipboard data format returned by Windows API, RegisterClipboardFormat(). Application must register clipboard format first.

Implementation

Complete listing is as follow:

unit uclip;

interface

uses classes,windows,clipbrd;

type TClipboardCopier=class(TPersistent)
     private
       FFormat:word;
       FBuffer:TMemoryStream;
       procedure AssignToClipboard(Dest:TClipboard);
       procedure AssignClipboard(source:TClipboard);
     protected
       procedure AssignTo(Dest:TPersistent);override;
     public
       constructor Create;
       destructor Destroy;override;
       procedure Assign(Source:TPersistent);override;

       procedure SaveToStream(Stream:TStream);
       procedure LoadFromStream(Stream:TStream);
     published
       property Format:word read FFormat write FFormat;
     end;

implementation

constructor TClipboardCopier.Create;
begin
  FBuffer:=TMemoryStream.Create;
end;

destructor TClipboardCopier.Destroy;
begin
  FBuffer.Free;
end;

procedure TClipboardCopier.AssignToClipboard(Dest:TClipboard);
var aDataHandle:THandle;
    aDataPtr:pointer;
begin
  if (Dest=nil) or (FFormat=0) then
    exit;

  //if Buffer is empty
  if FBuffer.Size=0 then
    exit;

 //open clipboard
  Dest.Open;
  try
    //make sure stream is at start
    FBuffer.Seek(0,soFromBeginning);

    //memori GMEM_DDESHARE is used for clipboard
    aDataHandle:=GlobalAlloc(GMEM_DDESHARE or
                             GMEM_MOVEABLE,FBuffer.Size);
    try
      //lock to get memory adrress from handle because
      //we use GMEM_MOVEABLE when GlobalAlloc()
      aDataPtr:=GlobalLock(aDataHandle);
      try
        if aDataPtr<>nil then
        begin
          MoveMemory(aDataPtr,FBuffer.Memory,FBuffer.Size);

          //clear clipboard content
          Dest.Clear;
          //set data. after setAsHAndle,
          //aDataHandle is owned by clipboard. 
          //we must not delete 
          Dest.SetAsHandle(FFormat,aDataHandle);
        end;
      finally
        GlobalUnLock(aDataHandle);
      end;
    except
      //if failed free handle
      GlobalFree(aDataHandle);
    end;
  finally
    //close
    Dest.Close;
  end;
end;

procedure TClipboardCopier.AssignClipboard(Source:TClipboard);
var aDataHandle:THandle;
    aDataPtr:pointer;
begin
  if (Source=nil) or (FFormat=0) then
    exit;

  //check clipboard if contain supported format.
  if Source.HasFormat(FFormat) then
  begin
    //opne clipboard
    Source.Open;
    try
      //get handle to clipboard data
      aDataHandle:=Source.GetAsHandle(FFormat);

      //lock to get memory address because
      //we use GMEM_MOVEABLE on GlobalAlloc()
      aDataPtr:=GlobalLock(aDataHandle);
      try
        FBuffer.Clear;
        FBuffer.Size:=GlobalSize(aDataHandle);
        //copy clipboard to Buffer
        MoveMemory(FBuffer.Memory,aDataPtr,FBuffer.Size);

      finally
        GlobalUnLock(aDataHandle);
      end;
    finally
      //close clipboard
      Source.Close;
    end;
  end;
end;

procedure TClipboardCopier.AssignTo(Dest:TPersistent);
begin
  if Dest is TClipboard then
    AssignToClipboard(Dest as TClipboard)
  else
    inherited AssignTo(Dest);
end;

procedure TClipboardCopier.Assign(Source:TPersistent);
begin
  if Source is TClipboard then
    AssignClipboard(Source as TClipboard)
  else
    inherited Assign(Source);
end;

procedure TClipboardCopier.LoadFromStream(Stream:TStream);
begin
  //copy data from Stream to Buffer
  FBuffer.Clear;
  if Stream.Size<0 then
    FBuffer.CopyFrom(Stream,Stream.Size);
end;

procedure TClipboardCopier.SaveToStream(Stream:TStream);
begin
  //copy data from Buffer to Stream
  //Caller must
  //set correct position of Stream
  FBuffer.Seek(0,soFromBeginning);
  if FBuffer.Size<0 then
    Stream.CopyFrom(FBuffer,FBuffer.Size);
end;

end.

Creating Demo Application

Next we create sample application to utilize TClipboardCopier class. Create main form with three TEdit controls, three TLabel controls and three TButton controls.

Change Name property of TEdit to edNama, edAlamat, edTelp. Change name of each TButton: btnCut, btnCopy, btnPaste. Change Caption of each TLabel to Nama, Alamat, Telp. Complete listing as follow:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    edNama: TEdit;
    Label1: TLabel;
    edAlamat: TEdit;
    Label2: TLabel;
    edTelp: TEdit;
    Label3: TLabel;
    btnCut: TButton;
    btnPaste: TButton;
    btnCopy: TButton;
    procedure btnCutClick(Sender: TObject);
    procedure btnCopyClick(Sender: TObject);
    procedure btnPasteClick(Sender: TObject);
  private
    clipCopier:TClipboardCopier;
    { Private declarations }
  public
    constructor Create(AOwner:TComponent);override;
    destructor Destroy;override;
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}
var CF_MyFormat:word;

constructor TForm1.Create(AOwner:TComponent);
begin
  inherited;
  clipCopier:=TClipboardCopier.Create;
  clipCopier.Format:=CF_MyFormat;
end;

destructor TForm1.Destroy;
begin
  clipCopier.Free;
  inherited;
end;

procedure WriteStringToStream(Stream:TStream;const
 str:string);
var len:integer;
begin
  len:=Length(Str);
  Stream.WriteBuffer(len,sizeof(integer));
  Stream.WriteBuffer(Str[1],len);
end;

procedure ReadStringFromStream(Stream:TStream;var
 str:string);
var len:integer;
begin
  Stream.ReadBuffer(len,sizeof(integer));
  SetLength(str,len);
  Stream.ReadBuffer(Str[1],len);
end;

procedure TForm1.btnCutClick(Sender: TObject);
begin
  btnCopyClick(sender);
 //kalau cut, clear edit
  edNama.Text:='';
  edAlamat.Text:='';
  edTelp.Text:='';
end;

procedure TForm1.btnCopyClick(Sender: TObject);
var aData:TMemoryStream;
begin
  adata:=TMemoryStream.Create;
  try
 //simpan data nama,alamant dan telp ke stream
    WriteStringToStream(aData,edNama.Text);
    WriteStringToStream(aData,edAlamat.Text);
    WriteStringToStream(aData,edTelp.Text);
 //kembalikan ke posisi awal
    aData.Seek(0,soFromBeginning);

 //kopi data ke buffer
    ClipCopier.LoadFromStream(aData);
  finally
 //bebaskan karena sudah tidak diperlukan
    adata.Free;
  end;

  Clipboard.Assign(clipCopier);
end;

procedure TForm1.btnPasteClick(Sender: TObject);
var aData:TMemoryStream;
    astr:string;
begin
  clipCopier.Assign(clipBoard);

  adata:=TMemoryStream.Create;
  try
    ClipCopier.SaveToStream(aData);
 //pastikan posisi stream berada diawal
    aData.Seek(0,soFromBeginning);

    ReadStringFromStream(aData,aStr);
    edNama.Text:=aStr;
    ReadStringFromStream(aData,aStr);
    edAlamat.Text:=aStr;
    ReadStringFromStream(aData,aStr);
    edTelp.Text:=aStr;
  finally
    adata.Free;
  end;
end;

initialization
 CF_MyFormat:=RegisterClipboardFormat('TutorialClipboardDelphi');
end.

To try it, fill each edit control with text. Press Cut or Copy button to copy and Paste to paste. After cut or copy, close application and run it again and press Paste. Each edit control will be filled with text copied from clipboard. This is easy example, but you can use TClipboardCopier to copy more complex data easily.

Monitoring Clipboard Content

If you pay attention, some applications is able to detect whether clipboard contains particular format and enable/disable menu Cut, Copy and Paste according to clipboard content. For second part, I will discuss how to monitor clipboard content, so we can tell whether clipboard content is changed.

Design

To be able to monitor clipboard, we must create window and add window handle to clipboard chain. After our window handle is in clipboard chain, we will receive WM_DRAWCLIPBOARD message when clipboard content is changed and WM_CLIPBOARDCHAIN when clipboard chain is changed.

We will encapsulate it into a class to simplify monitoring process i.e creating and adding window to clipboard chain. We will implement two event propertes that will be generated when clipboard content and clipboard chain are changed.

We need following unit: classes, windows, forms, messages. We also add following methods:

  • Create (constructor)
  • Destroy (destructor)

Construktor is used to create window and add window handle to clipboard chain. To create window, we use AllocateHwnd (declared in classes.pas). Destructor is used to remove window handle from clipboard chain and to destroy handle window (DeallocateHwnd). To add a window to clipboard chain we call SetClipboardViewer(). This function requires one parameter which is window handle to be added. It returns next window in clipboard chain which we must keep. To remove it from clipboard chain we call ChangeClipboardChain(). This requires two parameters, first is window handle to delete and second is next window handle in clipboard chain.

  • WindowProc, Window handler that will receive WM_DRAWCLIPBOARD and WM_CLIPBOARDCHAIN.

We add two properties:

  • OnDrawClipboard with type of TNotifyEvent. Generated each time clipboard content is changed.
  • OnClipboardChainChange, type of TNotifyEvent. Generated each time clipboard chain is changed.

When handle WM_CLIPBOARDCHAIN, we must update next window handle in clipboard chain. This data is kept in msg.next where msg is type of TWMChangeCBChain record.

Implementation

Ok complete listing is as follow:

unit uclipboardviewer;

interface

uses classes,windows,messages,sysutils,clipbrd;

type
{=======================================
 Kelas abstraksi proses monitoring content
 clipboard
 ========================================
 (c) 2005-2006 juhara.com
 ========================================}
      TClipboardViewer=class(TComponent)
      private
         FNextChainView,FHandle:HWND;
         FOnDrawClipboard,FOnClipboardChainChange:TNotifyEvent;
         procedure WMDrawClipboard(var msg:TWMDrawClipboard);
         procedure WMChangeCBChain(var msg:TWMChangeCBChain);
         procedure _OnDrawClipboard(sender:TObject);
         procedure _OnChangeCBChain(sender:TObject);
      protected
         procedure DoDrawClipboard(sender:TObject);virtual;
         procedure DoChangeCBChain(sender:TObject);virtual;
         procedure WndProc(var Msg: TMessage);virtual;
      public
         constructor Create(AOwner:TComponent);override;
         destructor Destroy;override;
      published
         //generated when clipboard content is changed
         property OnDrawClipboard:TNotifyEvent read FOnDrawClipboard 
                                    write FOnDrawClipboard;
         //generated when clipboard chain is changed
         property OnClipboardChainChange:TNotifyEvent 
                                 read FOnClipboardChainChange
                                 write FOnClipboardChainChange;
      end;

implementation

{ TClipboardViewer }
procedure TClipboardViewer.WndProc(var Msg: TMessage);
begin
  case msg.msg of
   WM_DRAWCLIPBOARD:begin
                      WMDrawClipboard(TWMDrawClipboard(msg));
                    end;
   WM_CHANGECBCHAIN:begin
                      WMChangeCBChain(TWMChangeCBChain(msg));
                    end;
   else
    DefWindowProc(FHandle, msg.Msg, msg.wParam, msg.lParam);
  end;
end;

constructor TClipboardViewer.Create;
begin
  inherited Create(AOwner);
  FOnDrawClipboard:=nil;
  FOnClipboardChainChange:=nil;
  FHandle:=classes.AllocateHWnd(WndProc);
  FNextChainView:=SetClipboardViewer(FHandle);
end;

destructor TClipboardViewer.Destroy;
begin
  ChangeClipboardChain(FHandle,FNextChainView);
  classes.DeallocateHwnd(FHandle);
  inherited;
end;

procedure TClipboardViewer.WMDrawClipboard;
begin
  _OnDrawClipboard(self);
  SendMessage(FNextChainView,WM_DRAWCLIPBOARD,0,0);
end;

procedure TClipboardViewer.WMChangeCBChain;
begin
  _OnChangeCBChain(self);
  if FNextChainView<0 then
  begin
    if msg.Remove<FNextChainView then
      SendMessage(FNextChainView,WM_CHANGECBCHAIN,
                    msg.remove,msg.next)
    else
     FNextChainView:=msg.Next;
  end;
  msg.Result:=0;
end;

procedure TClipboardViewer.DoDrawClipboard;
begin
 //default:nothing
end;

procedure TClipboardViewer.DoChangeCBChain;
begin
 //default:nothing
end;

procedure TClipboardViewer._OnDrawClipboard;
begin
  DoDrawClipboard(self);
  if Assigned(FOnDrawClipboard) then
    FOnDrawClipboard(self);
end;

procedure TClipboardViewer._OnChangeCBChain;
begin
  DoChangeCBChain(self);  
  if Assigned(FOnClipboardChainChange) then
    FOnClipboardChainChange(self);
end;

end.

To monitor cipboard content, we need to handle OnDrawClipboard event. To get notified when clipboard chain is changed, you should handle OnClipboardChainChange event. In OnDrawClipboard handler, if clipboard contains data in supported format we enable Paste menu. For example:

procedure TForm1.DoOnDrawClipboard(sender:TObject);
begin
  PasteMenu.Enabled:=Clipboard.HasFormat(CF_MyFormat);
end;

Ok that's all about clipboard. Have a nice coding session...

Related Article

Do you like this article? Help this website improve by donating. Any amounts is appreciated.

Or you can help by bookmarking this page. Delicious Bookmark this on Delicious