這篇文章昨天怎么也貼不上去,今天貼一貼試試。今天的blog鍛煉回來再寫。
今天早上起來不知怎么的感覺特別累。來深圳三個多月依來這是第一次感覺這么累,可能和這兩天連續奮戰有關系吧。忙過了這陣子要稍微修整一下了,否則身體垮了就太不值得了。不知道是產品延期發版了兄弟們有點放松警惕了,還是前兩天市場部一個同事去世對同事們敲響了警鐘了,大家下了班都早早的回家了。只有少數幾個人仍在堅守崗位,我當然就是其中一個了。給現場支持的同事寫了一個使用幫助文檔,然后我就琢磨起DB2來。來這個公司最大的好處就是可以接觸很多高級的東西,呵呵。
不管你說DB2強大也好,穩定也好,但從一個開發者的角度來講總感覺用著不舒服。比如它竟然不支持修改一個字段的類型,一旦你字段類型定義錯了,那么就必須刪掉重來,很恐怖呀。還有它的錯誤提示信息也很讓人摸不到頭腦,執行insert語句的時候如果某個字段不允許為空,它就提示你一個什么tableid=**,colId=**之類的,然后再告訴你怎么根據tableid=**,colId=**去查到底是哪個字段不允許為空。真是奇怪了,你幫我查出來,然后顯示給我不好嗎?在這一點上還是感覺微軟的東西好用呀。所以在mssql上開發,然后到DB2上部署也是個不錯的選擇。各取所長呀。
今天看了看HTC,也做了一個簡單的例子。一開始怎么也無法運行,后來發現在PUBLIC:與ATTACH之間不能有空格,否則就無法出現預期的效果。又看了看網上對htc的評論并不是很好,主要就是集中在資源消耗、穩定性和微軟以后將不再支持方面。特別是溫少的評論更有權威呀,他是見證了EAS.NET成敗的一個人,對HTC的優劣認識更深。抽時間和他聊聊,好在是一個Company的,只是不知道人家高手會不會有時間陪咱聊呀。呵呵。
太陽走了,留下的是星光;
云朵走了,留下的是藍天;
微笑走了,留下的是陰霾;
你走了,留下的是回憶。
風吹走了你,風吹干了淚,
留下的是干枯的雙眼。
回到宿舍同宿舍的兄弟想通過網絡鄰居訪問我的電腦,但是就是訪問不了,于是乎上網查了一下網絡鄰居的原理。這一查不要緊呀,太搞笑了,微軟的網絡鄰居實現的這么“民主”呀,竟然是誰先進來、誰更高級就推舉誰當主瀏覽器,竟然還要選出一個“副手”做為備份,呵呵。不過不知道為什么微軟當初這樣設計,很容易就出現網上鄰居不可用呀,不過好在大部分人用網絡鄰居用的不是很多。
這兩天對web的Rich client開發一直很感興趣,翻了翻公司控件開發部的服務器,人家做的確實不錯,用htc做的web前端太漂亮了,看來我要花一段時間研究研究htc了,雖然說htc是依賴IE6的,但是在企業級web開發中這也不是什么問題,我可不是那種敵視微軟的pure***的程序員。只要能快速完美的滿足客戶各種“變態”的需求,我就認為它是好的技術。
下載了一些htc的教程和一些示例代碼,我也不打算深入學習,畢竟我還是想多在java企業級開發上邊下功夫,但是多接觸點東西還是好的。在這行混的一個準則是你不必對什么都精通,但是必須什么都了解一點,其實業界更缺少的是這種“蜻蜓點水的專才”。話題扯遠了,還是說說HTC吧。HTC確實是web界面開發的一種進步,但是我感覺進步還不是很快,呵呵。如果能象Delphi或者.net那樣具有完全行為和屬性的組件體系的話就更完美了,程序員只要在htm中輸入類似于下面的代碼就可以將控件引入。<MyComponent/>。聽說微軟正在將HTC做為web標準提向w3c,希望能得到批準,并且做的更完美,那樣就能大大節省我們的開發時間了。
好了,不早了,我要休息了,這兩天我看看htc的教程和代碼,我會及時與大家交流自己的心得體會的。
? 忙是忙,總不能把自己困死在工作上的。畢竟還是要自己學點東西的。本想回來看一看hibernate的源代碼的,但是同宿舍兄弟剛買了臺電腦要用我的電腦做代理上網。生平第一次看到用雙網卡實現共享上網,又學了一招,我那個兄弟可是個網絡高手,以后要經常向他學習了,等有時間買個小路由,讓他教教我怎么配置路由器。
? 時間不多只能把我前一陣子研究的java中調用腳本語言的東西拿出來整理一下了。java中調用腳本語言有很廣泛的用途,比如用戶自定義公式,自定義單據轉換規則,用戶自定義工作流等等。java中調用腳本語言有很多中實現,比如Jython、Groovy、Beanshell等等。但是即使對于有一定開發經驗的程序員學習它們是需要一段時間的。而提起腳本,無論是程序員還是有一定軟件開發基礎的客戶(例如客戶公司的網管、信息管理員等等)都會想起javascript和VBScript,如果我們的系統使用這些用戶熟悉的腳本語言,必將降低系統學習的難度。VBScript是捆綁在windows平臺下的,因此javascript就成了我們專注的對象。javascript引擎可以在NetScape的js.jar中找到,而現在這個包已經做為一個開源項目獨立出來了,項目的名字是Rhino,項目網站:http://www.mozilla.org/rhino/。
? Rhino支持javascript標準的所有特性,如賦值、運算、判斷語句、循環語句、甚至簡單的io操作。Rhino很簡單易用,我以一個例子來說明,這個例子就是允許用戶設定一個使用x,y兩個變量進行一定邏輯算術運算(運算規則用戶用javascript定義),并允許用戶對x,y賦值,然后運行得出計算結果。代碼如下:
??????? Context ctx = Context.enter();
??????? Scriptable scope = ctx.initStandardObjects();
???????
??????? scope.put("x", new Integer(20));
??????? scope.put("y", new Integer(30));
??????? try
??????? {
????????? ctx.evaluateString("if(x
????????? System.out.println(scope.get("result", scope));
??????? }
??????? finally
??????? {
??????????? Context.exit();
??????? }
很簡捷吧。scope.put("x", new Integer(20))的意思是為變量x賦值,ctx.evaluateString("if(x
? 好了,都凌晨一點鐘了,該睡覺了,明天還要改bug呢,爭取提前把任務搞定,周五調休。
東軟那幫人真垃圾,讓他們提供個數據接口他們都故意為難你,讓你得程序繞了八百個彎才把數據導進去,而且還有很小的精度誤差。東軟我真服了了你了,你怎么也算是國內有影響的大公司了吧,怎么這樣的胸懷都沒有呀,劉雞人呀,I 他媽 服了 You!
抽空給在濟南兼職的時候那個項目經理打了個電話。年紀大了就是羅嗦了呀,今天又跟我說他在深圳的幾個同學都沒有結婚之類的話,言語中有一點不理解又有一些羨慕。想想也是呀,他都是一個7歲小丫頭的爸爸了,而比他大的同學還在耍單,肯定羨慕死他呀。至于他們為什么還在耍單,他也沒說。不過我聽說過一句話,來深圳的女人分為三種:當過雞的,正在當雞的,將要當雞的。應該還有下文吧,來深圳的男人分為三種:被傷害過的,正在被傷害的,將要被傷害的。被傷害的男人為了忘掉過去,為了自強,一堵氣來深圳創自己的一片天下;正在被傷害人帶著意思遺憾來深圳療傷;而來了深圳的幸福男人總會有一天嘗到來這的苦果,從此弄的遍體鱗傷,封閉自己的內心,從此落寞。
? 回到宿舍把電腦打開,《大話西游》終于下完了,看來電信還沒有完全屏蔽BT呀。你說我幼稚也好,說我低俗也好,說我落伍也好,但是《大話西游》我是百看不厭。也許我就是那個孫悟空轉世吧,只是也許輪回太多了,當初的猴崽子已經變成了一只小胖娃娃。但是不變的是命運的安排。也許你曾自由飛翔、也許你是山賊、也許和紫霞轟轟烈烈、也許對晶晶眉來眼去,但命中注定你要被烙上那三棵痔,注定要被套上緊箍咒。曾經的愛你一萬年的海誓山盟,曾經在你心中留下眼淚的女人,曾經幫你踩滅大火的兄弟,這些都將離你而遠去。不要再回憶,不管它曾多么美好,命中注定前行才是你的方向。為你而被殺死的那些兄弟們是否已經投胎,被師傅折磨而自盡的兩個小妖是否已經變回原型,盤絲洞是否會有新的主人,在城堡上擁抱的他們是否會幸福永遠,這些都不用你再管,不用你再操心,他們都象你一樣在命運定律的安排下有序的運轉著。你需要的只是抹去紫霞在你唇邊留下的香味,背起鐵棒,象一條黃狗一樣離開曾經的全部,然后扔掉香蕉皮,向命中注定的地方前行。

近日(呵呵,這篇文章是去年寫的)用了兩個月開發了一個物流信息系統,這個系統是兩層、三層相結合,C/S、B/S相結合的系統。雖然限于時間的緊張和人手的原因,系統規模不是很大,但是其中涉及的技術卻很全面。在這個《開發技術篇》中我們將講解我在開發系統中遇到的技術問題及解決方案,希望對大家有幫助。對于物流信息系統的分析設計問題,我將在另一篇文章《物流信息系統開發手記――系統構架篇》中講解。
一、Midas的安全問題。
Midas技術是Delphi中進行三層開發的首選技術,它不僅有純DCOM/COM+(COM+技術是.NET技術的基礎)的優點,而且也結合了Delphi的快速開發特性,可以快速開發出想要的系統,其開發速度是用VC,PB等開發DCOM的數十倍,把程序員從煩雜的代碼中解脫出來,從而將更多的精力投入到業務邏輯的設計中去。
但是Midas技術的一個最令人擔憂的就是它的安全問題:
遠端只要知道應用服務器的端口號即可訪問到應用服務器,而一旦訪問到應用服務器,TClientDataSet即可獲得ProviderNames列表。一旦知道了ProviderNames列表,這就相當于將數據庫暴露在外了。
關于可輕易獲得ProviderNames列表的問題,我使用下面的方法解決:
在服務器端定義一個
LoginMTS(const AUserId, APassword: WideString): WordBool;
方法。初始狀態下,所有的DataSetProvider和數據集的連接斷開。用戶必須調用LoginMTS并傳遞用戶名和密碼,登陸成功才將DataSetProvider和數據集的連接打開。這樣如果用戶驗證沒有通過,即使它獲得了ProviderNames列表也沒法調用接口中的方法對數據庫進行操作。
二、Midas中主從表的實現
主從表的應用在信息系統中應用很廣。在兩層開發中我們可以通過直接建立兩個數據集之間為主從關系來實現主從表;在三層中雖然我們仍然可以通過直接建立兩個數據集之間為主從關系來實現主從表,但是這樣就要求把數據庫中所有相關的數據行都下載到本地,喪失了三層開發的優勢。我在實際中使用下面的方法實現。這里我以實現入庫單查詢、添加、修改、刪除(CRUD)為例來講解:
(1)新建一個MTS Data Module,命名為TmtsStockInListBiz,增加如下方法:
function QueryStockInListMasterById(const AId: WideString;
var ADatas: OleVariant): WordBool; safecall;
function QueryStockInListSlaveByMasterId(const AId: WideString;
var ADatas: OleVariant): WordBool; safecall;
procedure UpdataStockInListMaster(var ADatas: OleVariant); safecall;
procedure UpdataStockInListSlave(var ADatas: OleVariant); safecall;
function GenerateStockInListId: WideString; safecall;
QueryStockInListMasterById作用是根據入庫單單號查詢入庫單的基本信息(入庫日期、負責人等),Aid為入庫單單號,Adatas為返回值,其格式就是Midas的數據包,可以將其附給ClientDatSet的Data屬性。
QueryStockInListSlaveByMasterId作用是根據入庫單單號查詢入庫單的詳細信息(商品條碼,數量)
UpdataStockInListMaster是對入庫單主表進行刪除、添加、修改操作。只要將ClientDataSet的Delta屬性做為傳遞即可。
UpdataStockInListSlave是對入庫單從表進行刪除、添加、修改操作。
GenerateStockInListId是產生一個唯一的入庫單號。
下面是幾個方法的代碼,都很簡單,就不多解釋了,可以查看Delphi的幫助。
function TmtsStockInListBiz.QueryStockInListMasterById(
const AId: WideString; var ADatas: OleVariant): WordBool;
begin
result := false;
ADatas := null;
try
cdsQuery.Close;
cdsQuery.CommandText := 'select * from t_StockInListMaster where Id=:Id';
cdsQuery.Params.ParamByName('Id').AsString := AId;
cdsQuery.Open;
if cdsQuery.RecordCount > 0 then
begin
result := true;
ADatas := cdsQuery.Data;
end;
finally
cdsQuery.Close;
end;
end;
procedure TmtsStockInListBiz.UpdataStockInListMaster(
var ADatas: OleVariant);
var
eCount: Integer;
OwnerData: OleVariant;
begin
DCOMConStockInList.GetServer.AS_ApplyUpdates('dspStockInListMaster',
ADatas, -1, eCount, OwnerData);
end;
function TmtsStockInListBiz.GenerateStockInListId: WideString;
var
LPrior: string;
i: Integer;
begin
cdsQuery.Close;
cdsQuery.CommandText := 'select top 1 id from t_StockInListMaster order by id desc';
cdsQuery.Open;
LPrior := cdsQuery.FieldByName('Id').AsString;
i := StrToIntDef(RightStr(LPrior,8),0);
Inc(i);
result := 'RK' + FormatFloat('00000000',i);
cdsQuery.Close;
end;
(2)、新建一個應用程序,通過DCOMConnection、SocketConnection等連接到MTS組件,然后就可以調用MTS的相應的方法實現客戶端功能了。
放入cdsStockInListMaster、cdsStockInListSlave兩個ClientDataSet控件,在控件上點擊右鍵,選擇“FieldsEditor”新建于服務器中的字段同樣的字段,然后再次在控件上單擊右鍵,選擇“CreateDataSet”,建立一個本地數據庫。
(3)
根據入庫單號查詢入庫單的方法實現:
procedure TFormStockInList.BtnFindClick(Sender: TObject);
var
v,vs: OleVariant;
begin
if SocketConStockInList.AppServer.QueryStockInListMasterById(Trim(LEdtId.Text), v) then
begin
cdsStockInListMaster.Data := v;//顯示入庫單主表(主要信息)
if SocketConStockInList.AppServer.QueryStockInListSlaveByMasterId(Trim(LEdtId.Text), vs) then
cdsStockInListSlave.Data := vs; ;//顯示入庫單從表(明細信息)
end
else
ShowMessage('此單不存在!');
end;
(4)新建入庫單的實現
procedure TFormStockInList.BtnNewClick(Sender: TObject);
var
LId: string;
begin
ClearCDSRecord;
cdsStockInListMaster.Open;
cdsStockInListMaster.Insert;
LId := SocketConStockInList.AppServer.GenerateStockInListId;
LEdtId.Text := LId;
cdsStockInListMaster.FieldByName('Id').AsString := LId;
cdsStockInListMaster.FieldByName('GenerateDate').AsDateTime := Now();
end;
(5)提交功能的實現
procedure TFormStockInList.BtnPostClick(Sender: TObject);
var
LQuerymts: ImtsQueryObjDisp;
LBar: string;
begin
SetSocketConnectionConnect(SocketConQuery);
LQuerymts := ImtsQueryObjDisp(SocketConQuery.GetServer);
SocketConQuery.Close;
if cdsStockInListMaster.RecordCount > 0 then
SocketConStockInList.AppServer.UpdataStockInListMaster(cdsStockInListMaster.Delta);
if cdsStockInListSlave.RecordCount > 0 then
SocketConStockInList.AppServer.UpdataStockInListSlave(cdsStockInListSlave.Delta);
end;
注:本文中ClientDataSet控件的名稱開頭一般為cds、TsocketConnection控件的名稱開頭一般為SocketCon。
三、動態設置TsimpleObjectBroker的服務器列表
procedure SetSocketConnectionConnect(AValue: TSocketConnection);
procedure FillAppServerList(ABroker: TSimpleObjectBroker);
var
sl: TStringList;
i, n: Integer;
begin
sl := TStringList.Create;
從配置文件中讀取服務器列表,并保存到sl中;
n := sl.Count - 1;
ABroker.ServerData := null;
for i := 0 to n do
begin
ABroker.Servers.Add;
ABroker.Servers[i].ComputerName := sl.Strings[i]
end;
sl.Free;
end;
var
LBroker: TSimpleObjectBroker;
begin
LBroker := TSimpleObjectBroker.Create(nil);
FillAppServerList(LBroker);
AValue.ObjectBroker := LBroker;
try
AValue.Connected := true;
except
raise Exception.Create('應用服務器連接錯誤!');
end;
LBroker.Free;
end;
《網絡吸管》開發手記
網絡確實是個好東西,文章呀,圖片呀什么的都很吸引人。每次上網都能滿載而歸,但是這些資料的收集過程卻很麻煩。對于好文章,每次都要復制、粘貼地在記事本和IE之間切換多次才能保存下來,而且說不定什么時候遇到那種怎么復制也復制不下來的防復制網頁;對于圖片也要點右鍵,選擇“圖片另存為”,再點確定才可以,遇到文件重名問題還要重命名。上網的興致全被打亂了。網上雖然也有“網文快捕”之類的小軟件,但是由于不是為自己“量身定做”的,所以用起來也不是很順手。既然這樣,就自己動手做一個吧,“自己動手豐衣足食”嘛!說干就干!
設計思想很簡單:監視剪貼板,當發現剪貼板中有新內容時,就根據內容是文字還是圖片來決定不同的保存方式。
如何監視剪貼板呢?很自然地想到放一個定時器,每隔一段時間檢測一個剪貼板,將剪貼板地內容于上次檢測地內容相比較,如果不同,就說明剪貼板的內容有變化。但是這樣效率太低了,并且定時器的時間間隔也不好把握,間隔太短會降低系統的效率,而間隔太長就有可能漏掉復制的內容。這讓我想起了CPU與外設之間通訊方式中的查詢方式,那么有沒有一種像CPU與外設之間的中斷方式的東西呢?啟動MSDN,搜索ClipBoard,呵呵!終于找到了!是什么呢?聽我慢慢道來!
為了使應用程序能自動感知剪貼板的變化,windows提供了兩個API函數。使用SetClipBoard可以將窗體注冊到剪貼板觀測鏈中,然后程序就能響應剪貼板的變化消息。剪貼板觀察器是一個顯示剪貼板當前內容的窗口。剪貼板觀察鏈是一系列相互獨立的剪貼板觀察窗口,它們都能夠接受當前發送到剪貼板的內容。
SetClipBoard的原型是:
function SetClipBoard(hwndNewViewer:HWND):HWND;
hwndNewViewer為要注冊的窗體句柄。如果注冊成功,則返回剪貼板觀測鏈中下一個窗體的句柄;如果發生錯誤或無其他窗體,則返回NULL。
如果剪貼板發生變化,windows會向窗體發送WM_CHANGECCHAIN或WM_DRAWCLIPBOARD消息,觀測鏈中每個窗體都會調用SendMessage將該消息傳送給下一個窗體。當應用程序退出時,要利用API函數ChangeClipboardChain將窗體從剪貼板觀測鏈中移去。其原型為:
function ChangeClipboardChain(hWndRemove, hWndNewNext:HWND):boolean;
hWndRemove將要刪除的窗口的句柄, hWndNewNext為SetClipBoard返回的窗體的句柄。
這樣我們只要在程序中等待剪貼板變化的消息即可。當消息到來時,我們應該怎樣得到剪貼板中的內容呢?Delphi的clipbrd.pas單元中定義了一個類TClipboard,它封裝了Windows剪貼板,簡化了大量復雜的處理過程。我們在程序中可以直接調用全局函數Clipboard,該函數用于返回TClipboard對象實例,使用這個實例對剪貼板進行剪切、復制和粘貼等操作。下面是TClipboard對象的幾個常用的方法和屬性的簡單介紹:
方法:
procedure Clear; 清空剪貼板。
function HasFormat(Format: Word): Boolean; 查詢剪貼板中是否有指定格式的內容。可以有三種取值:CF_TEXT(文字)、CF_BITMAP(位圖)、CF_METAFILEPICT(元文件)。
屬性:
AsText:用于讀寫剪貼板文字內容。
如何給用戶保存下來的圖片文件命名也是個問題。我們可以設置一個全局整型變量,每當保存一個圖片文件時,就令這個變量增加1,將這個整型變量轉換成字符串做為文件名。如果指定的文件名已經存在,就要給文件重命名。最簡單的辦法就是在文件名之前(或之后)加上一個字符串(比如'new'),如果加上這個字符串后還是存在重名的文件呢?這就要用到學編程的人在一開始就學到的一個小技巧:遞歸。這個問題的解決辦法見下面的代碼:
procedure SaveToPic(APic: TJPegImage; AFileName: string);
Const PICPLUSSTR = 'new';
begin
if FileExists(AFileName) then
savetopic(ABmp, PICPLUSSTR+AFileName)
else
SaveBmpAsJpg(APic, AFileName);
end;
在實際應用的時候,還應該加上異常處理(如磁盤空間已滿,文件名過長等)。圖片的保存的基本問題已經解決,我們再來看看文字的保存。為了增強程序的靈活性,我們應該使用用戶能方便地將不同地文字保存到不同的文件。繼續沿用上面保存圖片的方式用數字做文件名嗎?當然不可以。一是因為文本文件不像圖片那樣在資源管理器中可以預覽,用戶必須打開文件才能知道文件中保存的是什么內容,如果用戶想在一大堆“1.txt”、“2.txt”……中找自己想要的內容就太麻煩了;二是因為用戶并不要求每次復制下來的內容都保存到單一的文件中,而是要將相關的內容保存到一個文件中。我對這個問題的解決方法是這樣的:
用戶可以先復制一段文字,然后再按一個熱鍵(比如Ctrl+Alt+S,為什么要選Ctrl+Alt+S做熱鍵呢?后面再說!),這樣用戶以后復制下的文字就保存到以用戶復制的文字做為文件名的文件中。
記得無數位大師說過:“要將用戶界面與業務邏輯分開。”好吧,就將上面的東西封裝一下,也算是我向OO邁進的第一步吧!(下面之列出了類的部分成員)
TWebPageSaver = class(TObject)
private
FImagePath: string;
FTextPath: string;
FImageCount: Integer;
FTextFileName: string;
procedure SetImagePath(const Value: string);
procedure SetTextPath(const Value: string);
public
function Save: Boolean;//result is whether the content is saved
procedure NewTextFile(AFileName:string);
property ImagePath: string read FImagePath write SetImagePath;
property TextPath: string read FTextPath write SetTextPath;
end;
在用戶界面中,當用戶按下熱鍵Ctrl+Alt+S時,就調用TWebPageSaver.NewTextFile更改文字保存的文件名FTextFileName;當收到剪貼板變化的消息時就調用TWebPageSaver.Save保存剪貼板中的內容。另外還有ImagePath、TextPath等屬性,可以由用戶來更改圖片、文字的保存路徑。
核心代碼已經完成,來做一下用戶界面吧!仿照著“windows優化大師”我做了如下的界面:
很漂亮吧?左邊我用的是TSpeedButton組件,右邊是TNotePage組件。當用戶點擊一個TSpeedButton時,調用TNotePage.ActivePage := '頁面的代號'就可以激活相應的配置界面。這個軟件需要在后臺運行,那么就讓它在平時縮小到系統托盤吧!將程序縮小到系統托盤很容易做到,網上有很多這樣的示例代碼。我手頭有一個控件cooltray4.3可以用來實現系統托盤的功能,我就懶得自己再去寫代碼了。
軟件運行一切良好。不過一直令我耿耿于懷的就是網上那種防復制的網頁:不管你怎么拖動鼠標,那些文字就是無法被選定。仔細想一想,既然文字能夠在IE上顯示就一定可以得到它們。在MSDN中找了半天,才找到解決方法。可以通過ShellWindows集合來代表屬于shell 的當前打開的窗口的集合,而IE就是屬于shell的一個應用程序。用CoShellWindows.Create得到當前打開的shell的接口(IShellWindows),調用接口的Count屬性得到當前打開的shell的數量,然后遍歷這些窗口,嘗試從接口中取出IWebbrowser2接口(通過ShellWindow.Item(I) as IWebbrowser2這樣的接口類型轉換方式),如果結果不為nil說明這個窗口是IE窗口。之后只要調用IWebBrowser2接口的相應方法即可得到窗口中的文字、URL、標題等內容了。
示例代碼如下:
{需要使用mshtml,SHdocvw兩個單元}
var
ShellWindow : IShellWindows;
WebBrowser : IWebBrowser2;
I, ShellWindowCount: integer;
HTMLdocument : IHTMLdocument2;
URL, Title, Text:string;
begin
ShellWindow := CoShellWindows.Create;
ShellWindowCount := ShellWindow.Count;
for I := 0 to ShellWindowCount-1 do
begin
WebBrowser := ShellWindow.Item(I) as IWebbrowser2;
if WebBrowser <> nil then
begin
HTMLDocument := WebBrowser.Document as IHtmlDocument2;
URL := URL;
Title := HTMLDocument.title;
Text := HTMLDocument.body.outerText ;
ShowMessage(URL+Title+Text);
end;
end;
ShellWindow := nil;
end;
我們定義一個記錄類型:
TWebPageRecord = record
URL: string; //保存網頁的URL
Title: string;//保存網頁的標題
Text: string; //保存網頁的文字
end;
然后定義一個TWebPageRecord類型的數組FWebPageRecordArray,大小定位20吧(我想一般人不會打開20個以上的IE吧):
Const MAXPAGECOUNT = 20;
……
FWebPageRecordArray : array [0..MAXPAGECOUNT-1] of TWebPageRecord;
在遍歷IE窗口時,向數組中的元素的相應字段復制即可。
對這個復制防復制(好拗口呀:))網頁的功能也封裝成一個類吧!
type
TWebCracker = class(TObject)
private
FWebPageRecordArray : array [0..MAXPAGECOUNT-1] of TWebPageRecord;
FWebPageCount: Integer;
public
procedure SnapShot;
function GetWebText(AIndex:integer): string;
function GetWebTitle(AIndex:integer): string;
function GetWebURL(AIndex:integer): string;
procedure Clear;
procedure Refresh;
function GetWebPageCount: Integer;
end;
在用戶界面中,可以通過調用TWebCracker.SnapShot;來對打開的IE窗口進行遍歷,并保存到FWebPageRecordArray這個數組中。通過TWebCracker.GetWebPageCount方法可以得到FWebPageRecordArray中保存的頁面的個數,通過GetWebText、GetWebTitle、GetWebURL就可以得到指定頁面的文字、標題或是URL。
一切都已經搞定了!爽!
通過編寫這個小軟件,我是收獲頗豐呀!除了學到了上邊這些技巧外,我還有一些小的經驗,愿意與大家分享:
1、為用戶著想,讓用戶舒服
用戶是上帝嘛!以那個Ctrl+Alt+S熱鍵來說吧:一般用戶上網都是右手握鼠標,空下來的只有左手。小拇指按Ctrl,大拇指按Alt,食指剛好能按到S鍵,不費一點力氣!
2、 良好的編碼習慣
(1)不要出現魔術數
以TWebCracker定義的那個FWebPageRecordArray數組來說:
Const MAXPAGECOUNT = 20;
……
FWebPageRecordArray : array [0..MAXPAGECOUNT-1] of TWebPageRecord;
別人一看MAXPAGECOUNT就知道是什么意思,而如果你寫成:
FWebPageRecordArray : array [0..19] of TWebPageRecord;
估計除了你自己沒有人能夠知道19到底是什么意思。
(2)用sender的方式增強代碼的健壯性
procedure TMainfrm.CBAutoRunClick(Sender: TObject);
Const
SIGNINREGISTRY = 'WebSuction';
begin
if (Sender as TCheckBox).Checked then
AddToAutoRun(Application.ExeName,SIGNINREGISTRY)
else DelAutoRun(SIGNINREGISTRY);
end;
這樣即使Checkbox1改了名字也不怕。
又如:
procedure TMainfrm.N1Click(Sender: TObject);
begin
if (Sender as TMenuItem).Caption = '暫停(&S)' then
begin
(Sender as TMenuItem).Caption := '開始(&R)';
FWebPageSaver.Pause;
end
else
begin
(Sender as TMenuItem).Caption := '暫停(&S)';
FWebPageSaver.ReStart;
end;
end;
(3)不要直接使用Tform2單元的全局Form2變量,那樣就破壞了封裝性
procedure TMainfrm.SBNextClick(Sender: TObject);
var
LSelectedIndex : integer;
FormDisplay : Tform2;
begin
LSelectedIndex := LBWebPage.ItemIndex;
if LSelectedIndex <> -1 then
begin
FormDisplay := Tform2.Create(self);
FormDisplay.SetContent(FWebCracker.GetWebText(LSelectedIndex));
FormDisplay.Show;
end;
end;
在TForm2中定義 SetContent方法
procedure TWebCrackfrm.SetContent(AText:string);
begin
Memo.Clear;
Memo.Lines.Add(AText);
end;