應用程序間的數據交換是象Windows 這樣的多任務環境的重要特性。作為一種基於Windows的開發工具,Delphi支持如下四種數據交換方式:剪貼板、動態數據交換 ( DDE)、對象聯接與嵌入(OLE)以及動態聯接庫(DLLs)。這中間前三種方式最為常用,OLE功能最為強大,DDE次之。而剪貼板使用最為方便。在本章,我們只討論剪貼板和動態數據交換。利用OLE實現數據交換見下一章,利用動態聯接庫(DLLs)進行數據交換將在第十章中介紹。
7.1 剪貼板及其應用
本質上,剪貼板只是一個全局內存塊。當一個應用程序將數據傳送給剪貼板後,通過修改內存塊分配標志,把相關內存塊的所有權從應用程序移交給Windows自身。其它應用程序可以通過一個句柄找到這個內存塊,從而能夠從內存塊中讀取數據。這樣就實現了數據在不同應用程序間的傳輸。
剪貼板雖然功能較為簡單,且不能實現實時傳輸,但卻是更為復雜的DDE和OLE的基礎。對於一些只是偶爾需要使用其它應用程序數據的程序來說,使用剪貼板不失為一種方便、快捷的方式。
Delphi把剪貼板的大部分功能封裝到一個TClipboard類中,同時把使用頻度最高的文本傳輸功能(包括DBImage的圖像傳輸功能)置入相應部件作為部件的方法,從而使用戶可以十分方便地使用剪貼板進行編程。
7.1.1 使用剪貼板傳輸文本
剪貼板傳輸文本主要是應用如下的三個方法:CopyToClipboard、CutToClipboard 和PasteFromClipboard。包含這些方法的部件如下表所示。
表7.1 包含剪貼板方法的部件
━━━━━━━━━━━━━━━━━━━━━━━━━━━
方 法 部 件
———————————————————————————
TDBEdit TDBMemo
TDBImage
CopyToClipboard TEdit TMemo TMaskEdit
TOLEContainer
TDDEServerItem
———————————————————————————
TDBEdit TDBMemo
CutToClipboard TDBImage
TEdit TMemo TMaskEdit
———————————————————————————
TDBEdit TDBMemo
PasteFromClipboard TDBImage
TEdit TMemo TMaskEdit
━━━━━━━━━━━━━━━━━━━━━━━━━━━
除TDBImage外,其余全是有關文本的控件。
在把文本傳輸到剪貼板之前,文本必須被選中。
若選TMaskEdit的AutoSelect屬性為True,則當MaskEdit獲得輸入焦點時文本自動被選中;若選TEdit、TMemo的HideSelection屬性為True,則失去焦點時,文本選中狀態自動隱藏,重新獲得焦點時再顯示。
下面的語句把MaskEdit中選中的文本剪切到剪貼板:
MaskEdit .CutToClipboard;
下面的語句把剪貼板中的文本粘貼到Memo的當前光標處:
Memo.PasteFromClipboard;
利用剪貼板類也可以實現文本的傳輸,見(7.1.2)中的介紹。
7.1.2 剪貼板類
為方便剪貼板的操作,Delphi在Clipbrd庫單元中定義了一個TClipboard類,並且預定義了一個變量Clipboard作為類TClipboard的實例,從而使用戶在絕大多數場合不必自己去定義一個TClipboard的實例。
利用剪貼板類可以進行文本、圖像和部件的傳輸,剪貼板類為實現這些方法提供了相應的屬性和方法。表7.2、表7.3列出了TClipboard屬性和方法的意義。
表 7.2 TClipboard的屬性
━━━━━━━━━━━━━━━━━━━━━━━━━━━
屬 性 意 義
───────────────────────────
AsText 保存剪貼板的文本,只有運行時才可設置
FormatCount 可用剪貼板格式的數目
Formats 可用剪貼板格式鏈
━━━━━━━━━━━━━━━━━━━━━━━━━━━
表 7.3 TClipboard的方法
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
方 法 參 數 意 義
─────────────────────────────────────
Clear 無 清除剪貼板的內容
Assign Source:TPersistent 把Source參數指定的對象拷貝到剪貼板,常
用於圖形、圖像對象
Open 無打開剪貼板,阻止其它應用程序改變它的內容
Close 無 關閉打開的剪貼板
SetComponent Source:TPersistent 把部件拷貝到剪貼板
GetComponent Owner 從剪貼板取回一個部件並放置
Parent :TPersistent
SetAsHandle Format:Word 把指定格式數據的句柄交給剪貼板
返回類型:THandle
GetAsHandle Format:Word 返回剪貼板指定格式數據的句柄
返回類型:THandle
HasFormat Format:Word 判斷剪貼板是否擁有給定的格式
返回類型:Boolean
SetTextBuf Buffer:PChar 設置剪貼板的文本內容
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
剪貼板中可能的數據格式如下表。
表 7.4 剪貼板數據格式及其意義
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
數據格式 意 義
──────────────────────────────
CF_TEXT 文本。每行以CF_LF結束,nil標志文本結束
CF_BITMAP Windows位圖
CF_METAFILE Windows元文件
CF_PICTURE TPicture類型的對象
CF_OBJECT 任何TPersistent類型的對象
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
利用TClipboard實現文本的傳輸使用AsText屬性和SetTextBuf方法。
AsText屬性為非控件部件的剪貼板操作提供了方便。如:
Clipboard. AsText := Form1.Caption ;
把Form1的標題拷貝到剪貼板。
Label1.Caption := Clipboard.AsText;
把剪貼板中的文本寫入Label1。
SetTextBuf用於把超過255個字符的字符串拷入剪貼板。
7.1.3 利用剪貼板傳輸圖像
7.1.3.1 拷貝
Image部件上的內容和窗體上的圖形可以直接拷貝到剪貼板。圖像拷貝利用Clipboard的Assign方法。
例如:
Clipboard.Assign(Image1.Picture);
把Image1上的圖像拷貝到剪貼板。
7.1.3.2 剪切
圖像的剪切是首先把圖像拷貝到剪貼板,而後在原位置用空白圖像進行覆蓋。
下面一段程序表示了圖像的剪切。
procedure TForm1.Cut1Click(Sender: TObject);
var
ARect: TRect;
begin
Clipboard.Assign(Image1.Picture);
with Image.Canvas do
begin
CopyMode := cmWhiteness;
ARect := Rect(0, 0, Image.Width, Image.Height);
CopyRect(ARect, Image.Canvas, ARect);
CopyMode := cmSrcCopy;
end;
end;
7.1.3.3 粘貼
從剪貼板上粘貼圖像,首先檢測剪貼板上的數據格式。如果格式為CF_BITMAP,則調用目標位圖的Assign 方法粘貼圖像。
程序清單如下。
procedure TForm1.PasteButtonClick(Sender: TObject);
var
Bitmap: TBitmap;
begin
if Clipboard.HasFormat(CF_BITMAP) then
begin
Bitmap := TBitmap.Create;
try
Bitmap.Assign(Clipboard);
Image.Canvas.Draw(0, 0, Bitmap);
finally
Bitmap.Free;
end;
end;
end;
try...finally為資源保護塊,參第十二章。
7.1.4 建立自己的剪貼板觀察程序
在這一節中我們要建立一個自己的剪貼板觀察程序,用來保存截獲到剪貼板中的位圖。
Windows允許用戶建立自己的剪貼板觀察程序,並把該程序添加到一個剪貼板觀察器鏈中。在鏈中,位置靠前的程序有義務把有關剪貼板的消息傳遞到緊隨其後的觀察程序。而處於鏈首的程序由Windows的消息循環機制直接把剪貼板消息發送過來。
建立一個剪貼板觀察程序,首先該程序必須能響應相應的Windows消息。對於那些熟悉Microsoft公司Visual Basic的讀者來說,這是令他們頭疼而束手無策的地方。但Delphi在這方面卻有良好的表現:利用關鍵字message,用戶可以將一個過程定義為響應特定的Windows消息。如:
procedure WMDrawClipboard(var Msg:TWMDrawClipboard);
message WM_DRAWCLIPBOARD;
可以響應WM_DRAWCLIPBOARD消息。類TWMDrawClipboard是消息類Message 的子類。Delphi把所有的消息都重新進行了定義,使用戶在使用時可以直接引用其便於記憶的數據成員,而不必再自己動手去分解消息。雖然這並不能算作是一個重大的改進,但卻體現了Delphi處處為用戶方便著想的特點。
我們將要建立的程序目的是把截獲到剪貼板上的位圖保存下來。在本書的寫作過程中,這一工作是大量存在的。雖然利用Windows工具PaintBrush(畫筆),通過粘貼、保存等操作可以實現這一功能,但卻存在以下一些問題:
1.程序頻繁切換影響效率,當有大量位圖存在時更是如此;
2.畫筆有一個很討厭的缺陷:當剪貼板上的位圖比畫筆界面的客戶區大時,客戶區外的位圖被截斷。因而往往需要根據所截獲位圖的大小來調整畫筆客戶區的大小,並重新進行粘貼。而如果開始就把畫筆客戶區調整到足夠大,又會覆蓋掉屏幕上一些有用的信息。
為解決這些問題,我開發了下面的程序。程序啟動時,以極小化方式運行。此時只要剪貼板中存入位圖,則自動彈出一個對話框請求用戶保存。如果用戶希望查看確認,則可以雙擊運行程序圖標,選擇相應按鈕,剪貼板中的位圖就會顯示在屏幕上。
部件關鍵屬性設計如下:
ClipSaveForm:
Caption=‘Save Bitmap in Clipboard '
Panel1:
Align = ' Top '
Image1:
Align = ' ClIEnt '
SaveDialog1:
FileEditStyle = fsEdit
FileName = '*.bmp'
Filter = 'Bitmap Files(*.bmp)|*.bmp|Any Files(*.*)|*.*'
InitialDir = 'c:\bmp'
Title = 'Save Bitmap'
程序主窗口是TForm派生類TClipSaveForm的實例。TClipSaveForm通過定義一些私有數據成員和過程,使響應和處理Windows的相應消息成為可能。下面是TClipSaveForm的類定義:
type
TClipSaveForm = class(TForm)
SaveDialog1: TSaveDialog;
Image1: TImage;
Panel1: TPanel;
Button1: TButton;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
private
{ Private declarations }
MyBitmap: TBitmap; { 保存截獲的位圖 }
VIEw: Boolean; { 判斷是否顯示 }
NextVIEwerHandle: HWND; { 下一剪貼板觀察器的句柄 }
procedure WMDrawClipboard(var Msg:TWMDrawClipboard);
message WM_DRAWCLIPBOARD;
procedure WMChangeCBChain(var Msg:TWMChangeCBChain);
message WM_CHANGECBCHAIN;
{ 響應Windows的剪貼板消息 }
public
{ Public declarations }
end;
窗口創建時,把該窗口登錄為剪貼板觀察器,添加到剪貼板觀察器鏈中,同時進行變量、部件和剪貼板的初始化。
procedure TClipSaveForm.FormCreate(Sender: TObject);
begin
VIEw := False;
SpeedButton2.Down := True;
MyBitmap := TBitmap.create;
try
MyBitmap.Width := 0;
MyBitmap.Height := 0 ;
except
Application.terminate;
end;
Clipboard.Clear;
NextViewerHandle := SetClipboardVIEwer(Handle);
end;
窗口關閉時,退出剪貼板觀察器鏈,並釋放內存:
procedure TClipSaveForm.FormDestroy(Sender: TObject);
begin
ChangeClipboardChain(Handle,NextVIEwerHandle);
MyBitmap.Free;
end;
在以上兩段程序中用到的兩個Windows API函數SetClipboardVIEwer和ChangeClipboardChain分別用於登錄和退出剪貼板觀察器鏈。
程序保存位圖的功能是在消息響應過程WMDrawClipboard中實現的。該過程在剪貼板內容有變化時被調用。
procedure TClipSaveForm.WMDrawClipboard(var Msg: TWMDrawClipboard);
var
FileName: String;
begin
If NextVIEwerHandle <> 0 then
SendMessage(NextVIEwerHandle,msg.Msg,0,0);
If ClipBoard.HasFormat(CF_BITMAP) then
begin
MyBitmap.Assign(Clipboard);
If SaveDialog1.Execute then
begin
FileName := SaveDialog1.FileName;
MyBitmap.SaveToFile(FileName);
end;
If VIEw then
begin
Windowstate := wsNormal;
Image1.Picture.Bitmap := MyBitmap;
end;
end;
Msg.Result := 0;
end;
程序首先判斷在剪貼板觀察器鏈中是否還存在下一個觀察器。如果有,則把消息傳遞下去,這是剪貼板觀察器程序的義務。而後判斷剪貼板上內容的格式是否為位圖。如是,則首先把剪貼板上內容保存到數據成員MyBitmap中,並激活一個文件保存對話框把位圖保存到文件中。如果VIEw=True,則把窗口狀態(Windowstate)設置為wsNormal,並把MyBitmap賦給Image部件的相應值,使用戶可以對剪貼板上的位圖進行觀察。
消息響應過程WMChangeCBChain在剪貼板觀察器鏈上其它觀察器退出時被調用。根據被移出觀察器的不同位置決定了不同的處理方法。
procedure TClipSaveForm.WMChangeCBChain(var Msg: TWMChangeCBChain);
begin
if Msg.Remove = NextVIEwerHandle then
NextVIEwerHandle := Msg.Next
else
if NextVIEwerHandle <> 0 then
SendMessage(NextVIEwerHandle,Msg.Msg,Msg.Remove,Msg.Next);
Msg.Result := 0;
end;
窗口上有兩個加速按鈕,兩個按鈕。它們擊鍵(click)事件處理過程如下。每一程序段的意義是非常顯然的。
procedure TClipSaveForm.Button1Click(Sender: TObject);
begin
Close;
end;
procedure TClipSaveForm.Button2Click(Sender: TObject);
begin
Windowstate := wsMinimized;
end;
procedure TClipSaveForm.SpeedButton1Click(Sender: TObject);
begin
VIEw := True;
Image1.Picture.Bitmap := MyBitmap;
end;
procedure TClipSaveForm.SpeedButton2Click(Sender: TObject);
begin
VIEw := False;
Image1.Picture.Bitmap := nil;
end;
通過對這個程序的介紹,以下幾點是應該注意的:
1.提供了一種自己截獲和處理剪貼板上內容的方法。讀者可以根據需要進一步擴充;
2.提供了響應Windows消息的方法。在第三篇有關自定義部件開發的內容中,這一問題還要詳細論述;
3.最後的一點啟示是:在Delphi程序開發中巧妙應用傳統的Windows方法(如消息處理、 API函數等)仍是很有必要的。而在應用這些方法中所體現的方便之處,正是Delphi勝過其它可視化開發工具的一個重要方面。
7.2 Windows的DDE原理和 Dephi的DDE實現機制
7.2.1 Windows的DDE原理
Windows的DDE機制基於Windows的消息機制。兩個Windows應用程序通過相互之間傳遞DDE消息進行DDE會話(Conversation),從而完成數據的請求、應答、傳輸。這兩個應用程序分別稱為服務器(Server)和客戶(ClIEnt)。服務器是數據的提供者,客戶是數據的請求和接受者。
DDE會話由客戶程序啟動。客戶程序把一條消息(WM_DDE_INITIATE)傳播給當前運行的所有Windows程序。這條消息指明了客戶程序所需要的一般數據(應用程序、主題)。擁有這些數據的DDE服務器可以響應這條被傳播的消息。此時,DDE會話就開始了。
由於在每個主題中,DDE服務器可以支持一個或多個數據項,所以在客戶請求數據時應同時指明應用程序名、主題名和項目名。應用程序、主題、項目是DDE中三個最基本的概念。
利用Windows本身提供的DDE消息和API進行DDE編程是一件相當棘手的問題。 雖然使用DDE管理庫(ddeml.dll)可以一定程度上減輕開發者的工作負擔,但開發DDE程序仍不是一件輕松的事情。
此時Delphi出現了!Delphi通過其自身巧妙的設計使開發一個DDE應用程序同開發一個普通程序一樣地快捷、方便。