天天看點

MFC狀态欄的程式設計

接下來我們看一下狀态欄的程式設計。

分為兩類:

第一類:提示行,左邊上的那一塊,主要用來菜單項的指令和工具欄的指令

第二類:窗格形式,顯示開關的狀态,狀态訓示器。例如鍵盤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