接下來我們看一下狀态欄的程式設計。
分為兩類:
第一類:提示行,左邊上的那一塊,主要用來菜單項的指令和工具欄的指令
第二類:窗格形式,顯示開關的狀态,狀态訓示器。例如鍵盤Caps LOCK這樣的開關的狀态
狀态欄也是在架構類裡邊定義的
CStatusBar m_wndStatusBar; //定義一個狀态欄的對象
然後呢, 在OnCreate的函數中建立一個狀态欄。
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()設計一個訓示器 ,在這裡 第一個參數用了一個訓示器的數組。第二個參數
那麼讓我們來看一下訓示器數組的定義。
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
如果我們想修改狀态欄窗格的數目,我們隻需要在indicators當中添加字元串資源的ID,或者減少字元串資源的ID。在字元串表中定義。
我們可以在ID_SEPAPATOR的下一行添加窗格。
首先在字元串資源中定義一個IDS_TIMER 時鐘
然後添加。
這就是訓示器的數組,我們可以看到的一些ID号。ID_SEPARATOR這個ID号就表示了狀态欄最長的部分,我們叫做提示行。後便的三個ID号,分别表示了鍵盤上的:NumLock鍵,CapsLock鍵 ScrollLock鍵
接下來我們完成一個功能,就是在我們時鐘的窗格顯示一個系統的時間。
這就需要我們先去擷取系統的時間。用到CTime類。
有這樣一個方法static CTime PASCAL GetCurrentTime( );
能夠傳回一個CTime對象,表示目前的時間。
然後我們用CTime中的
CString Format( LPCTSTR pFormat ) const;
CString Format( UINT nFormatID ) const;
這個方法将時間做一個格式化,傳回這個時間的小時,分鐘和秒
我們擷取了表示時間的字元串對象,如何将它設定為我們時鐘這個窗格顯示的内容
這個時候我們需要用到狀态欄所對應的C++中的類,CStatusBar中的一個函數SetPaneText()
BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE );
第一個參數,面闆的一個索引,就是在訓示器中的索引,第二個參數,新的文本,第三個參數,如果為真在文本輸入之後面闆是無效的,當下一次WM_PANE消息發送的時候面闆會發生重繪。
例:
CTime t=CTime::GetCurrentTime();
CString str;
str=t.Format("%H:%M:%S");
m_wndStatusBar.SetPaneText(1,str,true);
如果我們不知道這個字元串的索引。
我們就可以用CStatucBar類的一個方法CommandToIndex
int CommandToIndex( UINT nIDFind ) const;
作用是給定一個字元串資源的ID号來擷取索引。
例:
int index=0;//定義一個變量儲存索引
index=m_wndStatusBar.CommandToIndex(IDS_TIMER); //得到字元串的索引。
下面我們來了解如何改變窗格的寬度。
這就用到了CStatusBar這個類中的方法:GetPaneInfo
void GetPaneInfo( int nIndex, UINT& nID, UINT& nStyle, int& cxWidth ) const;
第一個參數,要修改的訓示器面闆的索引。第二個參數,給面闆可以重新配置設定一個ID号。第三個參數,它的一個類型,有參數清單。第四個參數,面闆的寬度。
要得到字元串顯示時候的一個寬度。用GetTextExtent(str0;)
例:
CTime t=CTime::GetCurrentTime();
CString str;
str=t.Format("%H:%M:%S");
CClientDC dc(this);
CSize sz=dc.GetTextExtent(str);//擷取字元串顯示的寬度
int index=0;
index=m_wndStatusBar.CommandToIndex(IDS_TIMER);//給定一個字元串資源的ID号來擷取索引。
m_wndStatusBar.SetPaneInfo(index,IDS_TIMER,SBPS_NORMAL,sz.cx);//改變窗格的寬度。
m_wndStatusBar.SetPaneText(index,str,true);//在窗格中顯示str的内容
此時的時間是一個固定的時間,而我們想要的時間是一個變化的。我們就需要一個定時器
SetTimer(1,1000,NULL); 每一秒鐘發送一個消息。
然後我們在OnTimer中執行
CTime t=CTime::GetCurrentTime();
CString str;
str=t.Format("%H:%M:%S");
CClientDC dc(this);
CSize sz=dc.GetTextExtent(str);//擷取字元串顯示的寬度
int index=0;
index=m_wndStatusBar.CommandToIndex(IDS_TIMER);//給定一個字元串資源的ID号來擷取索引。
m_wndStatusBar.SetPaneInfo(index,IDS_TIMER,SBPS_NORMAL,sz.cx);//改變窗格的寬度。
m_wndStatusBar.SetPaneText(index,str,true);//在窗格中顯示str的内容
進度欄:
MFC中有一個相關類:CProgressCtrl
首先我們看一下它的構造函數。
CProgressCtrl( );
在我們構造了CProgressCtrl之後,調用Create方法去建立一個進度欄的控件。
我們看一下這個Create方法
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
第一個參數 訓示進度欄的一個類型,一個進度欄也是從CWnd間接派生而來,是以他有視窗的類型,也就自己的類型。第二個參數是指進度欄的大小,通過rect結構體,來訓示進度欄的大小,第三個參數,設定進度欄的父視窗。最後一個參數,指定進度欄的ID号
接下來我們就建立一個進度欄
首先我們在架構類構造一個架構類的對象:CProgressCtrl m_progress;
然後在OnCreate這個函數當中。在視窗建立完成之後,建立:
m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,CRect(100,100,200,120),this,123);//水準進度欄
m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_VERTICAL,CRect(100,100,120,200),this,123);//垂直進度欄
我們可以讓進度欄的位置設定為我們指定的位置,我們可以用CProgressCtrl類中的一個方法:SetPos
int SetPos( int nPos );
設定進度欄的目前位置。
例:
m_progress.SetPos(50); //進度欄進度到一半的狀态。即50%
接下來,我們要把進度欄添加到狀态欄的窗格處:
這就需要知道窗格處的坐标和矩形大小。
在CStatusBar中有一個方法
void GetItemRect( int nIndex, LPRECT lpRect ) const;
得到指定索引的窗格的區域,第一個參數,訓示器的索引,第二個參數,接收指定索引的訓示器矩形區域的坐标
狀态欄的初始化沒有完全完成,是以直接用GetItemRect這個函數,得到的是一個無效的矩形區域。是以我們要定義個消息,在OnCreate這個函數調用完成之後,在消息響應函數中完成矩形區域的擷取。
我們在頭檔案中定義一個消息:
在Windows當中消息都是用整數來表示的,我們如何避免跟系統的消息發生沖突呢?Windows為我們提供了一個宏
WM_USER,在這個WM_USER數值一下的有我們的系統保留着,我們定義消息時,就定義一個比WM_USER大的就可以了,于是我們定義消息是,讓WM_USER+一個數,加多少自己定義,這裡我們加1就可以。
例:#define UM_PROPRESS WM_USER+1 //系統消息一般是WM開頭,而使用者消息我們用UM表示
有了消息之後,我們就要做消息響應函數的原型的聲明。
同樣是在頭檔案中:
在消息響應函數聲明的區域,做消息響應函數的原型的聲明。
protected:
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnTest();
afx_msg void OnViewNewtoolbar();
afx_msg void OnUpdateViewNewtoolbar(CCmdUI* pCmdUI);
//}}AFX_MSG
afx_msg void OnProgress(); //這裡就是我們自己添加的消息聲明,如果發送消息是我們需要附帶發送一些數
//據,那麼聲明中我們需要附帶兩個參數。
DECLARE_MESSAGE_MAP()
接下來消息映射,我們在源檔案中定義消息映射:
對于消息來說我們用ON_MESSAGE這個宏。對于指令消息來說我們用ON_COMMAND這個宏,将我們的消息跟消息響應函數關聯起來。
例:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_TIMER()
ON_COMMAND(IDM_TEST, OnTest)
ON_COMMAND(IBM_VIEW_NEWTOOLBAR, OnViewNewtoolbar)
ON_UPDATE_COMMAND_UI(IBM_VIEW_NEWTOOLBAR, OnUpdateViewNewtoolbar)
//}}AFX_MSG_MAP
ON_MESSAGE(UM_PROGRESS,OnProgress) //這就是我們自己定義的消息映射
END_MESSAGE_MAP()
最後就是我們的消息響應函數實作部分。
void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,rect,&m_wndStatusBar,123);
// m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_VERTICAL,CRect(100,100,120,200),this,124);
m_progress.SetPos(50);
}
我們在OnCreate函數中發送一個消息。
SendMessage(UM_PROGRESS); //因為SendMessage它發送消息,直接把消息發送給消息響應函數,當消息處理完成以後再傳回。是以在這裡我們發送消息的時候不能用SendMessage
我們這裡用到PostMessage(UM_PROGRESS);這個函數
它将消息放到消息隊列當中,然後根據消息擺放的順序,通過GetMessage一條一條的消息取出,他把消息放到消息隊列就立即傳回了。OnCreate函數就執行完成了。執行完成後,MFC底層代碼GetMessage取出代碼後,消息響應函數才執行了。
當我們改變了視窗的大小的時候,我們的進度欄就不在我們的窗格裡了,這是因為我們取到的是窗格的坐标,一旦窗體大小改變了,窗格的坐标也改變了,我們想使進度欄一直在窗格裡應該怎麼實作呢?
實作方法是,當我們窗格發生變化的時候,重新擷取窗格的矩形區域。
因為視窗重繪的時候會發送一個WM_PAINT的消息,我們隻需要在響應這個消息的函數中,重新擷取窗格的矩形的區域,把進度欄移動到這個區域就可以了。
在OnPaint這個函數中我們需要判斷一下,狀态欄是否建立了。沒有建立,那麼我們就建立一個,建立了,我們就直接把狀态欄移動到窗格的矩形區域就可以了。
例:
CRect rect;
m_wndStatusBar.GetItemRect(2,&rect);
if(!m_progress.m_hWnd) //通過判斷對象句柄是否為NULL來判斷進度欄是否建立
{
m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,rect,&m_wndStatusBar,123);
// m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_VERTICAL,CRect(100,100,120,200),this,124);
}
else
{
m_progress.MoveWindow(&rect); //移動進度欄到矩形區域
}
m_progress.SetPos(50); //進度欄進度到50%的位置。
接下來我們要做的是讓進度欄進度動起來。
那麼我們就要在CProgressCtrl的成員函數中調用一個
int StepIt( );
作用就是按一定的步長前進。
我們通過
int SetStep( int nStep );
這個函數來設定步長。
我們也可以設定進度欄的範圍
void SetRange( short nLower, short nUpper );
void SetRange32( int nLower, int nUpper );
預設範圍最小是0最大100。根據我們要完成的功能,去設定範圍。例如你要播放影片,那麼就可以根據影片播放的時間來設定進度欄的範圍。
我們在On_Timer中完成一個功能,每一秒鐘走一個步長。
就可以直接用m_progress.StepIt(); 就可以完成了。當然,我們步長用的是預設的值10;
下面我們要完成一個小程式,就是當我們滑鼠移動到視窗上的時候。我們的狀态欄的訓示行把我們滑鼠的目前位置顯示出來。
首先,我們要捕獲滑鼠移動的這個消息。
因為我們的View類在架構類之上,是以,滑鼠移動是在View的視窗上進行的,是以我們在View類中捕獲滑鼠移動,然後呢,我們要擷取狀态欄,狀态欄在架構類中定義的,首先我們需要擷取架構類的指針,即通過
GetParent()這個函數獲得
例:
void CStyleView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CString str; //定義一個CString對象
str.Format("x=%d,y=%d",point.x,point.y); //讓str按一定的形式接受兩個點的坐标
((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);//通過擷取架構類的指針,進而擷取
//狀态欄,然後通過函數SetWindowText()這個函數顯示到訓示行
//需要注意的是,這裡用到了(CMainFrame*)這個強制轉換,需要
//包含"MainFrm.h"這個頭檔案。
CView::OnMouseMove(nFlags, point);
}
我們可以用
void SetMessageText( LPCTSTR lpszText );
void SetMessageText( UINT nID );
這個函數,它屬于CFrameWnd的成員函數,可以直接把str放置到狀态欄的訓示行上去。不再需要擷取狀态欄的對象或者指針。
例:
我們可以把((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);換成
((CMainFrame*)GetParent())->SetMessageText(str);來實作我們想要的功能。
我們看第三種方式:
要用到另外一種函數,這個函數叫 GetMessageBar 也是CFrameWnd的成員函數。它傳回的是一個指向狀态欄視窗的指針。就可以不用把m_wndStatusBar的保護類型換成公共類型;
例:
((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
我們看一下第四種方式:
要用到一個函數GetDescendantWindow
CWnd* GetDescendantWindow( int nID, BOOL bOnlyPerm = FALSE ) const;
它是CWnd的成員函數,傳回一個CWnd的指針,這個函數通過給定的一個ID号還擷取一個子孫視窗,搜尋整個子視窗處,知道找到我們所給定ID的子孫視窗。我們傳遞狀态欄的ID他就會得到一個狀态欄的指針。因為我們狀态欄屬于架構類,是以我們需要通過架構類的指針來調用這個函數。
我們看一下第二個參數:它是用來指定是否這個視窗對象可以作為一個臨時的被傳回,如果為真隻有持久的視窗對象被傳回,如果為false,他可以傳回一個臨時的視窗對象。
MFC臨時視窗和永久視窗:
在MFC中,都是以C++對象來操作視窗,而視窗是用句柄來辨別的,這樣就需要将視窗和C++對象關聯起來,通過C++對象的成員變量m_hWnd來建立這種聯系。如果一個視窗對象和一個視窗相關聯了,那麼我們要擷取這個視窗對象(通常都是CWnd*形式),傳回的就是一個持久的C++對象,如果你要擷取一個視窗(不是通過MFC類庫建立的)的C++對象,那麼MFC就會為你臨時建立一個C++對象,傳回其指針,這就是一個臨時的對象。作為臨時對象,它在它産生的函數中有效,例如:你在OnMouseMove中擷取到一個臨時對象的指針,那麼它在OnMouseMove函數中是有效的,但出來這個函數,就不一定了,因為在Windows消息循環的空閑時間,臨時對象将會被删除。
我們看一下狀态欄類CStatusBar的類成員:Create
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );
我們會發現有一個ID号,這個ID号就是系統預先為狀态欄定義的ID号。
例:
GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str); //因為 GetDescendantWindow本身就是CWnd的一個成員函數,是以不需要對GetParent()進行強制類型轉換了。
我們看一下MFC為我們定義的常用的控件的ID号
#define AFX_IDW_TOOLBAR 0xE800 //工具欄的ID
#define AFX_IDW_STATUS_BAR 0xE801 // 狀态欄的ID
#define AFX_IDW_PREVIEW_BAR 0xE802 // PrintPreview Dialog Bar
#define AFX_IDW_RESIZE_BAR 0xE803 // OLE in-place resize bar
#define AFX_IDW_REBAR 0xE804 // COMCTL32 "rebar" Bar
#define AFX_IDW_DIALOGBAR 0xE805 //控制條的ID