









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.
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:
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.
LoadFromStream and SaveToStream is used to copy data from and to internal buffer of instance of TClipboardCopier class.
We will define a property:
Holds clipboard data format returned by Windows API, RegisterClipboardFormat(). Application must register clipboard format first.
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.
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.
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.
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:
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.
We add two properties:
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.
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...
Do you like this article? Help this website improve by donating. Any amounts is appreciated.
Or you can help by bookmarking this page.
Bookmark this on Delicious