從Turbo Vision原始碼看事件驅動 (轉)

worldblog發表於2007-12-13
從Turbo Vision原始碼看事件驅動 (轉)[@more@]

1:緣起
在傳統的中,一個具有互動功能的應用程式的主體無疑是如下的一段迴圈程式碼:不斷讀取的輸入,根據輸入採取相應的動作,完成所需的功能。例如:
bool quit=false;
char ch;
while(!quit) {
 ch=read_input();
 switch(ch) {
 case 'i':
 // ... …
 break;
 case 'q':
 quit=true; // … …
 break;
 default:
 // ... …
 } }
而基於事件機制的程式則不大相同,主程式可能如下這般:
int main()

TMyApp myApp;
  myApp.run();
  return 0;
}
在整個程式中,唯一與事件有點關係的要屬handleEvent了,它可能並不像你我想像的那樣(注:以Turbo Vision中的應用程式為例,根據類的繼承關係,函式的序列依次為:TMyApp::run()  TApplication::run()  TGroup::execute()  TMyApp::handleEvent() 來處理事件):
void TMyApp::handleEvent(TEvent& event) {
  TApplication::handleEvent(event);  // act like base!
  if( event.what == evCommand ) {
  switch( event.message.command ){
  case cmMyNewWin:  // but respond to additional commands
  myNewWindow();  // define action for cmMyNewWin
  break;
  default:
 // … …
  return;
  }
  clearEvent( event );  // clear event after handling
  }
}
從這裡,我們明顯可以看到一份簡單,一個鴻溝:沒有了令人頭疼的迴圈,程式中的只需要處理自己收到的事件,程式的結構變得異常簡單清晰;但也正是這份簡單,讓本來清楚明白的流程變得神秘起來,沒有了讀取使用者輸入,擺放在面前的已經是送到的事件,這多少有點讓人措手不及,前面的路是誰鋪就的?一切的一切,都緣起於事件驅動機制:讀取了使用者的輸入,並把其包裝成事件,然後按照一定的規則分發給不同的物件;正是它,造就了簡單,也成就了神秘;有了它,使用者才可以把所有的精力集中於事件的處理上,而不用分心其他瑣事。例如:在一個非活動視窗上按下滑鼠左鍵,使用者只需確保該視窗在接收到該訊息時,能夠把自己調到前臺,正確地顯示即可。在滑鼠輕點與視窗響應之間,在幕後默默地做了許多事情:讀取滑鼠的動作並形成的事件,按照一定的規則把事件傳送給該非活動視窗。
系統替我們完成的這許多事情,給事件驅動蒙上幾分神秘、幾分誘惑。不論是好奇心、還是求知慾,都使人有揭開面紗,一探究竟的衝動。最直接的方法是去看原始碼,借用一句話:“原始碼之前,了無秘密”。原始碼之中,會清楚地顯示系統是如何收集使用者的輸入,又是如何把使用者的輸入打包成事件,分發給程式中的各個物件的。追蹤到原始碼級別,相信已經足以理解事件驅動機制。然而,去哪兒找到這樣的原始碼呢?是基於事件驅動的,但哪兒有其原始碼?作業系統倒是有原始碼,然而,面對其浩如煙海的,如何簡潔、幹練地抽出事件驅動的原始碼而不至於陷於泥沼無法自拔?因此,最好是能夠有一個基於事件驅動的、微型的應用程式,既能把事件驅動機制講個清楚明白又不至於嚇退登山者。Turbo Vision無疑是最佳人選。
Turbo Vision是一個應用程式框架,為基於DOS的應用程式開發提供了全新的方法,可以為使用者生成完整的介面,包括視窗、對話方塊、選單、滑鼠以及簡單的編輯器等。Borland C++3.1中就附帶有Turbo Vision的原始碼,網上也可以找到Turbo Vision的原始碼。
Turbo Vision應用程式的框架如圖:

TMyApp--&gtTApplication--&gtTProgram--&gtTGroup--&gtTView--&gtT
  --&gtTProgInit --&gtTStreamable
 
圖1:Turbo Vision應用程式框架圖
一個Turbo Vision應用程式是檢視、事件及啞物件的組合。下面,分別對檢視和啞物件作一簡單的介紹,至於事件,後面會有詳盡的介紹。
檢視是螢幕上可見的程式元素物件。在Turbo Vision程式中,凡可見的,都是檢視。標題、視窗邊框、捲軸、選單條等都是檢視。檢視也可以組合,形成複雜的程式元素,如對話方塊。這些檢視的集合稱為組。組作為特殊的檢視,由其它稱為其子檢視的元素組成,而組自身也可以作為單一的檢視與其他檢視形成更復雜的元素,這樣形成了一個複雜的檢視鏈。
啞物件是程式中非檢視的物件,在螢幕上不顯示,無法與使用者互動,故稱為其啞物件。它們完成計算、與外圍裝置的通訊等後臺任務,當其需要顯示資訊時,必須透過檢視來進行。
這是隻對Turbo Vision做一簡單的介紹,在後面用到某些概念時,會略加註釋,幫助讀者理解。
2:事件的介紹
事件是一個無法再細分的資訊包,描述了應用程式必須響應的具體事件。每一次擊鍵、每一個滑鼠動作以及程式的某一部分引起的某種特定情況都構成一個單獨的事件。因此,使用者輸入一個單詞不是一個事件,而是一系列獨立的擊鍵事件的集合。
不同的事件具有不同的性質,具有不同的結構,因此,有必要先了解不同種類的事件。事件的結構如下:
struct TEvent{
  ushort what;
  union{
  MouseEventType mouse;
  KeyDownEvent keyDown;
  MessageEvent message;
  };
  void getMouseEvent();
  void getKeyEvent();
};
由事件TEvent的結構可以清楚地看到,從本質上講,有四類事件:滑鼠事件,鍵盤事件,訊息事件和空事件,對應於TEvent::what成員的四個可能取值,evMouse,evKeyboard,evMessage,evNothing。根據TEvent::what的值,TEvent物件可以決定自身的型別,使用相應的成員函式getMouseEvent()或getKeyEvent()來獲得事件。
滑鼠事件包括四種:滑鼠鍵的按下與鬆開導致的evMouseDown、evMouseUp事件,移動滑鼠產生的evMouseMove事件,保持按下某一鍵產生的evMouseAuto事件等,故滑鼠事件的結構如下:
struct MouseEventType{
  uchar buttons; // 滑鼠的按鍵
  Boolean doubleClick; // 是否雙擊
  TPoint where; // 發生滑鼠事件時,滑鼠的位置
};
鍵盤事件則簡單得多。當按下某一鍵時,產生一個evkeyDown事件,記錄按鍵資訊。其結構如下:
struct KeyDownEvent{
  union{
  ushort keyCode;
  CharScanType charScan;
  };
};
struct CharScanType{
  uchar charCode; // 字元程式碼
  uchar scanCode; // 掃描碼
};
訊息事件分命令、廣播與使用者資訊三種,evCommand、evBroadcast對應於命令與廣播,使用者資訊可以表示使用者定義的常量。三種事件的不同之處在於其傳送的方式,關於事件的傳送方式在後面會有更詳細地討論。訊息事件的結構如下:
struct MessageEvent{
  ushort command;
  union{
  void *infoPtr;
  long infoLong;
  ushort info;
  short infoInt;
  uchar infoByte;
  char infoChar;
  };
};
空事件表示一個已經處理完畢的事件。該事件內含有的資訊不再有用,可以忽略。當Turbo Vision物件處理完一個事件後,會呼叫成員函式void clearEvent(TEvent &e)將事件設定為空事件。
許多事件最後都轉換成某種命令而結束。例如:在狀態行裡的某個狀態項上按下滑鼠鍵,會產生一個滑鼠事件。狀態行物件在處理此事件時,透過產生一個掛起的命令事件響應滑鼠的動作,繫結在該狀態項物件上的命令決定了命令事件的command資料成員,在下次呼叫getEvent獲取事件時,獲得剛才產生的命令事件。
對事件有了簡單的概念之後,下面進入正題:事件驅動。
3:事件驅動機制
面對事件驅動的千頭萬緒,哪裡才是最佳的切入點呢?我們想了解的主要有兩個方面:(1)系統是如何收集使用者的輸入,把它包裝成事件的?(2)系統是如何去分發這些事件的?因為這是事件驅動機制中最神秘的地方,至於程式中的物件是如何處理這些事件的,則並非我們關心的重點。
我們還是從主程式main()開始吧,依照函式的呼叫序列,順藤摸瓜,把系統在幕後替我們做的事件逐一拿到前臺,把事件驅動機制的神秘面紗慢慢揭開。
TMyApp::run()--&gtTApplication::run()--&gtTProgram::run()--&gtTGroup::execute()
圖3:Turbo Vision程式的呼叫序列
類TGroup的部分原始碼摘錄如下:
ushort TGroup::execute(){
  do  {
  endState = 0;
  do  {
  TEvent e;
  getEvent( e ); // 讀取使用者的輸入
  handleEvent( e ); // 處理使用者的輸入
  if( e.what != evNothing )
  eventError( e ); // 如若事件沒有被處理,則廢棄此事件
  } while( endState == 0 ); // 若該組是模態,則會一直,直到呼叫
// endModal成員函式終止組的模態
  } while( !valid(endState) );
  return endState;
}
在這裡,我們又看到了熟悉的流程:不斷地讀取使用者的輸入,然後進行處理,直到使用者退出。這是在意料之中的,正是這樣的迴圈完成了互動。即使是基於事件驅動的,也少不了這段迴圈程式碼。
3.1:事件的產生
事件從何而來,系統是如何將使用者的輸入收集起來,並加以包裝的,這要歸功於神秘的void getEvent(TEvent& event)函式了。getEvent函式是Turbo Vision中唯一必須關心事件來源的函式。由類的繼承關係可知:TGroup繼承自TView,而自己並未過載基類的getEvent。因此,在TGroup::execute()中,呼叫的是TView::getEvent,追蹤可知:
void TView::getEvent( TEvent& event ){
  if( owner != 0 )
  owner->getEvent(event);
}
檢視類TView呼叫其所有者的getEvent成員函式,而在一個Turbo Vision應用程式中,一個TProgram(或TApplication)物件是所有檢視類的最終擁有者,因此,在預設情況下,每個getEvent呼叫都終止於TProgram::getEvent(除非使用者自己修改了某個檢視類的getEvent成員函式)。TProgram::getEvent的原始碼如下:
void TProgram::getEvent(TEvent& event){
  if( pending.what != evNothing ){
  event = pending;
  pending.what = evNothing;
  } // 考慮是否有上一個事件產生的掛起事件
  else{
  event.getMouseEvent();
  if( event.what == evNothing ){
  event.getKeyEvent();
 // 依次考慮是否有滑鼠事件、鍵盤事件
  if( event.what == evNothing )
  idle();
  }
  }
  if( statusLine != 0 ) {
   // 對於狀態行的鍵盤和滑鼠事件,直接轉換成相應的命令事件
 // 從而避免一個無謂的迴圈。
  if( (event.what & evKeyDown) != 0 ||
  ( (event.what & evMouseDown) != 0 &&
  firstThat( hasMouse, &event ) == statusLine )  )
  statusLine->handleEvent( event );
  }
}
在TProgram::getEvent中,首先檢查是否有由TProgram::putEvent產生的掛起事件,然後依次檢查滑鼠事件、鍵盤事件,如若都沒有,則呼叫idle函式,表示當前沒有事件發生。如果使用者想增加新型事件(如:從口讀取字元),可以在應用程式物件中過載TApplication::getEvent。在getEvent的迴圈程式碼中增加一個getComEvent(event)呼叫即可。如果使用者不想修改迴圈程式碼,可以過載idle函式,在沒有滑鼠、鍵盤事件時,執行getComEvent。有一點需要注意的是:在idle中執行的任務不能掛起應用程式,否則會堵塞使用者的輸入,造成程式不能響應的假象。
至此,事件的獲取已經逐漸明朗,依舊雲遮霧罩的是getMouseEvent、getKeyEvent等其實現細節。下面列出其原始碼,略作解釋:
1:掛起事件(pending)
掛起事件由putEvent產生,檢視的putEvent呼叫其所有者的putEvent,因此,預設情況下,所有檢視的putEvent呼叫都終止於TProgram::putEvent。TProgram::putEvent將event到類的靜態變數pending中去,供下次TProgram::getEvent呼叫時返回。
void TProgram::putEvent( TEvent & event )
{  pending = event; }
2:滑鼠事件
滑鼠事件由TEvent::getMouseEvent函式獲得,追蹤其呼叫序列可知:
TEvent::getMouseEventTEventQueue::getMouseEvent,而TEventQueue::getMouseEvent的原始碼如下 (TEventQueue為滑鼠事件的FIFO佇列,可容納的事件個數為16個) :
void TEventQueue::getMouseEvent( TEvent& ev ){
  if( mouseEvents == True ){
  getMouseState( ev ); // 獲得當前滑鼠的狀態
  if( ev.mouse.buttons == 0 && lastMouse.buttons != 0 ){
  ev.what = evMouseUp;
  lastMouse = ev.mouse;
  return; // 獲得一鬆開按鍵事件(evMouseUp)
  }
  if( ev.mouse.buttons != 0 && lastMouse.buttons == 0 ){
  if( ev.mouse.buttons == downMouse.buttons &&
  ev.mouse.where == downMouse.where &&
  ev.what - downTicks <= doubleDelay )
  ev.mouse.doubleClick = True; // 判斷事件雙擊
  downMouse = ev.mouse;
  autoTicks = downTicks = ev.what;
  autoDelay = repeatDelay;
  ev.what = evMouseDown;
  lastMouse = ev.mouse;
  return; // 返回一滑鼠雙擊事件(evMouseDown)
  }
  ev.mouse.buttons = lastMouse.buttons;
  if( ev.mouse.where != lastMouse.where ){
  ev.what = evMouseMove;
  lastMouse = ev.mouse;
  return; // 返回一滑鼠移動事件(evMouseMove)
  }
  if( ev.mouse.buttons != 0 && ev.what - autoTicks > autoDelay ){
  autoTicks = ev.what;
  autoDelay = 1;
  ev.what = evMouseAuto;
  lastMouse = ev.mouse;
  return; // 按下滑鼠按鍵不鬆開,則產生evMouseAuto事件
  }
  }
  ev.what = evNothing; // 無滑鼠事件
}
個人以為,追蹤到這一步,已可初窺滑鼠事件的端倪,再細究下去,就追蹤到Turbo Vision的滑鼠類TMouse及其低階支援類THWMouse,在那裡,呼叫中斷程式int86(0x33,&regs,&regs)要求系統提供關於滑鼠的支援,非本文之初衷。
3:鍵盤事件
相比之下,鍵盤事件則簡單得多,直接呼叫中斷處理程式來獲取鍵盤上的按鍵資訊:
void TEvent::getKeyEvent()
{ // 呼叫中斷獲得鍵盤事件
  asm {
  MOV AH,1;
  INT 16h;
  JNZ keyWaiting;
  };
  what = evNothing;
  return;
keyWaiting:
  what = evKeyDown;
  asm {
  MOV AH,0;
  INT 16h;
  };
  keyDown.keyCode = _AX;
  return;
}
4:狀態行(TStatusLine)的特殊處理
如果滑鼠事件或鍵盤事件發生在狀態行上,在TProgram::getEvent返回之前,直接呼叫TStatusLine的事件處理程式handleEvent,把滑鼠事件或鍵盤事件轉換成命令事件,呼叫putEvent把事件重新放入到事件佇列中,供下次呼叫getEvent時返回,從而減少了把滑鼠或鍵盤事件傳送到狀態行、再轉換成命令事件、再返回的這個無謂迴圈。
3.2:事件的傳送
事件已經由TProgram::getEvent取了出來,各個物件也知道如何去處理它們到接收到的事件,中間的橋樑無疑是事件傳送機制。這需要對組的概念、對事件的本質進行更深入一層的瞭解。
組是一個含有並管理其他檢視(稱為組的子檢視)的物件。組把其管理區域中的一部分授權給自己的一個子檢視使用。例如:TApplication是一個組物件,管理著整個螢幕,含有三個子檢視,TMenuBar、TDeskTop、TStatusLine,螢幕頂行分給TMenuBar,螢幕底行分給TStatusLine,其餘的部分歸TDeskTop擁有。這樣誠然不錯,然而,不允許子檢視重疊是沒有道理的,也是行不通的。那麼如何處理重疊的子檢視呢?
把子檢視連線到一個組上的處理,稱為插入過程。建立子檢視後,將其插入到組中。組記錄著子檢視的插入順序,即Z序。Z序決定著組中子檢視的顯示順序與事件的傳遞順序。組中的子檢視鏈為一迴圈連結串列,指標TView *last指向最後檢視鏈中的最後一個子檢視。每次插入,都將新的子檢視插入到鏈首。顯示的時候,按照插入的順序逐一顯示,因此,最後插入的子檢視顯示在最前面,覆蓋前面插入的子檢視。
Turbo Vision的檢視可以被定義為模態檢視,即只有該檢視和其子檢視能夠與使用者互動,程式中的其他物件無法接收到使用者的輸入。例如:對話方塊就是一個模態檢視。當一個對話方塊活動時,對話方塊之外的一切都無法接收到使用者的輸入。不過,狀態行(TStatusLine)是一個例外:在程式執行過程中,狀態行一直處於可選用狀態,即使程式正在執行一個不含狀態行的模態檢視時。
在前面,將事件分為滑鼠事件、鍵盤事件、訊息事件和空事件,其依據是事件的來源;根據事件的傳遞方法,可以對事件進行重新分類:位置事件(即滑鼠事件)、焦點事件(即鍵盤事件和命令事件)和廣播事件(廣播事件和使用者定義的資訊)。
事件總是從當前的模態檢視開始傳遞的。一般情況下,當前的模態檢視是程式物件(TApplication)。當執行一個模態對話方塊時,該對話方塊是當前的模態檢視。
事件的傳遞過程位於TGroup::handleEvent函式中,其原始碼及相應的輔助函式如下:
void TGroup::handleEvent( TEvent& event ){
  TView::handleEvent( event ); // 處理滑鼠的選擇操作
  handleStruct hs( event, *this ); // 把事件、檢視包裝成一個結構
  if( (event.what & focusedEvents) != 0 ){ // 考慮焦點事件
  phase = reProcess;
  forEach( doHandleEvent, &hs );

  phase = phFocused;
  doHandleEvent( current, &hs );

  phase = phPostProcess;
  forEach( doHandleEvent, &hs );
  }
  else{
  phase = phFocused;
 // 考慮位置事件
  if( (event.what & positionalEvents) != 0 )
  doHandleEvent( firstThat( hasMouse, &event ), &hs );
  else // 考慮廣播事件
  forEach( doHandleEvent, &hs );
  }
} // end_of_handleEvent

struct handleStruct
{ // 把事件、檢視形成一個處理結構
  handleStruct( TEvent& e, TGroup& g ) : event( e ), grp( g ) {}
  TEvent& event;
  TGroup& grp;
};

static void doHandleEvent( TView *p, void *s )
{ // 子檢視p完成焦點事件的處理
  handleStruct *ptr = (handleStruct *)s;
 // 如果檢視p不存在,或者檢視p被禁止且事件為位置事件或焦點事件
  if( p == 0  ||  ( (p->state & Disabled) != 0 &&
  (ptr->event.what & (positionalEvents | focusedEvents)) != 0) )
  return;

  switch( ptr->grp.phase ){
  case TView::phPreProcess:
  if( (p->options & ofPreProcess) == 0 )
  return;
  break;
  case TView::phPostProcess:
  if( (p->options & ofPostProcess) == 0 )
  return;
  break;
  }
 // 如果本檢視能夠處理此事件,則呼叫自己的handleEvent進行處理
  if( (ptr->event.what & p->eventMask) != 0 )
  p->handleEvent( ptr->event );
}

void TGroup::forEach( void (*func)(TView*, void *), void *args )
{ // 以Z序為檢視鏈中的每一個檢視呼叫func函式,引數為args
  TView *te= last;
  TView *temp = last;
  if( temp == 0 ) return;
  TView *next = temp->next;
  do  { 
  temp = next;
  next = temp->next;
  func( temp, args );
  } while( temp != term );
}

TView *TGroup::firstThat( Boolean (*func)(TView *, void *), void *args )
{ // 在檢視鏈中,以Z序找到第一個呼叫func、引數為args,
 // 返回值為真的子檢視
  TView *temp = last;
  if( temp == 0 ) return 0;

  do  {
  temp = temp->next; // 得到第一個子檢視
  if( func( temp, args ) == True )
  return temp;
  } while( temp != last );
  return 0;
}
由上面那些長長的原始碼,我們可以抽出三類事件的傳遞方式:
1:位置事件(positionalEvents)
從模態檢視開始,在子檢視鏈中,以Z序搜尋,找到第一個含有事件發生位置的子檢視,然後,模態檢視將事件傳遞給該子檢視,由其呼叫自己的事件處理程式進行處理。由於檢視可以重疊,因此,可能有多個前後重疊的子檢視都包含有此滑鼠事件的位置,以Z序進行搜尋能夠保證接收該事件的檢視位於最前面(最靠近使用者),即使用者在其上按動滑鼠的檢視。
這種過程一直向下進行,直到到達終端檢視或者因為事件發生的位置沒有檢視時才會停止。如有接收檢視,則接收檢視會處理該事件;如若沒有檢視,則該事件已到達產生該事件的物件,這個物件會處理自己產生的事件。
2:焦點事件(focusedEvents)
模態檢視首先獲得焦點事件,然後,按照以下三個階段傳送該焦點事件:
(1)焦點事件以Z序送給檢視鏈中每一個選項中ofPreProcess標誌為1的子檢視(焦點事件沒有被檢視鏈中位於前面的子檢視所處理);
(2)如若焦點事件沒有被處理,則被傳送給程式中當前具有焦點的子檢視;
(3)如若焦點事件仍舊沒有被處理,則依舊以Z序送給檢視鏈中的每一個選項中的ofPostProcess標誌為1的子檢視。
程式如下尋找具有焦點的子檢視:從模態檢視開始,尋找其被選擇子檢視,該過程一直進行到終端檢視,該終端檢視即為具有焦點的檢視。
3:廣播事件
廣播事件的傳遞比較容易理解,以Z序向模態檢視的每一個子檢視都傳遞,如若子檢視本身就是一個組,則遞迴進去,保證模態檢視的所有子檢視都接收到該廣播事件。
對於使用者定義的新型事件,預設情況下,Turbo Vision廣播這些事件,但使用者也可以修改TGroup::handleEvent,自定義其傳遞方式。
3.3:事件的處理
應該說,應用程式中的物件是如何處理它們接收到的事件並不屬於本文討論的範疇,但由於一些物件在處理事件的過程中,會呼叫putEvent產生掛起事件,或者呼叫message函式來傳送訊息事件,因此,在這裡只以TStatusLine為例進行簡單的討論。
TStatusLine的handleEvent(…)及輔助函式摘錄如下:
void TStatusLine::handleEvent( TEvent& event ){
  TView::handleEvent(event); // 處理檢視的選擇操作

  switch (event.what){
  case  evMouseDown:{
  TStatusItem *T = 0;
  do  {
  TPoint mouse = makeLocal( event.mouse.where );
  if( T != itemMouseIsIn(mouse) )
  draw( T = itemMouseIsIn(mouse) );
  } while( mouseEvent( event, evMouseMove ) );
 // 直到出現一個鬆開滑鼠事件,完成一個選擇動作
  if( T != 0 && commandEnabled(T->command) )
  { // 如果滑鼠點選的狀態項未被禁用
  event.what = evCommand;
  event.message.command = T->command;
  event.message.infoPtr = 0;
  putEvent(event); // 把滑鼠事件轉換成掛起的命令事件
  }
  clearEvent(event); // 滑鼠事件處理完畢,清除
  drawView();
  break;
}
  case evKeyDown:{
  for( TStatusItem *T = items; T != 0; T = T->next ){
  if( event.keyDown.keyCode ==  T->keyCode &&
  commandEnabled(T->command)){
  event.what = evCommand;
  event.message.command = T->command;
  event.message.infoPtr = 0;
  return; // 對於按鍵事件,直接將事件轉換成命令事件返回
  } 
}
  break; 
}
  case evBroadcast:
  if( event.message.command == cmCommandSetChanged )
  drawView(); // 對於廣播,改變了命令集,則重新顯示狀態行
  break;  } }

Boolean TView::mouseEvent(TEvent& event, ushort mask)
{ // 一直從取事件,直到出現mask、evMouseUp之中的任意一個為止。
  do {
  getEvent(event);
  } while( !(event.what & (mask | evMouseUp)) );
 // 如若出現evMouseUp事件,則返回0;否則,返回1
  return Boolean(event.what != evMouseUp);
}
這裡不再詳細羅列出不相關函式的原始碼,只是給出簡單地介紹以助讀者能夠理解程式的流程,由於其函式的命名相當清楚,當不影響讀者對程式的理解。主要想說明的是putEvent的使用:
TStatusLine繼承自檢視類TView,在其事件處理程式中,首先呼叫基類的handleEvent處理檢視的選擇操作;然後,把滑鼠事件、鍵盤事件轉換成相應的命令事件,放入到TProgram的靜態變數pending中,供下次getEvent呼叫時返回(即響應相應的滑鼠動作或按鍵動作);對於命令集發生變化的廣播,呼叫顯示成員函式drawView重新顯示狀態行。
至此,事件驅動機制已經清清楚楚、明明白白呈現於讀者的面前。最後,以一個簡單的例子結束本文:
在如BC++3.1的環境中,在狀態行的某項上(例如:Alt-X)按下滑鼠左鍵,事件迴圈的流程如下(假設此時無掛起事件):
此時,程式中的物件有application,其有三個子物件:menuBar、deskTop、statusLine,其中,deskTop包括兩個window物件,每個windows物件包括一個frame、兩個scrollBar、一個scroller。事件的迴圈如下(在無法用圖表示的情況下,希望能夠講清楚):
在TGroup::execute()迴圈中,
getEventTProgram::getEvent(event1)TEvent::getMouseEvent(event1)TEventQueue::getMouseEvent(event1),event1被設定成滑鼠按下事件(evMouseDown),然後原路返回至TProgram::getEvent,由於滑鼠的這個動作是在TStatusLine上的,故在返回至TGroup::execute之前,首先呼叫TStatusLine::handleEvent(event1)對滑鼠事件進行轉換;在TStatusLine::handleEvent(event1)中,將這個滑鼠按下動作產生的一系列evMouseAuto事件取出,直到取出evMouseUp事件(完成對滑鼠按鍵動作的響應),此時,event1為evMouseUp事件;然後,生成一掛起的命令事件,命令cmQuit為按鍵所處的狀態項Alt+x上繫結的命令,並清除此按鍵事件(event1為空),退出TStatusLine::handleEvent(event1),返回至TProgram::getEvent(event1)。在TGroup::handleEvent(event1)中,由於event1已經為空,故直接返回;然後,再次呼叫TProgram::getEvent(event2),得到剛才產生的掛起的命令事件cmQuit,交由TGroup::handleEvent(event2)傳送給其子物件(焦點事件),導致TProgram::handleEvent呼叫endModal(cmQuit),終止當前應用程式的模態,關閉整個程式,從使用者的角度完成對滑鼠動作的響應。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992982/,如需轉載,請註明出處,否則將追究法律責任。

相關文章