全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:3117
推到 Plurk!
推到 Facebook!

[轉貼] 用C++BUILDER制作群發郵件程序

 
axsoft
版主


發表:681
回覆:1056
積分:969
註冊:2002-03-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2002-08-05 10:05:23 IP:61.218.xxx.xxx 未訂閱
用C++BUILDER制作群發郵件程序   作者:陳嘉洲    www.wlcjz.com        BCB是一種功能強大的WINDOWS環境編程工具,有著DELPHI的外表和C++的內涵。它提供了SMTP組件用于發送電子郵件,將繁瑣的操作封裝在組件內部,使程序員通過友好的界面就可以實現編程。我們來開發一個群發郵件的程序,通過實例學習BCB編程。         我們共分五節來分析源碼:(1)系統的功能特點和使用的組件;(2)發送按鈕操作;(3)成功事件處理;(4)BCC文件的操作;(5)文件列表的操作。                          第一節:系統的功能特點和使用的組件        我們假設閱讀者能夠使用標准C編寫小的應用程序,使用過諸如TURBO C之類的DOS時代的C編譯系統;並且接觸過C++BUILDER,初步了解常用組件的使用,能夠做一個類似于傳統C的“HELLO,WORLD”檔次的小小程序。我們在實例中將學習MEMO、LISTBOX等文本編輯類控件、OPENDIALOG、SAVEDIALOG等對話框類控件和TIMER時間控件、TNMSMTP郵件發送控件的使用方法與技巧,不必擔心,所講的內容是很淺顯易懂的,充分考慮到了BCB初學者的需要,對于一些控件的技術細節和編程中的技巧給出了詳細的講解,並且所用實例是一個完整的群發郵件程序的BCB源碼,讀者可以前后對照,學習編寫一個完整的應用程序。     所用實例是東川花園系列軟件的“飛花”群發郵件FAA1.0版本的源碼,這是一個單SMTP發送的群發程序,提供了檢測用EMAIL地址、“第一行問候語”等特色功能及良好的發送郵件管理文件的列表管理功能。完整的程序源碼和可執行文件FAA10.EXE見“東川花園”(鏡像站點:http://wlcjz.yeah.net、http://zycjz.163.net、http://zycjz.533.net)的“C++BUILDER小學”。        FAA1.0程序的設計思路是這樣的:用SMTP控件可以很簡單的實現一封郵件的發送,關鍵在于如何實現“群發”,也就是說,如何實現從BCC文件中讀取EMAIL地址,連續不斷的發送。程序採用了點擊BUTTON1按鈕發送一封郵件,然后在發送成功后的NMSMTP1Success()事件中,讀取BCC文件中的規定數目的EMAIL地址作為BCC項目的內容並再次調用BUTTON1按鈕的點擊操作,實現新郵件的發送;新郵件發送成功后,再次觸發NMSMTP1Success()事件,再次讀取BCC文件並再次調用BUTTON1點擊操作。如此循環,直至BCC文件中的郵件地址全部發完或用戶終止操作。也許細心的讀者會問,這樣是不是一個遞歸操作?每次調用都會將上一次的調用數據壓入堆棧,如果BCC文件足夠大是不是會導致系統資源被耗盡而導致系統崩潰呢?事實確實如此,這也就是我們使用TIMER控件的原因,具體的解決方法會在后述章節中有詳細說明。     一般用于群發的BCC文件都是非常大的,用戶可能分多次發送,這就需要程序提供用戶終止本次發送后“保護現場”的“發送郵件管理文件”,以備用戶下次發送時,能夠“恢複現場”,繼續本次發送。FAA1.0程序中提供了列表管理模塊實現這種功能。        我們共分五節來分析源碼:(1)系統的功能特點和使用的組件;(2)發送按鈕操作;(3)成功事件處理;(4)BCC文件的操作;(5)文件列表的操作。下面首先給出程序中主要用到的控件、函數、變量的列表。    
/*------------主要用到的控件列表-------------*/    //
        TEdit *Edit1;//發件人姓名
        TEdit *Edit2;//發件人郵件地址 
        TEdit *Edit5;//收件人姓名
        TEdit *Edit6;//發件人郵件地址
        TEdit *Edit7;//郵件主題
        TEdit *Edit8;//發信檢測用EMAIL
        TEdit *Edit_one;//第一行問候語
        TEdit *Edit3;//SMTP主機地址
        TEdit *Edit4;//用戶ID
        TEdit *Edit9;//每次發送郵件的BCC單位數,介于1~100之間,默認為10
        TEdit *Edit10;//BCC文件名
        TEdit *Edit_bcc_begin;//讀取BCC文件的開始字節
        TEdit *Edit_bcc_end;//讀取BCC文件的結束字節
        TEdit *Edit_bcc_q;//讀取BCC文件的當前字節    //            TMemo *Memo1;//郵件正文
        TMemo *Memo2;//當前發送郵件的BCC內容
        TMemo *Memo3;//程序幫助內容
        TMemo *Memo4;//當前選擇的發送郵件管理文件的內容
        TMemo *Memo5;//發送郵件情況的信息窗口    //< BUTTON類 >
        TButton *Button1;//開始發送按鈕
        TButton *Button2;//強制斷開按鈕
        TButton *Button3;//退出系統按鈕
        TButton *Button4;//打開歷史列表文件按鈕
        TButton *Button5;//保存當前設置到列表文件按鈕    //
        TListBox *ListBox1;//郵件的附件(可多個)
        TListBox *ListBox2;//發送郵件管理文件的列表    //<其他類>
        TNMSMTP *NMSMTP1;//SMTP發送郵件控件,我們就依靠它工作啊!
        TTimer *Timer1;//時間部件
        TPanel *Panel3;//與服務器連接情況顯示板
        TOpenDialog *OpenDialog1;//打開文件對話框
        TSaveDialog *SaveDialog1;//保存文件對話框        另外還有用于美化屏幕、顯示狀態的控件。    /*------------主要用到的函數列表-------------*/            void __fastcall Button1Click();//發送按鈕的操作處理
        void __fastcall NMSMTP1Success();//每次發送成功后的處理,涉及循環壓棧的處理
        void __fastcall Timer1Timer();//時間控件,為了處理循環壓棧問題
        void __fastcall Edit10KeyDown();//BCC文件被選擇時的處理
        void __fastcall ListBox1KeyDown();//郵件附件選擇框            void __fastcall FormCreate();//窗體創建時加載發送郵件管理文件的列表
        void __fastcall Button4Click();//打開歷史列表文件,加載至內存,恢複當時的發送現場
        void __fastcall Button5Click();//保存當前的發送現場到管理文件,並將管理文件加入列表
        void __fastcall ListBox2KeyDown();//增刪列表中的管理文件
        void __fastcall ListBox2Click();//選定列表中的管理文件
        void __fastcall Button3Click();//退出系統按鈕,保存管理文件列表        另外用到的還有一些狀態和數據的設置、顯示用的函數:NMSMTP1ConnectionFailed()、Button2Click()、NMSMTP1Connect()、NMSMTP1Disconnect()、Edit9Change()、Edit_bcc_beginChange()、Edit_bcc_endChange()、FormClose()等。    /*------------主要用到的內存變量列表-------------*/    /*
zflag1 : 標志BCC文件是否已經打開
zflag2 :標志BCC發送單位數
zflag3 :發送成功的次數
zflag4 :總共發送成功的郵件數
zflag_limit:每發送200次進行一次特殊處理
zflag_limit_js:多少次特殊處理
zflag_file_b:BCC文件開始字節處
zflag_file_e:BCC文件結束字節處    fp: BCC文件句柄
fp1: 臨時用文件句柄    */    //begin for 全局變量
int zflag1=0,zflag2=10,zflag3=0,zflag4=0,zflag_limit=0,zflag_limit_js=0;
long int zflag_file_b=0,zflag_file_e=0,zflag_list=0;
FILE * fp,* fp1;
//end of 全局變量    //begin for 臨時變量(因為程序中涉及循環壓棧的問題,所以將這些不具有全局意義的臨時變量也放在全局變量處定義,以減少運行時的內存占用)
char ch;
char ss[290],ss1[290],ss2[290],ss3[290],ss4[290],ach[180],filename[10],test[90];
int i=0,si=0,a1=0,a2=0,i1=0,i2=0,aa=10,j;
long i3;
//end of 臨時變量
介紹完要用的控件和變量,將全局變量在ufaa1.cpp中定義如下。注意在文件首的include頭文件中,STDLIB.H頭文件包含exit()的定義;STDIO.H包含FILE結構的定義,這是傳統C文件操作不可缺少的。如果不添加這兩個頭文件,那麼exit()函數和傳統C的I/O系統就無法使用。 我們要用到的文件除了ufaa1.cpp外,還有與之對應的ufaa1.h頭文件,在那里面有我們所用的控件和函數的定義(聲明),也就是“主要用到的控件列表”、“主要用到的函數列表”中出現的內容。另外與ufaa1.cpp單元文件對應的窗體文件是ufaa1.dfm文件。
//---------------------------------------------------------------------------
#include 
#pragma hdrstop
#include 
#include 
#include "ufaa1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
//全局變量定義開始    int zflag1=0,zflag2=10,zflag3=0,zflag4=0,zflag_limit=0,zflag_limit_js=0;
long int zflag_file_b=0,zflag_file_e=0,zflag_list=0;
FILE * fp,* fp1;    char ch;
char ss[290],ss1[290],ss2[290],ss3[290],ss4[290],ach[180],filename[10],test[90];
int i=0,si=0,a1=0,a2=0,i1=0,i2=0,aa=10,j;
long i3;    //全局變量定義結束
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------
OK!從這里我們就要開始具體編寫代碼了,放輕松,這是一個快樂的過程,我們會很快就能編寫一個實際的INTERNET應用程序,來吧,LET’S GO! 第二節:發送按鈕操作 BUTTON1的單擊處理是一個完整的SMTP發送郵件的過程。首先檢測是否已經與SMTP服務器建立了連接,NMSMTP->Connected返回布爾值,如果沒有建立連接則首先用NMSMTP1->Connect()方法建立連接,當然這個操作是在已經聯網的基礎上進行的,SMTP組件的Connect方法只是負責連接到SMTP服務器,同時應注意使用connect方法之前應該先給HOST和USERID屬性賦值,其中HOST的賦值必須是准確的,USERID也必須有,否則會導致連接失敗,但它可以是一個隨意的字符串。 在確保連通的情況下,就給NMSMTP1->PostMessage屬性賦值,POSTMESSAGE屬性本身又有自己的子屬性,包括發信人名稱、地址,收件人名稱、地址等,賦值之后就可以用NMSMTP1->SendMail()方法發送了。在這個功能模塊里,需要學習NMSMTP控件的各種屬性和方法,以及FAA1.0的兩個創意功能:檢測用EMAIL和第一行問候語的實現方法。下面先看源代碼:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  if (NMSMTP1->Connected)
    Memo5->Lines->Add("已經連接至:" IntToStr(zflag3) "*" IntToStr(zflag2));
  else
    {
      NMSMTP1->Host = Edit3->Text;
      NMSMTP1->UserID = Edit4->Text;
      NMSMTP1->Connect();
    Memo5->Lines->Add("send:" IntToStr(zflag_limit) "*" IntToStr(zflag3) "*" IntToStr(zflag2));
    }      if (NMSMTP1->Connected)
    {
      NMSMTP1->PostMessage->FromAddress = Edit2->Text;
      NMSMTP1->PostMessage->FromName = Edit1->Text;
        //if語句處理BCC第一行作為本次發送郵件的接收人
      if(Memo2->Lines->Strings[0]!="") {
      Edit6->Text=Memo2->Lines->Strings[0];
      Memo2->Lines->Delete(0);
             //又一個IF語句處理檢測EMAIL
      if(Edit8->Text!="") Memo2->Lines->Add(Edit8->Text);
                                       }
      NMSMTP1->PostMessage->ToAddress->Text =Edit6->Text;
      NMSMTP1->PostMessage->ToBlindCarbonCopy->Text = Memo2->Text;
      NMSMTP1->PostMessage->Body->Text = Memo1->Text;
#include "ufaa1_1.h"  //完成第一行處理
      NMSMTP1->PostMessage->Attachments->Text = ListBox1->Items->Text;
      NMSMTP1->PostMessage->Subject = Edit7->Text;    //主題
      NMSMTP1->PostMessage->LocalProgram = "飛花郵件-http://zycjz.533.net";
      NMSMTP1->SendMail();
    }
  else
    ShowMessage("連接失敗!無法傳送!!");
}
//---------------------------------------------------------------------------
上面這個模塊思路很簡單,就是給NMSMTP1的PostMessage屬性賦值。其中涉及到BCC屬性的部分是這樣處理的:ToBlindCarbonCopy(BCC)的值由MEMO2的值決定,MEMO2中的原始值由第四節要講的BCC文件框處理模塊來獲得。此處要處理的是如果BCC編輯框不為空,那麼就將它的第一行賦值給收件人地址EDIT6編輯框,並將該行刪掉;然后判斷是否有“檢測用EMAIL”,如果有那麼將它添加在BCC編輯框的最后。這樣就實現了不斷更換收件人地址發送新郵件和用設定的EMAIL檢測本次發送效果的功能。 關于MEMO、EDIT等文本編輯類的控件的使用我們將在第五節“文件列表的操作”中做進一步的探討,此處我們應該掌握的是MEMO的單行操作。EDIT控件是一個單行的文本編輯框,而MEMO是一個多行的文本編輯框,要訪問MEMO的某一行需要使用它的Lines屬性,要訪問整體可使用Text屬性。因為Lines是TStrings類型,所以可以使用Add、Delte等方法,例中 Memo2->Lines->Delete(0) 的0代表第一行,C語言中習慣從0開始計數,而ADD方法總是將新行添加到最后,要實現插入到任意位置需要用INSERT方法,這些都是TStrings類型共有的。TEXT屬性只能在運行時才能訪問,MODIFIED屬性用來標志TEXT的數據是否已經修改,例中 NMSMTP1->PostMessage->Body->Text = Memo1->Text 用TEXT實現了整體賦值。 “第一行問候語”是一個創意小功能,大家都知道,群發郵件就是將1封信發給多個人,如果你接到一封郵件,看到千篇一律的開頭“您好!”、“尊敬的先生/女士:”等等,是否感覺發信者根本不認識你,只是站在大街上拿著大喇叭扯著嗓子向著眾人喊“來來,快來看,揮淚大甩賣啦!......”,不能給收件人以個性化的服務好象是群發郵件的先天不足。如果能夠實現針對每個人的不同的問候語,那麼無疑會大大加強郵件內容的吸引力,比方說,對于zycjz@163.net,我們發給他的郵件開頭是“尊敬的zycjz,你好!”或“您好,zycjz”是不是會效果大增呢?當然,這需要我們每次只發送一封郵件,不使用BCC功能,但這個速度上的損失換來的閱讀效果的增強在許多場合是值得的。現在我們就來實現這一功能。 實現的思路是這樣的:首先分析EMAIL地址,以@為分界線,‘@’左邊的就認為是用戶名,將其放入ss1內存變量;然后分析Edit_one編輯框,在存在‘@’的位置,插入用戶名,如果沒有‘@’,就認為用戶名是在問候語的開頭,依此原則同樣以‘@’為分界線,左值放入ss3,右值放入ss4;最后用sprintf函數將ss3,ss1,ss4合並成為問候語。 ufaa1_1.h的內容:
                                                //第一行處理BEGIN
   if(Edit_one->Text!="")   {
        //取得用戶名BEGIN
      strcpy(ss,Edit6->Text.c_str());
  for(i=0;iText.c_str());
     ss3[0]=ss4[0]='\0';
     si=strlen(ss);
  for(i=0;iPostMessage->Body->Insert(0,AnsiString(ss));
                            }
//第一行處理END 這段代碼中我們要學習的是(1)標准C的字符串操作(2)標准C的字符串類型與BCB的AnsiString類型的轉化。不難看出,標准C的字符串操作使用時是非常得心應手的,字符串是一個以'\0'結尾的字符數組,strlen() 函數可以返回它的長度,這個值是從1開始計數的,而字符串數組是從0開始計數的,所以該值比數組下標大1;strcpy()是拷貝字符串函數,在AnsiString類型中,同樣的操作已經通過運算符重載實現了使用‘=’的更加直觀的賦值方式;sprintf()是格式化輸出到字符串函數,可以實現複雜的賦值。需要注意的是這個語句strcpy(ss4,&ss[i 1]),SS被定義成字符串(字符數組),按照標准C的約定,SS就是一個指針,它與&ss[0]的含義是一致的,都是指向字符數組開頭的字符型指針。所以&ss[i 1]就是一個指向ss[i 1]字符開頭的字符數組的指針,可以給SS4賦值。 AnsiString類型是BCB中定義的類似于CHAR *的對象,擁有自己的屬性、方法,BCB中的VCL控件都是使用這一類型來處理字符串操作。而標准C的字符數組操作函數與之並不兼容,AnsiString類型的重載功能和方法標准C也無法使用,所以涉及二者的轉換問題。轉換方法是:AnsiString類型->標准C字符數組──使用“c_str()”如例中 strcpy(ss,Edit_one->Text.c_str());標准C字符數組->AnsiString類型──使用(AnsiString)進行強制類型轉換,如例中 NMSMTP1->PostMessage->Body->Insert(0,AnsiString(ss))。 好了,生成並發送一封郵件的知識我們已經掌握了。但並非每次與服務器的連接都會成功,我們需要讓用戶知道連接情況,下面是NMSMTP關于連接的幾個事件和方法,我們用來向用戶報告連接情況,編程簡單就不做過多解釋了,只要知道該控件有這些事件和方法即可。 主動斷開:
void __fastcall TForm1::Button2Click(TObject *Sender)
{
if(NMSMTP1->Connected)
  NMSMTP1->Disconnect();
Panel3->Caption="強制斷開連接!";
}        連接成功:
void __fastcall TForm1::NMSMTP1Connect(TObject *Sender)
{
Memo5->Lines->Add("Connected  --" Edit3->Text);
}        連接失敗:
void __fastcall TForm1::NMSMTP1ConnectionFailed(TObject *Sender)
{
Application->MessageBox("連接失敗","連接SMTP",MB_OK);
}        斷開連接:
void __fastcall TForm1::NMSMTP1Disconnect(TObject *Sender)
{
Panel3->Caption="發送完畢,連接已經斷開。";
}
 
 
 
第三節:成功事件處理 發送了郵件,對于普通的郵件程序而言工作已經完成了,但對于群發郵件程序而言,只是邁出了萬里長征第一步,革命的工作才開始。如何實現多個地址的連續發送呢?我們採用的方法是在NMSMTP1Success()事件中通過再次調用發送命令來實現的。 NMSMTP1Success()事件在郵件發送成功時被觸發,我們要做的工作就是處理BCC文件,重新填充BCC編輯框,然后進行計數處理,最后主動觸發BUTTON1的單擊操作,實現再一次發送。源碼如下:
void __fastcall TForm1::NMSMTP1Success(TObject *Sender)
{      if(Edit9->Text=="") zflag2=10; else  zflag2=StrToInt(Edit9->Text);      if(zflag1==1 && fp!=NULL) {             //如果BCC文件已經打開--BEGIN
  Memo2->Lines->Clear();
  fseek(fp,StrToInt(Edit_bcc_q->Text),0); //每次都從當前位置再開始,主要為了恢複現場
  for(a1=a2=0;a1<280 && !feof(fp) && a2Lines->Add(ach); a1=-1,a2  ; } else {
       ach[a1]=ch; }                            }
  Edit_bcc_q->Text=ftell(fp);
       if(feof(fp) || ftell(fp)>zflag_file_e)  { zflag1=0; fclose(fp); }
       zflag4 =zflag2; //ZFLAG4記錄總共向多少用戶發送郵件
  zflag3  ;            //ZFLAG3記錄總共發送次數
  zflag_limit  ;
#include "ufaa1_2.h"   //每發送成功200次要進行的處理
  Button1->Click();
  }   else {                     //如果BCC文件已經打開--END
  Memo5->Lines->Add("發送成功!---" IntToStr(zflag4));
  NMSMTP1->Disconnect();
  zflag3=0;}    }
應該說,這個思路是很“理所當然”的清晰正確,看起來沒有什麼問題。而實際情況如我們在第一節所初步分析的,會導致循環壓棧,最終耗盡系統資源,導致系統崩潰。讓我們模擬運行一下看看會發生什麼情況: (1)BCC文件准備就緒,單擊“發送”按鈕; (2)發送成功,觸發SUCCESS事件 (3)SUCCESS處理BCC文件,然后主動點擊“發送”按鈕 (3_1)發送成功,觸發SUCCESS事件 (3_2)SUCCESS處理BCC文件,然后主動點擊“發送”按鈕 (3_2_1)發送成功,觸發SUCCESS事件 (3_2_2)SUCCESS處理BCC文件,然后主動點擊“發送”按鈕 (3_2_2_1)發送成功,觸發SUCCESS事件 (3_2_2_2)SUCCESS處理BCC文件,然后主動點擊“發送”按鈕 ...... 不難看出,從SUCCESS主動點擊“發送”按鈕開始,反複逐級調用,始終得不到回溯的機會,除非用戶終止程序或者BCC文件結束,否則只能是調用程序數據(包括調用程序本身的入棧信息)不停的壓入堆棧,循環不止,最終必然導致堆棧溢出,系統崩潰。最初沒有進行特殊處理的時候在M2-300 CPU、64M內存機器上的實測結果是每當發送到470次左右時系統死機並提示“STACK OVERFLOWER”。根據提示判斷象是有數組超界或堆棧溢出,在排除了數組超界的可能性后,考慮是兩個主要因素在起作用,一是臨時變量放在NMSMTP1Success()中,加大了內存負擔,按照C語言的變量範圍約定,這種在模塊內部定義的臨時變量在程序退出該模塊時就被釋放,正常情況下不會耗用多少內存資源,但在本例中,由于NMSMTP1Success()一直沒有釋放資源,所以會造成內存耗用;在將臨時變量定義改為全局變量后(因為每次調用BUTTON1->CLICK時這些變量已經使用完畢,所以將其由模塊內的臨時變量改為全局變量不會影響運算的正確性),實測結果是能夠運行到千余次,然后是MEMO5由于始終在用ADD方法增加,超過了它能夠承受的行數導致程序崩潰。MEMO5的處理比較簡單,在一定行數后清掉即可。關鍵是調用程序本身的入棧信息就足以導致系統崩潰,所以解決問題的關鍵就是如何防止遞歸調用而沒有回溯的入口這種情況的發生。 如果用戶一直坐在那兒,看著快到500次左右的時候就斷開連接,然后再重新連接發送就可以避免崩潰。工具可以命令主人嗎?不行。程序可以命令用戶嗎?不行。所以,那種設想顯然不是一個程序員在開發工具程序時的正確思路。但我們可以讓程序模擬用戶的行為,讓它在發送到某個次數時斷開連接,產生回溯,釋放資源,然后再自動重新連接發送。這,就是解決問題的正確思路,看下面的源碼:
//------------
   ufaa1_2.h內容:      //進行200次檢測--BEGIN
  if(zflag_limit>=200) {
     if(NMSMTP1->Connected)
        NMSMTP1->Disconnect();
     zflag_limit_js  ;
     Panel3->Caption="<" IntToStr(zflag_limit_js) ">200自動斷開,重新連接!";
     Memo5->Clear() ;
     zflag_limit=0;
     Timer1->Interval = 5100;
     return;
   }
  //進行200次檢測--END
就這麼簡單,發送至200次就斷開連接,清掉MEMO5,設置時間控件的參數,然后用RETURN指令回溯,彈出入棧數據,釋放系統資源。 在這里,我們用到了TIMER時間控件,這個控件在BCB的IDE的“SYSTEM”頁上,它是一個極其重要的編程控件,在與時間相關或者是雖然與時間因素關聯不大但無其他WINDOWS事件觸發相應操作的場合有著不可替代的作用,本程序中TIMER的應用就是一例。TIMER的屬性較少,主要是INTERVAL屬性,它定義觸發ONTIMER事件的事件間隔,以毫秒為單位,例中設 Timer1->Interval = 5100就是設置為5.1秒觸發ONTIMER事件,如果INTERVAL為零則相當于關閉計時器。 在我們的實例中,初始的Timer1->Interval被設置為零,這沒有在程序源碼中出現,但這是不可缺少的設置,不能用TIMER1的默認值,因為我們只在需要時才打開計時器,而ONTIMER將主動點擊發送按鈕。ONTIMER源碼如下:
    //------------      //初始的Timer1->Interval為0!
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
if( Timer1->Interval == 5100){
  Timer1->Interval = 0;
Button1->Click();
                              }
}
NMSMTP1Success()事件中涉及的標准C的I/O操作我們將在第四節“BCC文件的操作”中詳細講解。在本節中主要需要掌握的一是NMSMTP類的ONSUCCESS事件處理,注意體會“事件驅動”的WINDOWS編程的一些運行特性,體會排除“RUN_TIME”錯誤的方法。一般來說,語句錯誤是最容易排除的,而“運行時”錯誤是最難排除的,而且“運行時”錯誤的確定本身也是非常麻煩的。需要積累經驗才能夠較快的找到故障原因,進而排除故障。編程要力求簡潔,越是簡潔的語句故障越少。 二是要掌握TIMER的使用方法。需要注意的是TIMER在有耗用大量資源的程序運行時,計時不一定准確,可能會延時或丟失。 在本節的最后我們再分析一下輸入數字的文本編輯框的使用方法,先看“BCC單位數”的輸入框代碼:
void __fastcall TForm1::Edit9Change(TObject *Sender)
{
aa=10;
if(Edit9->Text!="") {
aa=StrToInt(Edit9->Text);
if(aa >100) aa=10;
if(aa <0)  aa=10;
Edit9->Text=IntToStr(aa);
                     }
}
//---------------------------------------------------------------------------
這個編輯框中的數字範圍是有要求的,必須是在0~100之間,默認為10。判斷編輯框是否為空,不為空則用StrToInt()轉化字符類型到整數類型,判斷是否超界,超界則設為默認值,然后再用IntToStr()轉換回去。由此實現了對于數值的控制。下面是類似的代碼:
    void __fastcall TForm1::Edit_bcc_beginChange(TObject *Sender)
{
aa=10;
if(Edit_bcc_begin->Text!="") {
aa=StrToInt(Edit_bcc_begin->Text);
if(aa <=0)  aa=0;
Edit_bcc_begin->Text=aa;
                             } else  {
aa=0;
Edit_bcc_begin->Text=aa;
                              }
}
//---------------------------------------------------------------------------    void __fastcall TForm1::Edit_bcc_endChange(TObject *Sender)
{
aa=10;
if(Edit_bcc_end->Text!="") {
aa=StrToInt(Edit_bcc_end->Text);
if(aa <=0)  aa=0;
Edit_bcc_end->Text=aa;
                     } else  {
aa=0;
Edit_bcc_end->Text=aa;
                              }
}
//---------------------------------------------------------------------------
 
 
 
 
第四節:BCC文件的操作 群發郵件的核心文件就是BCC文件,選擇文件涉及文本編輯框的按鍵操作,打開文件並解析郵件地址涉及標准C的傳統I/O操作。我們首先用附件選擇框的操作說明“文本編輯框的按鍵操作”方法: 附件欄目:
void __fastcall TForm1::ListBox1KeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{    if (Key == VK_INSERT)
  if (OpenDialog1->Execute())
     ListBox1->Items->Add(OpenDialog1->FileName);
if (Key == VK_DELETE)
    ListBox1->Items->Delete(ListBox1->ItemIndex);    }
//---------------------------------------------------------------------------
這個操作是這樣的:用戶選擇了附件選擇框,然后如果按下INSERT鍵將會彈出“打開文件對話框”,用戶選擇了一個文件“確定”后,被選擇的文件就會增加到附件選擇框中;如果按下了DELETE鍵,並且附件選擇框中有文件被選擇,那麼該項就會被從選擇框中刪除。ONKEYDOWN事件是EDIT、MEMO、LISTBOX等文本編輯框共有的事件,它可以響應用戶的按鍵操作,在許多場合會簡化操作畫面,為用戶提供更多的易于理解的操作選擇。KEY值在調用該事件時傳入,如果在處理語句中加入ShowMessage(Key),就會發現KEY值在按INSERT鍵時返回45,在按DELETE鍵時返回46,實際上這些宏定義存放在“......\Include\winuser.h”中,可以在那個頭文件中查看其他按鍵的宏定義。 清楚了文本編輯框的按鍵操作再閱讀BCC文件選擇框的代碼,難點就集中在“標准C的傳統I/O操作”上了,我們先來閱讀下面這段代碼: BCC文件:
    void __fastcall TForm1::Edit10KeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{        //BCC文件處理
if (Key == VK_DELETE)           //如果是DELETE鍵被按下
   { Edit10->Text=""; zflag1=0; }
if (Key == VK_INSERT)           //如果是INSERT鍵被按下
  if (OpenDialog1->Execute())   {
       Edit10->Text= (OpenDialog1->FileName);
        //ZFLAG2是BCC的數目,限制在100以內
       if(Edit9->Text=="") zflag2=10; else  zflag2=StrToInt(Edit9->Text);
       if(zflag1==1)  {         //如果BCC文件已經打開
       Memo5->Lines->Add("終止原來的BCC文件發送,啟動新BCC文件");
       fclose(fp);
       zflag1=0;
                      }    a1=a2=i1=i2=0;
Memo2->Lines->Clear();
     fp=fopen(OpenDialog1->FileName.c_str(),"rb");
if( fp!=NULL) {
fseek(fp,0,2);
zflag_file_b=0;
zflag_file_e=ftell(fp);
i1=StrToInt(Edit_bcc_begin->Text);
i2=StrToInt(Edit_bcc_end->Text);
        //如果指定的BCC文件結束位置小于等于文件大小則設置有效,否則就是實際的文件尾
         if(i2!=0 && i2<=zflag_file_e) zflag_file_e=i2;
        //如果指定的開始位置小于等于結束位置則設置有效,否則為0
         if(i1>=0 && i1<=zflag_file_e)  zflag_file_b=i1;
fseek(fp,zflag_file_b,0); //到開始位置
zflag1=1;                 //點亮BCC指示燈
    for(a1=a2=0;a1<280 && !feof(fp) && a2Lines->Add(ach); a1=-1,a2  ; } else {
       ach[a1]=ch; }                              } //end for
    Edit_bcc_q->Text=ftell(fp); //當前位置(注意賦值方式,體會ANSISTRING類型的構造函數)
               }   //end fp
   }   // end if
}
//---------------------------------------------------------------------------
C語言中的文件被看作是“流設備”,對文件的操作屬于I/O操作,不是C語言的核心組成,也就是說,C語言中沒有直接的I/O操作定義,而是用函數來實現的。對于文件數據的讀取是“串行”的,文件是一個字節的序列,基本的文件操作函數每次只讀取一個字節。這種對于字節的控制使得C語言具有強大的靈活性。 從數據的組織形式講,可以將文件分為ASCII碼文件和二進制文件,ASCII碼文件也被稱做文本文件,就是我們在編輯文件存盤時所用的“純文本”格式,我們要用到的BCC文件就可以看作是一個ASCII文件。而二進制文件是包含ASCII文件的,可以將所有文件都作為二進制文件操作。 (1)文件的打開:FILE * fopen(const char * filename,const char * mode); fopen函數返回一個FILE類型的指針,習慣上稱之為“文件句柄”,這個函數有兩點需要掌握,一是文件名需要是標准C的字符指針,例中 fp=fopen(OpenDialog1->FileName.c_str(),"r") 就是將“打開文件對話框”的ANSISTRING類型的FILENAME屬性通過“.c_str()”轉化為CHAR *類型,如果不轉化,編譯程序會給出錯誤提示。;第二點就是關于文件的打開方式,例中“r”表示打開這個文件是用于“只讀”的,其他的參數還有“w”─“只寫”、“a”─“追加”(追加到文件尾),如果在它們的后面加一個“ ”,則表示打開一個文件用于讀寫,“w ”表示建立一個新文件用于讀寫,另外在“r、w、a”之后加“b”表示是對二進制文件進行操作。如果返回NULL,則表示打開文件的操作因為某種原因導致失敗,文件句柄不可使用。例中 if( fp!=NULL) 就是進行這種判斷。 (2)文件的讀寫:int fgetc(FILE * stream); int fputc(int c,FILE * stream); char fgets(char * string,int n,FILE * stream); int fputs(char * string,FILE * stream); int fscanf(FILE * stream,char * format,); int fprintf(FILE * stream,char * format,); int fread(void * buf,int size,int count,FILE * stream); int fwrite(void * buf,int size,int count,FILE * stream); 讀操作都是從文件流->內存變量,寫都是從內存變量->文件流。 例中 if((ch=fgetc(fp))=='\n') 是從fp指示的文件流中讀取一個字節放入ch內存變量,然后判斷它是否是回車鍵,如果不是就將ch中的字符拷至ach字符串,如果是,就意味著一行結束了,就將這一行追加到BCC編輯框中。與輸入函數fgetc()類似,fputc()是將內存變量c中的一個字節寫到文件流。與下面兩組函數相比這兩個函數具有很強的通用性,因為它們讀寫的就是一個底層的字節。 fgets()從文件流中讀取(n-1)個字符到字符串變量string中,讀入完畢自動向字符串末尾增加一個'\0'結束字符;fputs()是將string字符串中的字符寫入stream文件流。與fgetc()和fputc()不同的是這兩個函數讀寫的是字符串變量而不是單個字符變量。此二者是針對文本格式數據的。 fscanf()可以從文件流中獲取指定格式的數據,fprintf()可以向文件流輸出指定格式的字符文本。與它們的最初原形scanf和printf相比只是輸入、輸出的方向由鍵盤和屏幕改為文件流。顯然,文件中的被操作數據不管是輸入輸出都是字符文本。 fread()是一個結構化讀函數,與fwrite()配合使用能夠實現類似于數據庫讀寫記錄的效果。而且它們操作的是二進制數據,所以它們具備更強的通用性。 (3)文件的定位:long ftell(FILE * stream); int fseek(FILE * stream,long offset,int fromwhere); 前面介紹的文件讀寫函數都是順序進行的,這是流式文件本身的一個特點。如果我們要隨機讀取數據,就需要用到fseek()定位函數,與之配合使用的是ftell()報告位置函數。 例中 fseek(fp,zflag_file_b,0)是將文件讀寫指針定位到從文件頭開始跳過zflag_file_b個字節數處。formwhere起始位置有三個可取值:0──從文件開頭;1──從文件讀寫指針的當前位置;2──從文件末尾。offset是一個long變量,可以直接指定,也可以由ftell()獲得當前位置。 例中 Edit_bcc_q->Text=ftell(fp)就是將當前位置記入“BCC文件當前字節”編輯框。 應該指出的是,文件位置指針的定位是一個不很好掌握的概念,在發生了讀寫操作后它的當前位置究竟是如何指向的,尤其是到達文件尾時的邏輯指向是初學者較難理解的,最好能夠編寫一個小程序通過ftell()的位置匯報來仔細體味。 (4)文件的關閉:int feof(FILE * stream); int fclose(FILE * stream); 在第三節中有如下代碼 if(feof(fp) || ftell(fp)>zflag_file_e) { zflag1=0; fclose(fp); } 是判斷如果文件結束或者當前位置超過指定的“BCC文件結束字節”就關閉文件。關閉文件是文件使用完畢之后要做的操作,釋放文件句柄資源,其操作很簡單。需要說明的是判斷文件是否結束的方法,對于文本文件可以通過看讀到的數據是否為EOF(end of file)來判斷是否讀寫到達文件尾,但對于二進制文件這種方法是不適宜的,通用的判斷方法就是用feof()函數,返回非零值表示到達文件尾。 第五節:文件列表的操作 在本節中,我們要完整的討論如何實現文件列表。內容包括:實現文件列表的思路;具體的代碼實現;ListBox的操作、Memo的操作、SaveDialog和OpenDialog對話框的操作。 首先我們來分析列表應該具備的功能:在啟動系統時就要將已有的郵件列表內容加載到內存,顯示在程序畫面中;用戶可以選擇查看某一選項的內容;可以打開一個選項,將發送的“現場恢複”,以便繼續發送;可以將當前的發送現場保存為一個“發送郵件管理文件”,以備下次發送;可以刪除從列表中增加、刪除某個管理文件;在系統退出時能夠保存維護之后的列表。 我們的實現方法是這樣的,在FormCreate()中加載列表文件──當前目錄下的faa.list,用保存文本編輯框內容和內存變量數值的方法保存現場,用恢複文本編輯框內容和內存變量數值的方法恢複現場,在系統退出時保存改變之后的faa.list文件。下面就是具體實現的代碼: 系統啟動時,裝入列表
void __fastcall TForm1::FormCreate(TObject *Sender)
{
   OpenDialog1->InitialDir=".\\";
   SaveDialog1->InitialDir=".\\";
if((fp1=fopen("faa.list","r"))!=NULL) {
   zflag_list=1;
   fclose(fp1);
   ListBox1->Items->Clear();
   ListBox2->Items->LoadFromFile("faa.list");
   } else zflag_list=0; 
}
//---------------------------------------------------------------------------
首先初始化打開和保存文件對話框的初始目錄為當前目錄,然后檢測是否存在faa.list列表文件,存在的話就用LoadFromFile方法加載到ListBox2列表框中。 按鍵操作:
void __fastcall TForm1::ListBox2KeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
{
if (Key == VK_INSERT)
  if (OpenDialog1->Execute())
     ListBox2->Items->Add(OpenDialog1->FileName);
if (Key == VK_DELETE)
    ListBox2->Items->Delete(ListBox2->ItemIndex);
}
//---------------------------------------------------------------------------
文本編輯框的按鍵操作我們在第四節“BCC文件的操作”中已經詳細介紹,此處用這段代碼實現管理文件選項的添加和刪除。 選擇列表中某項時,顯示其內容:
void __fastcall TForm1::ListBox2Click(TObject *Sender)
{
Memo4->Lines->Clear ();
Memo4->Lines->LoadFromFile (ListBox2->Items->Strings[ListBox2->ItemIndex]);
}
//---------------------------------------------------------------------------
如果用戶單擊選擇了列表中的某一項,那麼就用LoadFromFile方法裝入選擇的管理文件,注意學習用ListBox2->Items->Strings[ListBox2->ItemIndex]指定當前選項為文件名的方法,ListBox2->ItemIndex返回當前選項的索引值。 關閉系統時,保存列表:

void __fastcall TForm1::Button3Click(TObject *Sender)
{
ListBox2->Items->SaveToFile("faa.list");
exit(1);
}
//---------------------------------------------------------------------------        下面的代碼將實現保存當前發送現場的功能,首先讀源碼:        保存一個“發送郵件管理文件”:
void __fastcall TForm1::Button5Click(TObject *Sender)
{
Memo4->Lines->Clear();
Memo4->Lines->Add(Edit1->Text);
Memo4->Lines->Add(Edit2->Text);
Memo4->Lines->Add(Edit3->Text);
Memo4->Lines->Add(Edit4->Text);
Memo4->Lines->Add(Edit5->Text
        
系統時間:2024-03-28 18:52:09
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!