第3講 菜單、工具欄和狀態欄之間的交互
摘要
本講先來用資源編輯器對菜單和工具欄進行可視化設計,然後討論命令消息的COMMAND和UPDATE_COMMAND_UI消息映射,並說明了工具按鈕和菜單命令的聯動方法,最後討論工具欄的顯示和隱藏、快捷菜單的實現,以及在狀態欄上如何顯示指定文本的方法。
菜單的可視化設計及其命令映射
在上一講中,我們主要討論了文檔數據的讀取和顯示,在這裡我們先來打開上一講中的單文檔應用程序項目Viewer,然後對其菜單和工具欄進行設計。需要說明的是,Visual C++ .NET把Windows各種應用程序所需要的圖形元素,例如菜單、工具欄、對話框、圖標、光標等,作為可以裝入應用程序的資源來存放。例如,Visual C++.NET將Viewer應用程序的資源都存放在Viewer.rc文件中,這種資源同源代碼相分離的機制,能大大方便用戶的操作,而且每一個資源元素都用相應的資源ID號來標識。
1. 菜單的可視化設計
在菜單設計之前,我們先了解一下菜單及其設計規范。
菜單可以有多級結構,即一個菜單項可以有多個子菜單,而一個子菜單又可以包含多個下一級的子菜單,依此類推。但在菜單實際設計時,菜單的級數一般以2~3級為宜,而且設計時還要注意一些菜單原則。例如,若單擊某菜單項會彈出一對話框,那麼在該菜單項文本後加上"…";若菜單項需要助記符(帶下劃線的字符),則用括號將其括起來,對於頂層菜單項來說,當按住"Alt"鍵不放,再按助記符所對應的字符鍵時,對應的頂層菜單就會被打開,若子菜單項還有助記符,則只要按對應的字符鍵,則可執行該菜單命令;定義助記符時,只要在字符前面加上"&"符號即可;若某項菜單需要快捷鍵的支持,則一般將其列在相應菜單項文本之後。
下面為Viewer項目添加一個"格式"菜單,其下有兩個菜單項。一個是"設置字體"菜單,另一個是"文本顏色"菜單,分別用來改變文本顯示的字體和顏色。具體步驟如下:
(1) 將解決方案資源管理器切換到"資源視圖",若沒有該標簽,則打開"視圖"菜單,選擇"資源視圖"菜單命令即可。
(2) 展開資源所有節點,雙擊Menu下的IDR_MAINFRAME,打開該程序的菜單資源。需要說明的是,凡是標識為IDR_MAINFRAME的資源均是程序框架加裝的默認資源。
(3) 如圖1所示,單擊頂層菜單最右邊的"請在此處輸入",直接按Insert鍵或右擊"請在此處輸入",在彈出的菜單中單擊"新插入"。再單擊"請在此處輸入",該位置就會變成一個可編輯的文本框,出現了插入符。鍵入菜單文本內容"格式(&M)",然後按Enter鍵。
圖1 頂層菜單資源
(4) 單擊"格式(&M)"菜單項下方的"請在此處輸入",按Insert鍵,鍵入菜單文本內容"設置字體(&F)",然後按Enter鍵。
(5) 單擊"設置字體(&F)",在右下角的屬性窗口中就會列出其所有的屬性,如圖2所示。需要說明的是,在屬性窗口中,我們可以重新編輯菜單的文本內容和資源標識ID。Caption(標題)屬性是用來標識菜單項顯示文本,如果使用助記符,則字母的前面須有一個&符號;當Popup(彈出)屬性為True時表示該菜單項是一個彈出式菜單,即該菜單下還有多個子菜單,此時屬性ID、Separator和Prompt項無效;因此,若添加的是一個可以映射的菜單命令,則Popup屬性一定要設為False。當Separator(分隔符)屬性為True時表示菜單項是一個分隔符或是一條水平線;而Prompt(提示)屬性用來指明鼠標指針移至該菜單項時在狀態欄上顯示的提示信息。
圖2 菜單屬性
(6) 將默認的菜單項"設置字體(&F)"標識ID_130改為ID_FOMAT_TXTFONT。更改時直接在屬性窗口中的ID欄右側的框中進行編輯,修改後按Enter鍵。
(7) 重復上述步驟,在"格式"菜單下再添加一個菜單項"文本顏色(&C)",ID為ID_FORMAT_TXTCOLOR,結果如圖3所示。
圖3 添加的菜單
(8) 單擊"格式"菜單不松開,然後將其拖放到"視圖"和"幫助"之間。
2. 菜單的命令映射
此時運行程序,則"格式"菜單下的命令都是"灰顯"(即顯示的顏色是灰色的)的,我們無法選擇相應的菜單命令,這是因為我們還沒有對菜單的命令消息進行映射。下面就來進行映射,由於我們添加的這些菜單命令是想更改變文本內容顯示的字體,因此我們將菜單命令的映射添加到視圖類CViewerView中,如下面的過程:
(1) 將解決方案資源管理器切換到"類視圖",展開節點,選定"CViewerView",在其屬性窗口中,單擊"事件"按鈕,結果如圖4所示。
圖4 事件映射
(2) 找到前面添加的菜單項ID_FORMAT_TXTFONT,單擊該ID前面的"+",展開後出現可以映射的消息,由於菜單消息是命令消息,因為我們在COMMAND消息框的右側,單擊後選擇"<添加>OnFormatTxtfont",如圖4所示。這樣相應的映射就被添加到CViewerView類中,此時文檔窗口中自動定位到該函數的實現代碼處。
圖5 映射COMMAND消息
(3) 重復上一步為菜單項ID_FORMAT_TXTCOLOR添加COMMAND消息映射。
注意:同一命令消息的響應是根據對象的級別來決定的,對於單文檔應用程序來說,各對象的級別從高到低依次為視圖類(文檔窗口)、文檔類、主框架窗口類、應用程序類。
3. 完善"格式"菜單代碼
(1) 為CViewerView類添加兩個成員變量(添加成員變量的方法上一講已討論過),一個是LOGFONT類型的m_lfTextFont,另一個是COLORREF類型的m_crTxtColor。LOGFONT是邏輯字體類型,所謂"邏輯字體",它是應用程序對於理想字體的一種描述方式。在使用邏輯字體繪制文字時,系統會采用一種特定的算法把邏輯字體映射為最匹配的物理字體(實際安裝在操作系統中的字體)。而COLORREF是專門用來定義RGB顏色的數據類型,RGB顏色是通過紅(R)、綠(G)、藍(B)三種基色分量的不同值混合而成的。
(2) 在構造函數CViewerView::CViewerView()中添加上述兩個成員變量的初始化代碼,如圖5所示。
圖5 在CViewerView類構造函數中添加的代碼
(3) 在CViewerView::OnFormatTxtfont()函數中添加如圖6所示的代碼。
圖6 OnFormatTxtfont()函數代碼
CFontDialog類為我們提供了字體及其文本顏色選擇的通用對話框,在構造對象中指定m_lfTextFont指針,其目的是用來設置對話框顯示的邏輯字體,這樣當下一次顯示字體對話框時,就會顯示當前的字體特性。
(4) 在CViewerView::OnFormatTxtcolor()函數中添加如圖7所示的代碼。
圖7 OnFormatTxtcolor()函數代碼
CColorDialog類封裝了通用顏色對話框的全部操作。在定義對話框對象時,可以指定默認選定的顏色值,若不指定,則默認顏色值為RGB(0,0,0)(黑色)。
(5) 修改CViewerView::OnDraw()函數代碼,如圖8所示的加框部分。
圖8 修改後的OnDraw()函數代碼
(6) 運行程序,打開當前目錄中的ReadMe.txt文檔,打開"格式"菜單,選中相應的菜單命令,改變其字體和顏色。圖9是其中的一個結果。
圖9 運行後的程序結果
工具欄設計及與菜單命令的聯動
工具欄上通常有一系列的工具按鈕,所有的按鈕圖像都具有相同的尺寸,一般是15像素高,16像素寬,借助它們可以提高用戶的工作效率,並且將常用的菜單命令也放在工具欄上,它們實際是命令不同的用戶方式。
1. 添加並設計工具欄
(1) 將解決方案資源管理器窗口切換到"資源視圖",展開後右擊Toolbar,在彈出的快捷菜單中單擊"插入Toolbar"。這樣,一個工具欄資源就添加到項目中,默認的標識為IDR_TOOLBAR1。
(2) 添加並設計2個工具按鈕,結果如圖10所示。
圖10 工具按鈕的設計
由於其編輯操作與Windows的畫圖相類似,故這裡僅列出操作的一些技巧:
① 單擊空白按鈕後就可以編輯其圖像,同時系統在隨後的位置自動添加一個空白按鈕。
② 用鼠標可以將一個按鈕拖放到工具欄上的其他位置上。若拖動時按下Ctrl鍵,則復制一個工具按鈕。若將工具按鈕拖出工具欄,則該工具按鈕被刪除。
③ 按Delete鍵可以將當前工具按鈕的圖像用背景色填充。
④ 在工具按鈕之間添加間隔時,可按不同情況來操作。若工具按扭前沒有任何間隔,拖動該工具按鈕向右直到它覆蓋相鄰工具按鈕的一半以上後,釋放鼠標鍵,則此工具按鈕前出現間隔。若工具按鈕前面有間隔而後面沒有間隔,拖動該工具按鈕向左直到它的左邊界接觸到它前面的工具按鈕為止,釋放鼠標鍵,則此工具按鈕後面將出現間隔。
⑤ 若工具按鈕前後均有間隔,拖動該工具按鈕向右直到它接觸相鄰工具按鈕,則此工具按鈕前的間隔保留,工具按鈕後的間隔消失。反之,若拖動該工具按鈕向左直到它接觸相鄰的前一個工具按鈕,則此工具按鈕前面的間隔消失,後面的間隔仍保留。
⑥ 刪除工具按鈕間隔時,只要將間隔一端的工具按鈕拖向間隔另一端的工具按鈕,直到與另一個按鈕重疊一半以上即可。
(3) 單擊第一個工具按鈕,在工具按鈕的屬性窗口中,將其ID號選擇為ID_FORMAT_TXTFONT,這是將工具按鈕與菜單命令聯動的關鍵。將其Prompt屬性內容改成"改變顯示的字體\n字體"。Prompt屬性是用來指定工具按鈕的提示文本。例如若為"改變顯示的字體\n字體"時,則表示當鼠標移至該工具按鈕時,在狀態欄中就會顯示"改變顯示的字體",稍等片刻後還會彈出一個小的提示窗口,顯示出"字體"字樣。注意:提示窗口顯示的內容是Prompt屬性字符串中"\n"後的內容。
(4) 將第二個工具按鈕的ID號選擇為ID_FORMAT_TXTCOLOR,Prompt設為"改變文本的顯示顏色\n顏色"。
2. 工具欄代碼的實現
(1) 在CMainFrame類中添加一個成員變量m_wndFormatBar,變量類型為CToolBar。CToolBar類封裝了工具欄的操作。
(2) 在CMainFrame::OnCreate()函數中添加工具欄的創建代碼,如圖11所示的加框部分。
圖11 添加的工具欄的創建代碼
程序說明:
① 主框架類CMainFrame用來負責窗口的菜單欄、工具欄和狀態欄的創建和更新工作。因此我們將工具欄的創建代碼添加在CMainFrame的OnCreate()函數中。
② CreateEx()是CToolBar類的成員函數,用來創建一個工具欄對象。
③ if語句的LoadToolBar()函數是用來裝載工具欄資源。若CreateEx()或LoadToolBar()的返回值為0,即調用不成功,則顯示診斷信息"未能創建工具欄"。TRACE0是一個用於程序調試的跟蹤宏。OnCreate()函數返回-1時,主框架窗口被清除。
④ 應用程序中的工具欄一般具有停靠或浮動特性,m_wndFormatBar.EnableDocking()使得m_wndFormatBar對象可以停靠,CBRS_ALIGN_ANY表示可以停靠在窗口的任一邊。 EnableDocking(CBRS_ALIGN_ANY)是調用的是CFrameWnd類的成員函數,用來讓工具欄或其他控制條在主框架窗口可以進行停靠操作。DockControlBar()也是CFrameWnd類的成員函數,用來將指定的工具欄或其他控制條進行停靠。
⑤ AFX_IDW_TOOLBAR是系統內部的工具欄子窗口標識,並將AFX_IDW_TOOLBAR+1的值表示默認的狀態欄子窗口標識。如果在創建新的工具欄時沒有指定相應的子窗口標識,則會使用默認的AFX_IDW_TOOLBAR。這樣,當打開"視圖"菜單時,單擊"工具欄"菜單時,顯示或隱藏的工具欄不是原來的工具欄而是新添加的工具欄。因此,我們需要重新指定工具欄子窗口的標識,並使其值等於AFX_IDW_TOOLBAR + 10。
(3) 運行程序,可以看到新添加的工具欄,如圖12所示,左圖是工具欄開始的停靠情況,右圖是工具欄浮動的情形。
圖12 新工具欄的停靠和浮動
需要說明的是,上述工具按鈕是與菜單命令聯動,因此無需進行工具按鈕命令的消息映射,因為該命令已在菜單操作該命令已映射過。若是單獨一個工具按鈕,則需要對該工具按鈕進行命令消息的映射,否則按鈕是灰顯的。工具按鈕的命令消息映射方法與菜單命令相同。
工具欄的顯示和隱藏的快捷方式實現
在圖12中,關閉浮動的"格式"工具欄後,若再顯示該工具欄則無法進行,為此我們需要添加相關的控制代碼。這裡我們先來介紹菜單命令的控制方式,然後再說明其他的快捷方式。
1. 菜單命令方式
所謂菜單命令方式,即使用菜單命令來顯示和隱藏指定工具欄。需要解決的問題有兩個,一是顯示和隱藏指定工具欄的函數是什麼?二是如何實現菜單項前面的顯示狀態的更新。所謂顯示狀態,即當工具欄顯示時,該菜單項前面有一個"a",否則什麼都沒有。
對於第一個問題,我們可以使用CFrameWnd類的成員函數ShowControlBar()來進行,它的原型如下:
void ShowControlBar( CControlBar* pBar, BOOL bShow, BOOL bDelay );
其中,pBar用來指定要操作的控制條指針,bShow為TRUE時表示顯示,否則表示隱藏,bDelay表示是否延遲顯示或隱藏,當為FALSE時表示立即顯示或隱藏。
對於第二個問題,可以通過映射宏ON_UPDATE_COMMAND_UI來實現菜單項和工具欄按鈕狀態的改變。下面就來實現。
(1) 在"視圖"菜單中添加一個菜單項"格式工具欄(&F)",ID為ID_VIEW_FORMAT。如圖13所示。
圖13 在"視圖"中添加的菜單項
(2) 在CMainFrame類中添加一個成員變量m_bViewFormat,變量類型為BOOL。該變量用來決定新添加的"格式"工具欄是否顯示。
(3) 在CMainFrame類的構造函數處,將m_bViewFormat的初值由原來的FALSE改為TRUE。
(4) 在CMainFrame類中分別添加菜單項ID_VIEW_FORMAT的COMMAND和UPDATE_COMMAND_UI事件映射,並在映射函數添加如圖14所示的代碼。
圖14 添加的代碼
程序說明:
① CCmdUI類是專門用於交互對象的更新操作,其成員函數Enable()用來使交互對象有效(參數為TRUE)或無效(參數為FALSE),若不指定參數,使用默認的參數值TRUE。
② CCmdUI::SetCheck()用來設置交互對象狀態是"選中"(參數為TRUE)還是"未選中"(參數為FALSE)。當"選中"時,SetCheck()在菜單項文本前面加上"a"。
(5) 運行程序。
2. 快捷鍵方式
快捷鍵用於那些反復使用的菜單命令或工具按鈕命令,當用戶執行命令時只要接相應的快捷鍵即可。下面來添加並使用快捷鍵。
(1) 將解決方案資源管理器窗口切換到"資源視圖",展開Accelerator,雙擊IDR_MAINFRAME,出現如圖15所示的快捷鍵資源內容。
圖15 快捷鍵資源
需要說明的是,在Visual C ++ .NET中,每一個快捷鍵除了ID外,還有三個屬性:修飾符、鍵和類型。"修飾符"屬性用來設置的快捷鍵是與Alt、Ctrl和Shift的哪一個或幾個控制鍵組合。"鍵"屬性用來設置使用的鍵。"類型"屬性是用來確定鍵是解釋為虛擬鍵(VIRTKEY)還是解釋為ASCII/ANSI。
(2) 單擊最下端的空白方框,出現默認的快捷鍵資源,如圖16所示。
圖16 添加的默認快捷鍵資源
(3) 單擊ID_ACCELERATOR32776後,該ID字段變成了一個組合框。在這裡,我們既可以自己定義一個資源標識,也可以單擊右側的下拉按鈕,從中選擇一個已有的資源標識。一旦指定了標識,快捷鍵就與該標識關聯起來,這樣當按快捷鍵時就會執行與標識相對應的命令。我們選擇前面的菜單標識ID_VIEW_FORMAT。
(4) 單擊Ctrl,從中選擇可以使用的控制鍵,單擊"鍵"字段可以選擇相應的虛擬鍵,或直接輸入字符,表示相應的字符鍵。按圖17來設置。
圖17 設置的快捷鍵
(5) 程序運行後,先按住Ctrl,然後再按1鍵,就可以顯示或隱藏格式工具欄了。
需要說明的是,為了使用戶能看到各菜單項所對應的快捷鍵,我們應該在各菜單項的文本後加上快捷鍵的內容。例如,在將菜單項ID_VIEW_FORMAT的文本內容改成"格式工具欄(&F)\t Ctrl+1",其中的"Ctrl+1"表示該菜單項的快捷鍵,"\t"用來將其後面的內容在下一個水平制表位置中顯示。
3. 快捷菜單方式
工具欄的顯示和隱藏的快捷方式最常用的是使用快捷菜單。所謂快捷菜單,它是一種浮動的彈出式菜單,當用戶右擊鼠標時,就會相應地彈出一個浮動菜單,其中提供了幾個與當前選擇內容相關的菜單命令。
(1) 在CMainFrame類的屬性窗口中,單擊"消息"按鈕,在列表框中找到並添加WM_CONTEXTMENU消息的映射。如圖18所示。
圖18 添加WM_CONTEXTMENU消息映射
(2) 在映射函數OnContextMenu()中添加代碼,如圖19所示的加框部分。
圖19 在OnContextMenu()中添加的代碼
需要說明的是:
① 在MFC中,AFX_IDW_DOCKBAR_TOP和AFX_IDW_DOCKBAR_FLOAT之間的值用來標識工具欄的停靠和浮動的窗口,而AFX_IDW_PANE_FIRST是標識第一個視圖窗口,由於單文檔的視圖只有一個,因此它的標識就是該值。
② GetMenu是用來獲取指定菜單下的彈出子菜單,參數的值表示子菜單在主菜單中的位置序號,0時表示第1個子菜單,1時表示第2個子菜單,以此類推。
③ TrackPopupMenu()用來彈出一個快捷菜單,第一個參數用來表示菜單在屏幕顯示的位置以及鼠標按鈕標志,當為TPM_LEFTALIGN時表示菜單的左邊位置由第二個參數確定,TPM_RIGHTBUTTON表示用戶單擊鼠標右鍵時彈出菜單,最後一個參數表示彈出菜單的父窗口。this是當前對象指針,每個類對象均有這個指針。
(3) 運行程序。圖20是兩次不同位置右擊時彈出的快捷菜單。
圖20 快捷方式運行結果
在狀態欄上顯示文本
狀態欄是一個水平長條,位於應用程序主窗口的底部。它可以分割成幾個窗格,用來顯示多組信息。 在"MFC應用程序向導"創建的單文檔或多文檔應用程序中,MainFrm.cpp文件定義了一個靜態的indicator數組,這個數組中的元素與狀態欄的窗格一一對應。
默認時,indicator數組元素只有四個:ID_SEPARATOR、ID_INDICATOR_CAPS 、ID_INDICATOR_NUM和ID_INDICATOR_SCRL。其中ID_SEPARATOR用作消息行窗格,用來顯示菜單項或工具按鈕的提示信息,其余三個元素是用作狀態指示器窗格,分別用於 、 和 這三個鍵的狀態顯示。
下面的過程用來將字體名和文本顏色值分別顯示在狀態欄窗格上。
1. 添加狀態欄窗格
(1) 將解決方案資源管理器窗口切換到"資源視圖",展開後右擊Viewer.rc,在彈出的快捷菜單中單擊"資源符號"。在"資源符號"對話框中,單擊"新建"按鈕,添加一個新的ID號ID_STAT_TXTFONT,並取其默認的值101,如圖21所示。
圖21 添加新的資源符號
(2) 再添加一個新的資源符號ID_STAT_TXTCOLOR,取其默認的值(102)。
(3) 展開"資源視圖"中的"String Table"節點,雙擊"String Table",打開"字符串表"資源。單擊最下方的空白框,出現默認的字符串標識和值,單擊該字符串標識,在其右側出現相應的下拉按鈕,單擊該按鈕,從中選擇標識ID_STAT_TXTFONT,單擊右側的標題框,輸入"顯示字體",結果如圖22所示。
圖22 添加新的字符串
(4) 同樣的方法再為ID_STAT_TXTCOLOR添加新的字符串"當前文本顏色",注意字符串的長度確定了添加的狀態欄窗格的大小。
(5) 打開MainFrm.cpp文件,向indicators數組添加兩個元素,如圖23所示的加框部分。
圖23 添加狀態欄的窗格
(6) 運行程序,結果如圖24所示,其中顯示的文本是在前面設置的字符串,顯然不能滿足我們的要示。我們的目的是將當前文本顯示的字體和當前顏色值在這兩個窗格中顯示出來。
圖24 添加窗格後運行的結果
2. 更新狀態欄窗格
更新狀態欄的窗格是通過映射窗格ID的更新命令事件UPDATE_COMMAND_UI來實現的,但由於在類的屬性窗口中不能直接對窗格ID進行事件映射,因此我們需要另尋他法。除了手動添加外,我們還可以使用臨時菜單的辦法,如下面的過程。
(1) 打開菜單資源,在"格式"菜單中再添加兩個菜單項"1"和"2",分別將其ID號設置為ID_STAT_TXTFONT和ID_STAT_TXTCOLOR。
(2) 由於顯示的內容與CViewerView類的成員變量直接有關,因此我們在CViewerView類中分別添加菜單項ID_STAT_TXTFONT和ID_STAT_TXTCOLOR的UPDATE_COMMAND_UI事件映射。
(3) 在映射函數中添加如圖22所示的代碼。
圖22 添加的窗格映射代碼
(4) 打開"生成"菜單,單擊"重新生成解決方案"。
(5) 刪除剛才在"格式"菜單中添加的"1"和"2"菜單項。
(6) 運行程序,結果如圖23所示。
圖23 最後運行結果
本講中常用操作問題的解決方法
由於Visual C++ .NET的本身原因以及人為的操作不當,導致一些問題的出現,下面就來說明。
問題1:添加的菜單項、是工具按鈕的標識的名稱可以在屬性窗口中修改,但其值卻始終為0。
解決辦法:在屬性窗口中,將標識的名稱後面添上"=200"(輸入時不加引號),然後按Enter鍵,這時該標識的值就是200。當然,也可指定其他的值。注意,每一個標識符的值要各不相同。
問題2:在屬性窗口的"事件"映射頁面中,沒有找到要映射的已添加的命令標識,但卻發現有的標識不是以符號出現的,而是雙數字出現的。
雖然這個問題不是大問題,但是看起來有點別扭。解決的步驟如下:
(1) 打開解決方案資源管理器頁面,展開所有節點,右擊"資源文件"下的"Viewer.rc",從彈出的快捷菜單中選擇"打開方式"。
(2) 在彈出的對話框中選擇"源代碼(文本)編輯器",如圖24所示。
圖24 指定打開方式
(3) 單擊"打開"按鈕,出現資源的文本內容,如圖25所示的片斷。
圖24 菜單文本片斷
(4) 找到相應的命令標識,然後將數值改為原來的標識符即可。
問題3:添加的代碼也對,就是編譯時出現類似"無法打開xxx資源"的錯誤。
解決辦法:打開"生成"菜單,選擇"重新生成解決方案",再運行程序,一般這種問題都會解決。
問題4:在運行程序中出現"Viewer fatal error LNK1168: 無法打開 Debug/Viewer.exe 進行寫入"編譯錯誤。
解決辦法:關閉已執行的應用程序Viewer.exe,然後再編譯運行程序。
結束語
在本講中,我們重點討論了菜單命令和工具按鈕的添加和消息映射,工具欄顯示和隱藏的各種快捷方式,以及狀態欄的文本顯示等內容。在下一講中,我們將重點進行對話框的界面設計、模式和無模式對話框的創建以及DDV/DDX機制的使用等。