如鵬網 大學生計算機學習社區

          CowNew開源團隊

          http://www.cownew.com 郵件請聯系 about521 at 163.com

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            363 隨筆 :: 2 文章 :: 808 評論 :: 0 Trackbacks
           

          《網絡吸管》開發手記

          網絡確實是個好東西,文章呀,圖片呀什么的都很吸引人。每次上網都能滿載而歸,但是這些資料的收集過程卻很麻煩。對于好文章,每次都要復制、粘貼地在記事本和IE之間切換多次才能保存下來,而且說不定什么時候遇到那種怎么復制也復制不下來的防復制網頁;對于圖片也要點右鍵,選擇“圖片另存為”,再點確定才可以,遇到文件重名問題還要重命名。上網的興致全被打亂了。網上雖然也有“網文快捕”之類的小軟件,但是由于不是為自己“量身定做”的,所以用起來也不是很順手。既然這樣,就自己動手做一個吧,“自己動手豐衣足食”嘛!說干就干!

          設計思想很簡單:監視剪貼板,當發現剪貼板中有新內容時,就根據內容是文字還是圖片來決定不同的保存方式。

          如何監視剪貼板呢?很自然地想到放一個定時器,每隔一段時間檢測一個剪貼板,將剪貼板地內容于上次檢測地內容相比較,如果不同,就說明剪貼板的內容有變化。但是這樣效率太低了,并且定時器的時間間隔也不好把握,間隔太短會降低系統的效率,而間隔太長就有可能漏掉復制的內容。這讓我想起了CPU與外設之間通訊方式中的查詢方式,那么有沒有一種像CPU與外設之間的中斷方式的東西呢?啟動MSDN,搜索ClipBoard,呵呵!終于找到了!是什么呢?聽我慢慢道來!

          為了使應用程序能自動感知剪貼板的變化,windows提供了兩個API函數。使用SetClipBoard可以將窗體注冊到剪貼板觀測鏈中,然后程序就能響應剪貼板的變化消息。剪貼板觀察器是一個顯示剪貼板當前內容的窗口。剪貼板觀察鏈是一系列相互獨立的剪貼板觀察窗口,它們都能夠接受當前發送到剪貼板的內容。

          SetClipBoard的原型是:

          function SetClipBoard(hwndNewViewer:HWND):HWND;

          hwndNewViewer為要注冊的窗體句柄。如果注冊成功,則返回剪貼板觀測鏈中下一個窗體的句柄;如果發生錯誤或無其他窗體,則返回NULL

          如果剪貼板發生變化,windows會向窗體發送WM_CHANGECCHAINWM_DRAWCLIPBOARD消息,觀測鏈中每個窗體都會調用SendMessage將該消息傳送給下一個窗體。當應用程序退出時,要利用API函數ChangeClipboardChain將窗體從剪貼板觀測鏈中移去。其原型為:

          function ChangeClipboardChain(hWndRemove, hWndNewNext:HWND):boolean;

          hWndRemove將要刪除的窗口的句柄, hWndNewNextSetClipBoard返回的窗體的句柄。

          這樣我們只要在程序中等待剪貼板變化的消息即可。當消息到來時,我們應該怎樣得到剪貼板中的內容呢?Delphiclipbrd.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保存剪貼板中的內容。另外還有ImagePathTextPath等屬性,可以由用戶來更改圖片、文字的保存路徑。

           

          核心代碼已經完成,來做一下用戶界面吧!仿照著“windows優化大師”我做了如下的界面:

          ns1.jpg

           

           

           

           

           

           

          很漂亮吧?左邊我用的是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中保存的頁面的個數,通過GetWebTextGetWebTitleGetWebURL就可以得到指定頁面的文字、標題或是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;

          posted on 2005-10-22 00:45 CowNew開源團隊 閱讀(470) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 高陵县| 永城市| 富蕴县| 南城县| 公安县| 襄汾县| 永顺县| 都安| 淮北市| 东辽县| 上饶县| 宝兴县| 昌都县| 曲麻莱县| 屏边| 宣武区| 伊金霍洛旗| 洛川县| 汉中市| 南阳市| 商城县| 依安县| 左云县| 玉龙| 沙坪坝区| 平顶山市| 焦作市| 洪洞县| 揭西县| 镇康县| 鄂伦春自治旗| 安义县| 德保县| 青河县| 友谊县| 鲁山县| 得荣县| 绥德县| 榆林市| 天峻县| 龙胜|