[轉貼] Windows編程和麪向對象技術 chap4

第四課 工具條和狀態欄

  在上一課中,同學們已經學到了一些基本的界面設計技術。這一課將指導大家如何設計實現工具條和狀態欄,並進一步加深對消息驅動機制的理解。

T4_1.tif (121352 bytes)

4.1 工具條和狀態欄

  如圖4.1所示,工具條和狀態欄是一個完善的Windows應用程序的重要組成部分(但不是必需的部分)。工具條一般位於主框架窗口的上部,上面有一些圖形按鈕。當用戶用鼠標在某一按鈕上單擊時,程序就會執行相應的命令;當鼠標在按鈕上停留片刻後,就會彈出一個黃色小窗口並顯示工具提示。按鈕的圖形是它所代表功能的形象表示,人們對於形象圖形的辨別速度要快於抽象文字,因此工具條提供了一種比菜單更快捷的用戶接口。在一個標準的Windows應用程序中,工具條的大部分按鈕執行的命令與菜單命令相同,這樣做的目的是能同時提供形象和抽象的用戶接口,以方便用戶的使用。

  狀態欄位於主框架窗口的底部,主要用來顯示一些提示信息,可細分爲幾個窗格。狀態欄的主要功能是簡要解釋被選中菜單命令或工具條按鈕命令,並顯示SCROLL LOCK、NUM LOCK等鍵的狀態。

  這一課包括以下幾個部分:

工具條的可視化設計

工具條的編程技術

狀態欄的設計與實現

  本課假定讀者要編寫一個採樣聲音的應用程序,並給出一個名爲Record的具體例子。當然,這個例子不會真的具有采樣聲音的功能,它只是用來演示工具條和狀態欄的使用。

 

4.1 工具條的可視化設計

  從4.0版開始,Visual C++支持一種新型的工具條資源,這使得工具條的創建比以往更加方便靈活了。在MFC中,工具條的功能由類CToolBar實現。工具條資源和工具條類CToolBar是工具條的兩個要素。創建工具條的基本步驟是:

1.創建工具條資源。

2.構建一個CToolBar對象。

3.調用CToolBar::Create函數創建工具條窗口。

4.調用CToolBar::LoadToolBar載入工具條資源。

  使用缺省配置時,AppWizard會自動創建一個工具條。如圖4.1所示,這個工具條包含一些常用按鈕,如打開文件、存盤、打印等等。用戶可以修改這個工具條,去掉無用的按鈕,加入自己需要的按鈕。如果用戶需要創建兩個以上的工具條,則不能完全依賴AppWizard,需要自己手工創建之。本節將分別討論這兩種方法。

4.1.1利用AppWizard自動創建

  自動創建工具條很簡單,請讀者按以下步驟操作:

  1. 選擇 File->New命令。

  2. 在彈出的標籤式對話框中選Projects頁,然後在該頁中選中MFC AppWizard (exe)項,並在Project name一欄中輸入Record以創建一個名爲Record的工程。按回車或用鼠標點擊Create按鈕後就進入了MFC AppWizard對話框。

  3. 在MFC AppWizard對話框的第一步中選中Single document。這樣就會創建一個單文檔應用程序,若選擇Multiple documents項,則將創建一個多文檔應用程序。單文檔程序一次只能打開一個窗口,顯示一個文檔的內容,而多文檔程序一次可以打開多個窗口,顯示多個文檔的內容。

  4. 用鼠標點擊Finish按鈕,並在接着的對話框中按OK按鈕。

  完成以上操作後,工程Record被創建並被自動載入Developer Studio中。將項目工作區切換到資源視圖,並展開資源,就會發現其中有一個名爲IDR_MAINFRAME的Toolbar(工具條)資源。用鼠標雙擊“IDR_MAINFRAME”,Developer Studio會打開一個功能強大的工具條資源編輯窗口,如圖4.2所示。該窗口的上部顯示出了工具條上的按鈕,當用戶用鼠標選擇某一按鈕時,在窗口的下部會顯示該按鈕的位圖。在窗口旁邊有一個繪圖工具面板和一個顏色面板,供用戶編輯按鈕位圖時使用。

T4_2.tif (116672 bytes)

圖4.2 工具條資源編輯窗口

提示:如果讀者看不到這兩個面板,請在Developer Studio的工具條的空白處單擊鼠標右鍵,並在隨之彈出的菜單中選中Graphics和Colors兩項。

  在修改工具條以前,首先要修改菜單資源。請按以下幾步修改菜單資源:

  1. 將項目工作區切換至資源視圖,選擇並打開menu(菜單)資源類型,雙擊名爲IDR_MAINFRAME的菜單資源。

  2. 刪除Edit菜單。

  3. 刪除File菜單中除Exit以外的所有菜單項。

  4. 在File菜單後插入一個名爲&Record的新菜單,並在該菜單中插入&Start和St&op兩個菜單項,它們的命令ID(標識符)分別爲ID_RECORD_START和ID_RECORD_STOP。Start表示開始錄音,而Stop表示停止錄音。

  修改後的菜單如圖4.3所示。

T4_3.tif (27461 bytes)

圖4.3 修改後的菜單

  接下來的任務是修改工具條資源,具體步驟是:

選擇並打開Toolbar(工具條)資源類型,雙擊名爲IDR_MAINFRAME的工具條資源以打開相應的資源編輯窗口。

刪除“?”按鈕前面的所有按鈕,刪除的方法是用鼠標將要刪除的按鈕拖出工具條即可。

先選中“?”按鈕後面的空白按鈕,然後在該按鈕的放大位圖上用紅色畫一個實心圓圈,以表示開始錄音功能。再選中空白按鈕,並用黑色在放大位圖上畫一個實心矩形,以表示停止功能。

通過用鼠標拖動按鈕調整按鈕的位置,調整後的位置如圖4.4所示。

T4_4.tif (115062 bytes)

圖4.4 修改後的工具條資源

分別爲兩個新加的按鈕指定命令ID爲ID_RECORD_START和ID_RECORD_STOP。指定ID的方法是先選中一個按鈕,接着按回車鍵,在彈出的屬性對話框中輸入ID(或從ID下拉列表中分別選擇ID_RECORD_START和ID_RECORD_STOP)。注意到這兩個按鈕的ID與Record菜單中的兩個菜單項Start和Stop的ID相同,這樣同樣的命令既可以通過菜單執行,也可以通過工具條執行。

爲兩個新加的按鈕指定命令提示。請分別在兩個按鈕的屬性對話框中的Prompt欄內輸入Start record/nStart和Stop record/nStop。命令提示實際上是作爲字符串保存在String Table字符串資源中的。命令提示用來解釋命令的意義,分狀態欄提示和工具提示兩種,在Prompt欄中,二者由/n分隔開。當鼠標移動到某個菜單項或工具條上的按鈕時,在狀態欄中就會顯示狀態欄提示,當鼠標在某個按鈕上停留片刻後,工具提示就會在一個黃色的彈出式窗口中顯示出來。輸入完成後,讀者會發現Record菜單中的兩個菜單項被自動加入了相同的提示信息,這說明兩個按鈕與兩個菜單項確實是相對應的。

提示:如果覺得按鈕太小,讀者可以用鼠標拖動圍繞按鈕放大位圖的虛框的右下角,把按鈕放大些。注意工具條內的所有按鈕都將被放大

  修改完後,讀者可以編譯並運行Record,來看看修改的結果。讀者很快會注意到Start和Stop菜單項及按鈕都是灰色的。這個現象是正常的,其原因將在4.2節解釋。有趣的是工具條可以被拖動(請在工具條的空白地方拖動)並停泊在主框架窗口的任何其它邊上,並且工具條是可以浮動的,即當用鼠標雙擊工具條的空白處時,工具條變成了一個浮動窗口,可被拖動到屏幕上的任意地方。這些有趣的現象將在4.2節解釋。不管怎麼說,創建和修改工具條的任務已經完成了。4.1.2手工創建

  如果想要再加一個工具條,那麼AppWizard就無能爲力了,必須手工創建。假設Record程序的聲音採樣頻率有11KHZ和44KHZ兩檔選擇,現在我們的任務是再創建一個工具條,可讓用戶對這兩種檔次進行選擇。本來這樣的功能應該位於第一個工具條內,但爲了演示工具條的手工創建,這裏不妨來個多此一舉。

  如果Record工程不在Developer Studio中,請選擇命令File->Open Workspace打開Record工程。首先要對原來的菜單進行修改,步驟如下:

  1. 打開IDR_MAINFRAME菜單資源

  2. 雙擊Record菜單底端的空白項,在其屬性窗口中選中Separator,這樣就加入了一條分隔線。

  3. 在分隔線下面加入兩個菜單項,其屬性如表4.1所示。

 

表4.1 菜單項的屬性

Caption

ID

Prompt

&Low quality

ID_LOW_QUALITY

Low quality(11k)/n11k

&High quality

ID_HIGH_QUALITY

High quality(44k)/n44k

  接着要創建一個新的工具條資源,請按以下步驟進行:

選擇Insert->Resource命令,然後在Insert Resource對話框中選中Toolbar。按了OK按鈕後,在Toolbar資源類下就會出現一個ID爲IDR_TOOLBAR1的新資源。

在新工具條中加入兩個按鈕,如圖4.5所示。每個按鈕上都畫了一些豎線,線稀的按鈕代表低頻率採樣,線密的按鈕代表高頻率採樣。

T4_5.tif (115754 bytes)

圖4.5 新創建的工具條資源

分別爲兩個新加的按鈕指定命令ID爲ID_LOW_QUALITY和ID_HIGH_QUALITY。

 

  要把這個新設計的工具條加入到程序中,需要在程序中加入一些與創建有關的源代碼。在創建第一個工具條時,AppWizard在程序中自動加入了創建源代碼,通過仿製這些代碼,我們很容易創建出第二個工具條。

  在MFC中,工具條的功能由類CToolBar實現。工具條實際上是主框架窗口的子窗口,因此工具條對象應該依附於主框架窗口對象。在AppWizard創建的MFC程序中,主框架窗口的類名是CMainFrame,該類是MFC標準類CFrameWnd類的派生類。將項目工作區切換至類視圖並展開CMainFrame類,讀者會發現該類有一個名爲m_wndToolbar的成員。雙擊該成員,則Developer Studio會自動打開類CMainFrame所在的頭文件,並將光標停在對m_wndToolbar成員的定義處。

提示:在類視圖中雙擊某一個類名,則該類所在的頭文件會自動打開。若雙擊某一個類的成員,則會自動切換到對該成員的定義處。

對m_wndToolBar的定義如下:

CToolBar m_wndToolBar;

由此可見m_wndToolBar是一個CToolBar對象,它是CMainFrame的成員。現在請緊接着該成員加入一個新的成員:

CToolBar m_wndToolBar1;

  m_wndToolBar1代表第二個工具條。讀者不要以爲給CMainFrame加入一個CToolBar對象就完事了。實際的創建工具條的工作不會在構造CToolBar對象時完成,只有調用了類CToolBar的一些成員函數後,創建工作才能結束。

  對工具條的實際創建工作在CMainFrame::OnCreate函數中完成。OnCreate函數是在創建窗口時被調用的,這時窗口的創建已部分完成,窗口對象的m_hWnd成員中存放的HWND句柄也已有效,但窗口還是不可見的。因此一般在OnCreate函數中作一些諸如創建子窗口的初始化工作。

提示:初學者一個易犯的錯誤是在構造函數而不是在OnCreate中創建子窗口。在構造函數中,父窗口並沒有創建,如果在這時創建子窗口,則將會因爲得不到父窗口的有效HWND句柄而導致創建失敗。

  找到CMainFrame::OnCreate函數,對該函數進行一些修改,修改的部分如清單4.1的黑體字所示。在以後,凡是程序中手工修改的部分,一般都會用黑體顯示。

清單4.1 修改後的CMainFrame::OnCreate函數

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

 

if (!m_wndToolBar.Create(this) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar/n");

return -1; // fail to create

}

 

 

if (!m_wndToolBar1.Create(this) ||

!m_wndToolBar1.LoadToolBar(IDR_TOOLBAR1))

{

TRACE0("Failed to create toolbar/n");

return -1; // fail to create

}

 

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators,

sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar/n");

return -1; // fail to create

}

 

// TODO: Remove this if you don't want tool tips or a resizeable toolbar

m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |

CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);

 

m_wndToolBar1.SetBarStyle(m_wndToolBar1.GetBarStyle()|CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);

// TODO: Delete these three lines if you don't want the toolbar to

// be dockable

m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

 

m_wndToolBar1.EnableDocking(CBRS_ALIGN_ANY);

 

EnableDocking(CBRS_ALIGN_ANY);

 

DockControlBar(&m_wndToolBar);

 

DockControlBar(&m_wndToolBar1);

return 0;

}  注意在CMainFrame::OnCreate函數首先調用了CFrameWnd::OnCreate。這行代碼是AppWizard自動加入的,但有必要解釋一下。CMainFrame是CFrameWnd類的繼承類,在CMainFrame::OnCreate中首先要調用基類CFrameWnd的OnCreate函數,因爲基類也要進行一些初始化工作,而基類的OnCreate函數不會自動調用,因此需要在繼承類的OnCreate函數中顯式調用。OnCreate實際上是WM_CREATE消息的消息處理函數,讀者可能要問,爲什麼是派生類的OnCreate處理WM_CREATE消息,而不是基類的OnCreate呢。如果讀者仔細觀察OnCreate函數在CMainFrame類頭文件中的說明,就會發現在該函數前有一個afx_msg前綴。afx_msg的作用與virtual關鍵字類似,它使得被說明的函數有虛擬函數的特性,即由繼承類而不是基類的處理函數來處理消息。

  讀者可以看出黑體的代碼是仿照第一個工具條的創建代碼進行編寫的。事實上,筆者不過是將原來的代碼複製一份,然後在略作修改而已。

提示: 讀者不必爲這種“抄襲”行爲感到羞愧。由於Visual C++博大精深,各種類和函數成百上千,除非你有外星人一般的記憶力,否則是不可能記住所有東西的。用Visual C++編程,重要的是理解而不是記憶。只要你理解了程序的來龍去脈,就可以最大限度的利用現有的成熟代碼,提高程序的開發效率和可靠性。

  對第二個工具條的創建代碼的解釋是:首先,調用CToolBar::Create以創建工具條窗口,注意Create函數的參數是this指針,這是因爲主框架窗口是工具條的父窗口。接着調用CToolbar::LoadToolBar(IDR_TOOLBAR1)以載入工具條資源。然後調用CToolBar::SetBarStyle指定工具條的風格,在調用該函數時先調用CToolBar::GetBarStyle取得工具條的風格,然後在原有風格的基礎上又指定了CBRS_TOOLTIPS、 CBRS_FLYBY和CBRS_SIZE_DYNAMIC風格,這使得工具條可顯示工具提示,並可以動態改變尺寸。接着調用CToolBar::EnableDocking(CBRS_ALIGN_ANY)使工具條是可以停泊的,但還需調用CFrameWnd::EnableDocking(CBRS_ALIGN_ANY),只有這樣才能實現可停泊的工具條。最後調用CFrameWnd::DockControlBar以停泊工具條。

  編譯並運行Record看看,現在Record程序已經擁有兩個工具條了。至此創建工具條的任務已經完成,下面需要對工具條編程,以使其能夠發揮執行命令的功能。

4.2工具條的編程技術

  本節將討論一些與工具條有關的編程技術,主要包括命令處理、命令更新、按鈕風格和工具條的隱藏/顯示等技術。

4.2.1命令處理

  要使菜單和工具條執行命令,光爲它們指定命令ID是不行的,必須爲每個命令ID定義命令處理函數。如果不爲命令定義命令處理函數或下面將要提到的命令更新處理函數,則框架將自動使該命令對應的菜單項和按鈕禁止(灰化),這就是4.1節中的工具條按鈕和菜單項灰化的原因。

  利用ClassWizard可以很方便地加入命令處理函數,請讀者按以下步驟操作:

按Ctrl+W鍵進入ClassWizard。

T4_6.tif (317964 bytes)

圖4.6 ClassWizard對話框

如圖4.6所示,在Class name欄中選擇CMainFrame,在Object IDs欄中選擇ID_RECORD_START,在Messages欄中雙擊COMMAND項,則ClassWizard會彈出一個對話框詢問命令處理函數的名字,使用其提供的函數名即可。按OK按鈕後,函數OnRecordStart就被加入到了Member functions欄中。

仿照第2步,爲ID_RECORD_STOP定義一個命令處理函數。

按OK按鈕關閉ClassWizard對話框。這時讀者會發現CMainFrame類多了兩個成員函數,OnRecordStart和OnRecordStop。

現在要在這兩個命令處理函數中插入相應的源代碼以實現其功能。當然,這裏不會真的實現開始錄音和停止錄音的功能。我們只是讓這兩個函數發出一個聲音,象徵性地表示功能的執行,具體代碼如清單4.2所示。

 

清單4.2 OnRecordStart和OnRecordStop函數

void CMainFrame::OnRecordStart()

{

// TODO: Add your command handler code here

 

MessageBeep((UINT)(-1));

}

void CMainFrame::OnRecordStop()

{

// TODO: Add your command handler code here

 

MessageBeep((UINT)(-1));

}

  編譯並運行Record,可以看到Start和Stop命令已經可以執行了。

4.2.2命令更新

  雖然Start和Stop命令可以執行了,但是還有一個不足之處。在沒有開始錄音之前,Stop命令應該是禁止的,也即對應的菜單項和按鈕應是禁止的,這是因爲此時沒有必要執行該命令。錄音開始後,Stop命令應該允許,而Start命令則應變爲禁止。我們可以利用MFC的命令更新機制實現此邏輯功能。

  在菜單下拉之前,或在工具條按鈕處在空閒循環期間,MFC會發一個更新命令,這將導致命令更新處理函數的調用。命令更新處理函數可以根據情況,使用戶接口對象(主要指菜單項和工具條按鈕)允許或禁止。定義命令更新處理函數的方法如下:

按Ctrl+W鍵進入ClassWizard。

T4_7.tif (317964 bytes)

圖4.7 ClassWizard對話框

如圖4.7所示,在Class name欄中選擇CMainFrame,在Object IDs欄中選擇ID_RECORD_START,在Messages欄中雙擊UPDATE_COMMAND_UI項,則ClassWizard會彈出一個對話框詢問命令更新處理函數的名字,使用其提供的函數名即可。按OK按鈕後,函數OnUpdateRecordStart就被加入到了Member functions欄中。

仿照步2,爲ID_RECORD_STOP定義一個命令更新處理函數。

按OK按鈕關閉ClassWizard對話框。這時讀者會發現CMainFrame類多了兩個成員函數,OnUpdateRecordStart和OnUpdateRecordStop。

  命令更新處理函數有一個參數是CCmdUI類的指針,通過調用CCmdUI類的成員函數Enable(TRUE)或Enable(FALSE)可以使用戶接口對象允許或禁止。需要給CMainFrame加一個布爾型成員變量以表明是否正在錄音,這樣命令更新處理函數可根據這個變量來決定用戶接口對象的狀態。請讀者在CMainFrame類內加入下面一行代碼:

BOOL m_bWorking;

接下來請讀者按清單4.3進行修改。

清單4.3 命令更新處理

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

 

m_bWorking=FALSE;

}

void CMainFrame::OnRecordStart()

{

// TODO: Add your command handler code here

MessageBeep((UINT)(-1));

 

m_bWorking=TRUE;

}

void CMainFrame::OnRecordStop()

{

// TODO: Add your command handler code here

MessageBeep((UINT)(-1));

 

m_bWorking=FALSE;

}

void CMainFrame::OnUpdateRecordStart(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->Enable(!m_bWorking);

}

void CMainFrame::OnUpdateRecordStop(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->Enable(m_bWorking) ;

}

  m_bWorking的初值應是FALSE,對它的初始化工作在CMainFrame的構造函數中完成。m_bWorking的值在處理Start和Stop命令時會被更新以反映當前的狀態。兩個命令更新處理函數都調用了CCmdUI::Enable,該函數根據m_bWorking的值來更新命令接口對象。

  編譯並運行Record,現在Start和Stop命令的邏輯功能已經實現了。

4.2.3按鈕風格

  在Record程序中,用戶可以選擇兩種採樣頻率來錄音。用戶接口對象應該能反映出當前的採樣頻率。普通的工具條按鈕在按下後會立刻彈起來,我們希望Record程序的頻率選擇按鈕具有單選按鈕的風格,即當用戶選擇了一個採樣頻率時,該採樣頻率對應的按鈕一直處於按下的狀態,而另一個頻率選擇按鈕應處於彈起狀態。

  我們可以利用CCmdUI::SetCheck函數來實現這一功能,在命令更新函數中調用CCmdUI::SetCheck(TRUE)或CCmdUI::SetCheck(FALSE)可將用戶接口對象設定爲選中或不選中狀態,當一個用戶接口對象被選中時,相應的工具按鈕會處於按下的狀態,並且相應的菜單項的前面會加上一個選中標記。這裏需要給CMainFrame類加一個布爾型成員變量以表明當前的採樣頻率。請讀者在CMainFrame類內加入下面一行代碼:

BOOL m_bHighQuality;

接下來請讀者按清單4.4進行修改。

清單4.4

CMainFrame::CMainFrame()

{

// TODO: add member initialization code here

m_bWorking=FALSE;

 

m_bHighQuality=TRUE;

}

void CMainFrame::OnHighQuality()

{

// TODO: Add your command handler code here

 

m_bHighQuality=TRUE;

}

void CMainFrame::OnLowQuality()

{

// TODO: Add your command handler code here

 

m_bHighQuality=FALSE;

}

void CMainFrame::OnUpdateHighQuality(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->SetCheck(m_bHighQuality);

}

void CMainFrame::OnUpdateLowQuality(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->SetCheck(!m_bHighQuality);

}

  m_bHighQuality的初值是TRUE,即缺省時是高頻採樣,對它的初始化工作在CMainFrame的構造函數中完成。m_bHighQuality的值在處理High quality和Low quality命令時會被更新以反映當前的狀態。兩個命令更新處理函數都調用了CCmdUI::SetCheck,該函數根據m_bHighQuality的值來更新命令接口對象,從而使工具條按鈕具有了單選按鈕的風格。

  編譯並運行Record,讀者可以看到具有新風格的工具條按鈕。當選擇採樣頻率時,相應的菜單項前會出現一個選中標記,相應的工具條按鈕會被按下。

4.2.4工具條的隱藏/顯示

  讀者可能已經試過了Record程序的View菜單的功能。通過該菜單用戶可以隱藏/顯示工具條和狀態欄,這個功能是由AppWizard自動實現的。由於第二個工具條是手工建立的,因此它不會自動具備隱藏/顯示功能。但我們可以通過編程來實現第二個工具條的隱藏/顯示:

打開IDR_MAINFRAME菜單資源

在View菜單中加入一個名爲Toolbar1的菜單項,指定其ID爲ID_VIEW_TOOLBAR1,並在Prompt欄中輸入Show or hide the toolbar1/nToggle ToolBar1。

按Ctrl+W鍵進入ClassWizard。在Class name欄中選擇CMainFrame,在Object IDs欄中選擇ID_VIEW_TOOLBAR1,併爲該命令ID定義命令處理函數OnViewToolbar1和命令更新處理函數OnUpdateViewToolbar1。

按清單4.5修改程序。

 

清單4.5 顯示/隱藏工具條

void CMainFrame::OnViewToolbar1()

{

// TODO: Add your command handler code here

 

m_wndToolBar1.ShowWindow(m_wndToolBar1.IsWindowVisible()?

SW_HIDE:SW_SHOW);

RecalcLayout();

}

void CMainFrame::OnUpdateViewToolbar1(CCmdUI* pCmdUI)

{

// TODO: Add your command update UI handler code here

 

pCmdUI->SetCheck(m_wndToolBar1.IsWindowVisible());

}

  調用CWnd::ShowWindow(SW_SHOW)或CWnd::ShowWindow(SW_HIDE)可以顯示或隱藏窗口。由於工具條也是窗口,CToolBar是CWnd類的繼承類,故該函數也是CToolBar的成員。在命令處理函數OnViewToolbar1中,我們調用CToolBar::ShowWindow來顯示/隱藏工具條,在調用時會利用CWnd::IsWindowVisible函數作出判斷,如果工具條是可見的,就傳給ShowWindow函數SW_HIDE參數以隱藏工具條,否則,就傳SW_SHOW參數顯示工具條。接着要調用CMainFrame::RecalcLayout以重新調整主框架窗口的佈局。

  命令更新處理函數OnUpdateViewToolbar1會根據工具條是否可見使View->Toolbar1菜單項選中或不選中。

  編譯並運行Record,現在Record程序已變得很有趣了。至此,讀者已經掌握了工具條的一些實用編程技術。

 

4.3   狀態欄的設計與實現

  狀態欄實際上是個窗口,一般分爲幾個窗格,每個窗格顯示不同的信息。AppWizard會爲應用程序自動創建一個狀態欄,該狀態欄包括幾個窗格,分別用來顯示狀態欄提示和CAPS LOCK、NUM LOCK 、SCROLL LOCK鍵的狀態。在MFC中,狀態欄的功能由CStatusBar類實現。

  創建一個狀態欄需要以下幾個步驟:

構建一個CStatusBar對象。

調用CStatusBar::Create創建狀態欄窗口。

調用CStatusBar::SetIndicators函數分配窗格,並將狀態欄的每一個窗格與一個字符串ID相聯繫。

  相應的代碼讀者可以在Record工程的CMainFrame::OnCreate成員函數中找到。如清單4.6所示。

清單4.6 創建狀態欄

if (!m_wndStatusBar.Create(this) ||

!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))

{

TRACE0("Failed to create status bar/n");

return -1; // fail to create

}

  SetIndicators函數的第一個參數indicators是一個ID數組,在CMainFrame類所在的CPP文件的開頭部分可以找到該數組,如清單4.7所示。

清單4.7 ID數組

static UINT indicators[] =

{

ID_SEPARATOR, // status line indicator

ID_INDICATOR_CAPS,

ID_INDICATOR_NUM,

ID_INDICATOR_SCRL,

};

  indicator數組提供了狀態欄窗格的分配信息,它的第一項一般爲ID_SEPARATOR,該ID對應的窗格用來顯示命令提示信息,後三項都是字符串ID,讀者可以在String Table字符串資源中找到這三個字符串分別是CAP、NUM和SCRL。它們對應的三個窗格用來顯示鍵盤的狀態。

  現在讓我們來給狀態欄再加一個時間窗格,它將用來顯示系統時間。顯示的格式是hh:mm:ss,即時:分:秒。

  首先在indicators數組的ID_SEPARATOR項之後插入一個名爲ID_INDICATOR_CLOCK的ID。然後找到並雙擊名爲String Table的字符串資源,打開字符串資源編輯窗口。接着在編輯窗口內按Insert鍵以插入一個新的字符串,請指定字符串的ID爲ID_INDICATOR_CLOCK,內容爲00:00:00。狀態欄將根據字符串的長度來確定相應窗格的缺省寬度,所以指定爲00:00:00就爲時間的顯示預留了空間。

提示:上述方法不能動態改變窗格寬度,並且有時是不精確的,當系統字體改變時,這種做法可能會導致一些誤差。考慮到該方法簡單直觀,且一般情況下問題不大,故本文用它來舉例。如果讀者對動態、精確地指定窗格感興趣,請參看Visual C++ 5.0隨光盤提供的一個名爲NPP的MFC例子(在samples/mfc/general/npp目錄下)。

  時間窗格顯示的時間必須每隔一秒鐘更新一次。更新時間窗格的正文可調用CStatusBar:: SetPaneText函數,要定時更新,則應利用WM_TIMER消息。在Windows中用戶可以安裝一個或多個計時器,計時器每隔一定的時間間隔就會自動發出一個WM_TIMER消息,而這個時間間隔可由用戶指定。MFC的Window類提供了WM_TIMER消息處理函數OnTimer,我們應在該函數內進行更新時間窗格的工作。

  請讀者利用ClassWizard給CMainFrame類加入WM_TIMER的消息處理函數OnTimer和WM_CLOSE消息的處理函數OnClose,具體方法是在Class name欄中選擇CMainFrame,在Object IDs欄中選擇CMainFrame,在Messages欄中找到WM_TIMER和WM_CLOSE項,分別雙擊之然後按OK按鈕退出ClassWizard。CMainFrame::OnClose函數是在關閉主框架窗口是被調用的,程序可以在該函數中做一些清除工作。

接下來請按清單4.8修改程序。

清單4.8 CMainFrame類的部分代碼

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

SetTimer(1,1000,NULL);

return 0;

}

void CMainFrame::OnTimer(UINT nIDEvent)

{

// TODO: Add your message handler code here and/or call default

 

CTime time;

time=CTime::GetCurrentTime();

CString s=time.Format("%H:%M:%S");

m_wndStatusBar.SetPaneText(m_wndStatusBar.CommandToIndex(ID_INDICATOR_CLOCK),s);

CFrameWnd::OnTimer(nIDEvent);

}

void CMainFrame::OnClose()

{

// TODO: Add your message handler code here and/or call default

 

KillTimer(1);

CFrameWnd::OnClose();

}

  在CMainFrame::OnCreate函數內調用了CWnd::SetTimer以安裝一個計時器,SetTimer的第一個參數指定計時器ID爲1,第二個參數則規定了計時器的時間間隔爲1000毫秒即1秒。這樣,每隔1秒OnTimer函數就會被調用一次。

  在OnTimer函數中,首先構建了一個CTime對象,接着調用CTime的靜態成員函數GetCurrentTime以獲得當前的系統時間,然後利用CTime::Format函數返回一個按時:分:秒的格式表示的字符串,最後調用CStatusBar::SetPaneText來更新時間窗格顯示的正文。SetPaneText的第一個參數是窗格的索引,對於某一個窗格ID,可調用CStatusBar::CommandToIndex來獲得索引。

  在撤銷主框架窗口時應關閉計時器,因此在CMainFrame::OnClose函數內調用了KillTimer函數。

  現在讓我們來看一下CMainFrame的消息映射,在CMainFrame類所在CPP文件的開始部分可以找到該類的消息映射,如清單4.9所示。

清單4.9

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

//{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE()

ON_COMMAND(ID_RECORD_STOP, OnRecordStop)

ON_COMMAND(ID_RECORD_START, OnRecordStart)

ON_UPDATE_COMMAND_UI(ID_RECORD_START, OnUpdateRecordStart)

ON_UPDATE_COMMAND_UI(ID_RECORD_STOP, OnUpdateRecordStop)

ON_COMMAND(ID_HIGH_QUALITY, OnHighQuality)

ON_COMMAND(ID_LOW_QUALITY, OnLowQuality)

ON_UPDATE_COMMAND_UI(ID_HIGH_QUALITY, OnUpdateHighQuality)

ON_UPDATE_COMMAND_UI(ID_LOW_QUALITY, OnUpdateLowQuality)

ON_COMMAND(ID_VIEW_TOOLBAR1, OnViewToolbar1)

ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR1, OnUpdateViewToolbar1)

ON_WM_TIMER()

ON_WM_CLOSE()

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

  讀者可以看到,在消息映射表中,ClassWizard爲消息處理函數和命令處理函數自動加入了消息映射。自動加入的部分呈灰色顯示,位於註釋行//{{AFX_MSG_MAP和//}}AFX_MSG_MAP 之間。命令處理函數由ON_COMMAND宏來映射,命令更新處理函數由ON_UPDATE_COMMAND_UI,而WM_消息的處理函數由ON_WM_消息宏來映射。

提示:今後只要看到//{{AFX_...的註釋對,則說明它們之間的部分是ClassWizard自動加入的,這部分呈灰色顯示。請不要隨便修改它們,更不能把手工加入的部分放在//{{AFX_...註釋對內,否則有可能導致ClassWizard出錯。

 

  編譯並運行Record ,可以看到狀態欄的新變化,最終的界面如圖4.8所示。

T4_8.tif (116548 bytes)

圖4.8 最終的Record程序

小 結

 

本章主要向讀者介紹了工具條和狀態欄的一些實用技術。要點如下:

在MFC中,創建一個窗口一般分兩步:1.構建一個窗口對象。構建的方法是定義一個對象或用new操作符動態創建之。2.調用窗口類的Create成員函數。該函數把實際的窗口作出來,並將其HWND保存在窗口的公共數據成員m_hWnd中。

創建工具條和狀態欄的工作是在CMainFrame::OnCreate函數中完成的,OnCreate函數是在創建窗口時被調用的,這時窗口的創建已部分完成,窗口對象的HWND句柄也已有效,但窗口還是不可見的。因此一般在OnCreate函數中作一些諸如創建子窗口的初始化工作。

afx_msg前綴保證了正確版本的消息處理函數被調用。

工具條有兩個要素:工具條資源和工具條類CToolBar。若用戶只需要一個工具條,可利用AppWizard自動生成,然後再修改之。若需要多個工具條,則必須手工創建。

如果不爲命令定義命令處理函數或命令更新處理函數,則框架將自動使該命令對應的用戶接口對象(主要指菜單項和按鈕)禁止。利用ClassWizard可以十分方便的加入命令處理函數和命令更新處理函數。

在菜單下拉之前,或在工具條按鈕處在空閒循環期間,MFC會發一個更新命令,這將導致命令更新處理函數的調用。命令更新處理函數利用CCmdUI類來更新用戶接口對象。調用CCmdUI::Enable可使用戶接口對象允許或禁止,調用CCmdUI::SetCheck可使用戶接口對象選中或不選中。

調用CWnd::ShowWindow可以隱藏/顯示一個窗口。

要在狀態欄中插入新的窗格,需要在indicator數組中插入新的字符串ID。而狀態欄將根據這個字符串的長度來確定新窗格的缺省寬度。

調用CStatusBar:: SetPaneText可更新狀態欄窗格顯示的正文。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章