凌科網(wǎng)頁精靈開發(fā)手記
楊中科
現(xiàn)在網(wǎng)上有大量的網(wǎng)頁特效軟件供網(wǎng)頁制作者使用,但是大多數(shù)網(wǎng)頁特效軟件只是羅列了如果將網(wǎng)頁特效代碼添加到html文檔中,比如:
“
第一步:把如下代碼加入<body>區(qū)域中:
<span id=liveclock style=position:absolute;left:250px;top:122px>
</span> (這里可以調(diào)整時(shí)鐘的方位。調(diào)用腳本時(shí)去掉括號中內(nèi)容)
<SCRIPT language=javascript>
var minutes=Digital.getMinutes()
if(hours>12){dn="PM"
hours=hours-12
}
……………………
setTimeout("show5()",1000)
}
</SCRIPT>
第二步:把<body>中的內(nèi)容改為
<body bgcolor="#fef4d9" ONLOAD=show5()>
”
這要求網(wǎng)頁制作者必須了解html語言,而且即使對熟悉html的用戶要想修改特效代碼中的參數(shù)(比如上面例子中的“調(diào)整時(shí)鐘的方位”)也是非常麻煩。這款軟件則解決了這個(gè)問題,您只要選擇一個(gè)要添加的特效,在彈出的對話框中填入幾個(gè)相關(guān)參數(shù),軟件將自動將特效代碼添加到網(wǎng)頁代碼的合適位置。
比如給軟件添加一個(gè)旋轉(zhuǎn)立體字的特效,只要選擇“插件”->“旋轉(zhuǎn)立體字”,就會彈出下面的對話框:

在對話框中填入各個(gè)參數(shù)后,網(wǎng)頁精靈就自動幫您把代碼插入到正確的位置了。
這款軟件的技術(shù)核心就是插件化開發(fā)和程序自動升級技術(shù)。下面我將分別講解這兩項(xiàng)技術(shù)在“網(wǎng)頁精靈”的應(yīng)用。
一、插件化開發(fā)。
1、基本原理
凌科網(wǎng)頁精靈中每個(gè)插件都是一個(gè)dll文件。插件這個(gè)名詞大家都很熟悉。如PhotoShop等軟件就是通過安裝很多插件來實(shí)現(xiàn)某些特殊功能的。插件僅僅是從外部提供給應(yīng)用程序的一個(gè)接口,通過調(diào)用約定的接口來實(shí)現(xiàn)插件所提供的功能。使用插件化開發(fā)的好處是明顯的,它可以很輕松的實(shí)現(xiàn)軟件的擴(kuò)展,并且簡化的軟件設(shè)計(jì)的構(gòu)架,使得開發(fā)程序變得更加簡單。
插件化開發(fā)可以通過很多技術(shù)實(shí)現(xiàn),比如COM,Dll等。我們這里采用Dll文件的形式實(shí)現(xiàn)插件。基本思想如下:程序每次啟動時(shí),在指定的目錄下查找*.dll文件,然后將其加入到某個(gè)菜單下。在用戶點(diǎn)擊插件對應(yīng)的菜單項(xiàng)時(shí),只要調(diào)用接口函數(shù)中約定好的某個(gè)函數(shù)就可以。
2、插件導(dǎo)出的接口
凌科網(wǎng)頁精靈中每個(gè)插件都必須導(dǎo)出下面三個(gè)接口函數(shù):
GetPlugInHTML, GetPlugInName, GetPlugInDescription;
它們的函數(shù)原型的pascal描述如下
function GetPlugInHTML(AHandle: THandle;ASelectedText: PChar;
AResultHTHML: TResultHTML): Boolean;stdcall;
procedure GetPlugInName(AValue: PChar);stdcall;
procedure GetPlugInDescription(AValue: PChar);stdcall;
其中 TResultHTML的pascal定義如下
TResultHTML = record
ReplaceHTML: PChar; //替換文字
BodyHTML: PChar;//添加到<Body></Body>區(qū)的文字
BodyTagHTML: PChar;//添加到<Body >中的文字,如<Body onload="show()">
HeadHTML: PChar;//添加到<Head></Head>區(qū)中的文字
end;
接口函數(shù)描述:
(1) function GetPlugInHTML(AHandle: THandle;ASelectedText: PChar;
AResultHTHML: TResultHTML): Boolean;stdcall;
在用戶點(diǎn)擊插件對應(yīng)的菜單時(shí),主程序?qū)⒄{(diào)用此方法來得到插件返回的對網(wǎng)頁的修改信息。
其中AHandle對應(yīng)主窗口,也就是網(wǎng)頁精靈的窗體句柄;ASelectedText代表用戶此時(shí)在網(wǎng)頁編輯器中選中的文本;AResultHTHML是返回值,將用ReplaceHTML將替換用戶選擇的文本,將把BodyHTML添加到網(wǎng)頁的<Body></Body>區(qū),將把BodyTagHTML添加到<Body >中,如在未調(diào)用插件的時(shí)候<Body> 在調(diào)用后bodyTagHTML=‘onload="show()"’則調(diào)用后<Body onload="show()">,將HeadHTML添加到<Head></Head>區(qū);
返回值代表此插件的運(yùn)行是否成功。如果返回False,則主程序會忽略插件對網(wǎng)頁的修改信息。
(2)procedure GetPlugInName(AValue: PChar);stdcall;返回值是AValue,它將做為菜單的標(biāo)題,代表插件的名稱。
(3)void GetPlugInDescription(char* AValue);返回值是AValue,它將做為此插件的功能描述。
3、動態(tài)加載插件
加載插件信息到菜單的偽代碼如下(關(guān)于FindFirst,FindNext的使用請參考Delphi的幫助, LoadLibrary, GetProcAddress, FreeLibrary的使用請參考MSDN):
var
LGetPlugInName: TGetPlugInName;
LSr: TSearchRec;
LHandle: THandle;
LName: PChar;
begin
FPlugIns.Clear;
if FindFirst(Adir+‘*.dll’, faAnyFile - faDirectory,LSr) = 0 then
// Adir是插件所在路徑
repeat
LHandle := LoadLibrary(PChar(ADir + LSr.Name));
try
GetMem(LName, MAXNAMEDESCSIZE);
@LGetPlugInName := GetProcAddress(LHandle, 'GetPlugInName');
//調(diào)用GetPlugInName到插件的名稱
LGetPlugInName(LName);
增加一個(gè)菜單,并設(shè)定菜單的標(biāo)題為Lname;
設(shè)定菜單的OnClick事件句柄=OnPlugInClick;
// OnPlugInClick的定義在后邊
將插件的文件名與菜單通過一定方法聯(lián)系起來;
//聯(lián)系起來以供在點(diǎn)擊菜單的時(shí)候加載此插件
finally
FreeLibrary(LHandle);
FreeMem(LName);
end;
until (FindNext(LSr) <> 0);
end;
其中
TGetPlugInName = procedure(AValue: PChar);stdcall;
4、運(yùn)行插件
在用戶點(diǎn)擊菜單之后將觸發(fā)我們在上邊設(shè)定的OnPlugInClick事件句柄。
procedure TFormMain.OnPlugInClick(Sender: TObject);
var
LHandle: THandle;
LGetPlugInHTML: TGetPlugInHTML;
LPlugInInfo: TPlugInInfo;
LRH: TResultHTML;
begin
LHandle := LoadLibrary(PChar(FPlugInsDir + LPlugInInfo.FileName));
try
@LGetPlugInHTML := GetProcAddress(LHandle, 'GetPlugInHTML');
LTmpStr := Trim(RichEditHTML.SelText);
GetMem(LRH.ReplaceHTML, MAXHTMLSIZE);
GetMem(LRH.BodyHTML, MAXHTMLSIZE);
GetMem(LRH.BodyTagHTML, MAXHTMLSIZE);
GetMem(LRH.HeadHTML, MAXHTMLSIZE);
//調(diào)用DLL中的GetPlugInHTML
LGetPlugInHTML(self.Handle, PAnsiChar(LTmpStr),LRH);
根據(jù) LRH中的信息更改HTML頁面中的相應(yīng)區(qū)域;
finally
釋放資源;
end;
5、開發(fā)插件示例
下面以開發(fā)一個(gè)“添加到收藏夾”插件為例來展示一下插件的開發(fā)。
實(shí)現(xiàn)這個(gè)功能的HTML如下:
<a href="javascript:window.external.AddFavorite('http://www.sohu.com', '搜狐
網(wǎng)')">將本站加入收藏夾</a>
顯然我們可以提供三個(gè)參數(shù)供用戶選擇,那就是網(wǎng)址(如http://www.sohu.com)、網(wǎng)站名稱(如 “搜狐網(wǎng)”)、超鏈接的標(biāo)題(如“將本站加入收藏夾”)。
新建一個(gè)Dll工程,在工程文件中導(dǎo)出Dll輸出的函數(shù)
exports
GetPlugInHTML, GetPlugInName, GetPlugInDescription;
新建一個(gè)窗體,布局如下:

將一個(gè)TbigStringContainer控件(我開發(fā)的可以存儲大字符串的控件,在開發(fā)包中),放到窗體中,雙擊strings屬性,輸入一下的文本:
<a href="javascript:window.external.AddFavorite('<!url>', '<!favoritename>')"><!text></a>
其中“'<!url>”、“<!favoritename>”、“<!text>”是我們要根據(jù)用戶輸入的值替換的字符串。
在窗體的public中定義如下的方法:
function GetReplaceHTML: string;
代碼如下
var
t: string;
begin
t := bigStringContainer1.GetString;
t := AnsiReplaceStr(t, '<!url>', EdtUrl.Text);
//將t字符串中的<!url>用, EdtUrl.Text替換
t := AnsiReplaceStr(t, '<!favoritename>', EdtFaName.Text);
t := AnsiReplaceStr(t, '<!text>', EdtText.Text);
result := t;
end;
Dll導(dǎo)出的三個(gè)函數(shù)的主要代碼如下:
function GetPlugInHTML(AHandle: THandle;ASelectedText: PChar;
AResultHTHML: TResultHTML): Boolean;stdcall;
var
Dlg: TFormFavorite;
begin
result := false;
AResultHTHML.ReplaceHTML[0] := #0;
AResultHTHML.BodyHTML[0] := #0;
AResultHTHML.BodyTagHTML[0] := #0;
AResultHTHML.HeadHTML[0] := #0;
if ASelectedText <> nil then
StrLCopy(AResultHTHML.ReplaceHTML, ASelectedText, MAXHTMLSIZE);
Dlg := TFormFavorite.Create(nil);
Dlg.ParentWindow := AHandle;
if Dlg.ShowModal = mrOK then
begin
result := true;
FillMemory(AResultHTHML.ReplaceHTML, MAXHTMLSIZE, 0);
StrLCopy(AResultHTHML.ReplaceHTML, PChar(Dlg.GetReplaceHTML),MAXHTMLSIZE);
end;
Dlg.free;
end;
procedure GetPlugInName(AValue: PChar);stdcall;
begin
FillMemory(AValue, MAXNAMEDESCSIZE, 0);
StrLCopy(AValue, PChar('添加到收藏夾功能'), MAXNAMEDESCSIZE);
end;
procedure GetPlugInDescription(AValue: PChar);stdcall;
begin
FillMemory(AValue, MAXNAMEDESCSIZE, 0);
StrLCopy(AValue, PChar('本插件將為在網(wǎng)頁中添加到收藏夾功能'),MAXNAMEDESCSIZE);
end;
編譯后將插件放到“網(wǎng)頁精靈”的插件目錄下,啟動“網(wǎng)頁精靈”就可以看到這個(gè)插件已經(jīng)被加載到了菜單中。
二、軟件自動升級技術(shù)
當(dāng)我們開發(fā)了新插件后,肯定希望用戶能盡快得到此插件。應(yīng)用程序升級的方法有兩種:一是通知用戶讓用戶到指定網(wǎng)站下載插件,然后由用戶將插件放到插件目錄下面;二是由程序負(fù)責(zé)從服務(wù)器上下載安裝插件,用戶唯一要做的就是決定是否愿意安裝新插件。顯然后一種方法比較好。
1、基本原理
在本地有一個(gè)存儲已安裝插件的信息的列表,在服務(wù)器端也維護(hù)一個(gè)服務(wù)器上的所有插件信息的列表。當(dāng)要升級插件的時(shí)候,程序從服務(wù)器上下載此列表,與本地的列表比較,如果發(fā)現(xiàn)本地沒有的插件,就將此插件下載下來,安裝到插件目錄下即可。
2、列表的結(jié)構(gòu)
由于列表中要保存所有插件的文件名、名稱、版本、描述等信息,所以用XML文件來保存比較合適。我定義XML文檔格式如下:
<PlugInsList>
<PlugIn>
<FileName>插件的文件名</FileName>
<Name>插件的名稱</Name>
<Version>版本</Version>
<Description>插件描述</Description>
</PlugInsList>
3、定義XML文件映射
我們可以使用DOM、SAX等解析XML文檔,但是寫起來很麻煩。好在咱們偉大的女神Delphi為我們提供了XML Data Binding Wizard這個(gè)強(qiáng)大的工具。XML數(shù)據(jù)綁定向?qū)?/SPAN>(XML Data Binding Wizard)可以將XML文件映射成類,這樣程序員能夠用它生成相應(yīng)的接口和類來訪問與修改XML文件數(shù)據(jù),完全沒有陌生感,用起來就好像使用普通的類一樣。
運(yùn)行Delphi7,點(diǎn)擊“File”->”O(jiān)ther”,選擇“New”頁面中的“XML Data Binding Wizard”。用記事本建立如下文件:
<PlugInsList>
<PlugIn>
<FileName>文件名</FileName>
<Name>名稱</Name>
<Version>版本</Version>
<Description>插件描述</Description>
</PlugIn>
<PlugIn>
<FileName>插件的文件名</FileName>
<Name>插件的名稱</Name>
<Version>版本</Version>
<Description>插件描述</Description>
</PlugIn>
</PlugInsList>
注意:
<PlugIn></PlugIn>必須要寫多于兩組(包含兩組),否則向?qū)J(rèn)為<PlugIn></PlugIn>是只能有一組的元素,從而生成的映射類無法供我們使用。而寫成多于兩組的時(shí)候向?qū)?/SPAN><PlugIn>屬性映射成<PlugInsList>的一個(gè)數(shù)組屬性。
以下是生成的代碼的接口部分:
IXMLPlugInsListType = interface;
IXMLPlugInType = interface;
{ IXMLPlugInsListType }
IXMLPlugInsListType = interface(IXMLNodeCollection)
['{5D777B2B-E265-472B-8035-ADCED92E0F65}']
{ Property Accessors }
function Get_PlugIn(Index: Integer): IXMLPlugInType;
{ Methods & Properties }
function Add: IXMLPlugInType;
function Insert(const Index: Integer): IXMLPlugInType;
property PlugIn[Index: Integer]: IXMLPlugInType read Get_PlugIn; default;
end;
{ IXMLPlugInType }
IXMLPlugInType = interface(IXMLNode)
['{76ED7F51-20FF-4A4A-87B7-CFB9BB280F80}']
{ Property Accessors }
function Get_FileName: WideString;
function Get_Name: WideString;
function Get_Version: WideString;
function Get_Description: WideString;
procedure Set_FileName(Value: WideString);
procedure Set_Name(Value: WideString);
procedure Set_Version(Value: WideString);
procedure Set_Description(Value: WideString);
{ Methods & Properties }
property FileName: WideString read Get_FileName write Set_FileName;
property Name: WideString read Get_Name write Set_Name;
property Version: WideString read Get_Version write Set_Version;
property Description: WideString read Get_Description write Set_Description;
end;
{ Forward Decls }
TXMLPlugInsListType = class;
TXMLPlugInType = class;
{ TXMLPlugInsListType }
TXMLPlugInsListType = class(TXMLNodeCollection, IXMLPlugInsListType)
protected
{ IXMLPlugInsListType }
function Get_PlugIn(Index: Integer): IXMLPlugInType;
function Add: IXMLPlugInType;
function Insert(const Index: Integer): IXMLPlugInType;
public
procedure AfterConstruction; override;
end;
{ TXMLPlugInType }
TXMLPlugInType = class(TXMLNode, IXMLPlugInType)
protected
{ IXMLPlugInType }
function Get_FileName: WideString;
function Get_Name: WideString;
function Get_Version: WideString;
function Get_Description: WideString;
procedure Set_FileName(Value: WideString);
procedure Set_Name(Value: WideString);
procedure Set_Version(Value: WideString);
procedure Set_Description(Value: WideString);
end;
{ Global Functions }
function GetPlugInsList(Doc: IXMLDocument): IXMLPlugInsListType;
function LoadPlugInsList(const FileName: WideString): IXMLPlugInsListType;
function NewPlugInsList: IXMLPlugInsListType;
我們直接使用的兩個(gè)接口是:
IXMLPlugInsListType = interface(IXMLNodeCollection);
IXMLPlugInType = interface(IXMLNode);
向?qū)н€提供了三個(gè)全局方法可以簡化我們的操作:
function GetPlugInsList(Doc: IXMLDocument): IXMLPlugInsListType;
function LoadPlugInsList(const FileName: WideString): IXMLPlugInsListType;
function NewPlugInsList: IXMLPlugInsListType;
(1)、我們一般將一個(gè)TXMLDocument組件(實(shí)現(xiàn)了IXMLNode接口)做為GetPlugInsList的參數(shù),GetPlugInsList將返回這個(gè)TXMLDocument組件對應(yīng)的XML文檔的根元素。
(2)、也可以將XML文件的文件名傳遞給function LoadPlugInsList(const FileName: WideString): IXMLPlugInsListType;返回值同樣是對應(yīng)的XML文檔的根元素。
(3)、function NewPlugInsList: IXMLPlugInsListType;
在內(nèi)存生成新文件。我們這里不直接用到它。
我們可以用IXMLPlugInsListType操縱文件中的<PlugInsList>中的<PlugIn>列表。
function Get_PlugIn(Index: Integer): IXMLPlugInType;
返回列表中指定位置Index的節(jié)點(diǎn)。
function Add: IXMLPlugInType;
將在列表中的最后位置添加一個(gè)節(jié)點(diǎn),返回剛添加的節(jié)點(diǎn)。
function Insert(const Index: Integer): IXMLPlugInType;
將在列表中的Index后位置添加一個(gè)節(jié)點(diǎn),返回剛添加的節(jié)點(diǎn)。
property PlugIn[Index: Integer]: IXMLPlugInType read Get_PlugIn;
則是一個(gè)以數(shù)組方式展現(xiàn)的所有節(jié)點(diǎn)的列表。
IXMLPlugInType的所有屬性FileName、Version等,都是對<PlugIn>的屬性的映射。
4、XML文件的讀寫操作
以前在Delphi中想操作XML文檔要頻繁調(diào)用XML API,十分的煩人。現(xiàn)在 XML Data Binding Wizard加上TXMLDocument(在Internet面板上)簡化了我們的操作。雙劍合壁,誰與爭風(fēng)!
在XML中添加新的節(jié)點(diǎn)的方法如下:
(1)var
ALocalList: IXMLPlugInsListType;
ALocalNode: IXMLPlugInType;
begin
ALocalList := GetPlugInsList(XMLDocLocal);
// XMLDocLocal為TXMLDocument控件
ALocalNode := ALocalList.Add;
ALocalNode.FileName := ‘DllFilename’;
ALocalNode.Name := ‘name’;
ALocalNode.Version := ‘version’;
ALocalNode.Description := ‘description’
XMLDocLocal.SaveToFile();
//如果XMLDocLocal的AutoSave=True則不用SaveToFile;
End;
(2)讀取一個(gè)節(jié)點(diǎn)的方法可以參考添加節(jié)點(diǎn)的代碼。
5、自動升級
有了上邊的知識相信開發(fā)一個(gè)自動升級的系統(tǒng)已經(jīng)不難了,您可以參考源代碼自己分析,我就不多費(fèi)口舌了。忘了說一點(diǎn),從服務(wù)器上下載XML列表可以使用WinInet或IdHTTP控件,我用的就是IdHTTP控件(方便呀,調(diào)用一個(gè)Get方法就可以做到,還支持代理服務(wù)器!嘻嘻!)。得到列表后賦值給TXMLDocument的XML.Text屬性就可以。


三、其他經(jīng)驗(yàn)總結(jié)
1、預(yù)覽功能的實(shí)現(xiàn)
預(yù)覽功能可以使用WebBrowser控件,在切換到“預(yù)覽”頁面的時(shí)候,將HTML代碼保存到文件中,然后調(diào)用WebBrowser.Navigate()方法將此頁面加載即可。
但是這里有一個(gè)如果保存網(wǎng)頁文件的問題,如果指定一個(gè)文件名,如”tmp.htm”,這樣在程序打開一個(gè)的時(shí)候沒問題,如果打開多個(gè)程序就會造成混亂:一個(gè)程序保存的”tmp.htm”被另一個(gè)程序加載了,會令人感到莫名其妙。
我解決此問題的方法是使用程序的句柄做為文件名。Windows每個(gè)程序都有一個(gè)句柄,即使是同一個(gè)軟件的兩個(gè)實(shí)例它們的句柄也不同。這個(gè)句柄本質(zhì)上是一個(gè)整形數(shù),所以可以把它轉(zhuǎn)換成一個(gè)字符串做為文件名,在程序退出時(shí)刪除此文件(如果保存在系統(tǒng)臨時(shí)文件夾下就不用刪除,Windows會自動替您刪除)。代碼如下:
FileName := 'tmp'+IntToStr(Integer(Application.Handle))+'.htm';
2、“撤銷”功能的實(shí)現(xiàn)
很多文本編輯器、網(wǎng)頁編輯器都有“撤銷”功能,這樣在用戶想返回編輯前的某個(gè)狀態(tài)時(shí)就會非常方便。我在這里用一個(gè)特殊的“堆棧”解決了這個(gè)問題(數(shù)據(jù)結(jié)構(gòu)還是很管用的呀,一定要好好學(xué)呀)。
這個(gè)堆棧的特殊之處就在于這個(gè)堆棧有個(gè)最大的容量(也就等于允許最大的撤銷次數(shù),如果不限制最大的撤銷次數(shù)就會導(dǎo)致系統(tǒng)資源越來越少,最后很可能就崩潰了),最特殊的地方就是當(dāng)堆棧增加到滿的后再壓入新元素的時(shí)候就刪除棧底元素,所有上邊的元素都自動下移一個(gè)位置,新壓入的元素放在棧頂。
我們當(dāng)然可以使用動態(tài)數(shù)組或鏈表解決這個(gè)問題,但是我發(fā)現(xiàn)Contnrs單元中有一個(gè)TobjectList類非常好用,我就用這個(gè)類來實(shí)現(xiàn)這個(gè)堆棧吧!代碼如下:
//字符串的Wrapper類
TStringObject = class
public
Value: string;
end;
TUndoStack = class(TObject)
private
FList: TObjectList;
FMaxSize: Integer;
protected
procedure DelBottom;virtual;//刪除底端一個(gè)
function FGetCurSize: Integer;
public
constructor Create(ASize: Integer);overload;virtual;
constructor Create;overload;virtual;
destructor Destroy;override;
procedure Push(AObj: string);virtual;
function Pop: string;virtual;
function IsFull: Boolean;virtual;//是否滿
function IsEmpty: Boolean;virtual;//是否空
procedure ClearStack;virtual;//清空
property MaxSize: Integer read FMaxSize; //最大容量
property CurSize: Integer read FGetCurSize;//現(xiàn)在的堆棧中的數(shù)量
end;
constructor TUndoStack.Create(ASize: Integer);
begin
inherited Create;
FList := TObjectList.Create;
FList.Capacity := ASize;
FMaxSize := ASize;
FList.Count := 0;
end;
constructor TUndoStack.Create;
begin
inherited;
Create(10);//default size
end;
procedure TUndoStack.DelBottom;
begin
if CurSize <= 0 then
raise Exception.Create('Stack already empty!');
FList.Delete(0);
FList.Capacity := FList.Capacity - 1;
FList.Capacity := FList.Capacity + 1;
end;
destructor TUndoStack.Destroy;
begin
FList.Free;
inherited;
end;
function TUndoStack.IsEmpty: Boolean;
begin
result := (CurSize <= 0);
end;
function TUndoStack.IsFull: Boolean;
begin
result := (CurSize = MaxSize);
end;
function TUndoStack.Pop: string;
begin
result := '';
if IsEmpty then
raise Exception.Create('Stack already Empty!')
else
begin
result := TStringObject(FList.Last).Value;
FList.Delete(CurSize - 1);
FList.Capacity := FList.Capacity - 1;
FList.Capacity := FList.Capacity + 1;
end;
end;
procedure TUndoStack.Push(AObj: string);
var
a: TStringObject;
begin
if IsFull then
begin
DelBottom;
end;
a := TStringObject.Create;
a.Value := AObj;
FList.Add(a);
end;
procedure TUndoStack.ClearStack;
begin
FList.Clear;
end;
function TUndoStack.FGetCurSize: Integer;
begin
FList.Pack;
result := FList.Count;
end;
使用方法如下:
定義一個(gè)FUndoStack: TUndoStack;撤銷堆棧變量,在用戶對文本做一個(gè)修改動作后調(diào)用
FUndoStack.Push(RichEditHTML.Lines.Text);
原來的文本壓入撤銷堆棧 。
在用戶點(diǎn)擊“撤銷”后,調(diào)用
RichEditHTML.Lines.Text := FUndoStack.Pop;
來還原到保存的值。