应用程序最下方就是状态栏。
状态栏分为两个部分:左边最长的那部分称为提示行;第二个部分就是最右边的三个窗格,主要用来显示CapsLock,NumLock,ScrollLock键的状态,称为状态栏指示器。
状态栏对象是在框架类中定义的:CStatusBar m_wndStatusBar;
CStatusBar类就是与状态栏相关的MFC类。
框架类的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
}
这段代码首先调用Create函数创建了状态栏对象。
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );
第一个参数指定状态栏的父窗口指针;第二个参数指定状态栏的风格;第三个就是状态栏的ID。
接着调用SetIndicators函数设置状态栏指示器,其中用到了一个数组参数:indicators。在框架类的源文件中有定义。
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
第一个ID是提示行,后面三个是Caps Lock,Num Lock,Scroll Lock键的状态指示器。
后三个ID都是MFC实现设定好的字符串资源ID。Resource View->双击String Table。就可以找到相应的字符串资源。
如果想要修改状态栏的外观,例如添加或减少状态栏上面的窗格,在indicators数组中添加或者减少相应的字符串资源ID即可。本例想在状态栏上显示当前系统的时间和一个进度条控件。
首先在字符串表中增加两个新的字符串资源:
ID:IDS_TIMER,Caption:时钟。
ID:IDS_PROGRESS,Caption:进度栏。
然后将两个字符串的ID添加到indicate数组中。
运行,结果如图。
现在准备将时钟的字符串显示为系统当前时间。这时用到MFC类:CTime。该类有一个静态的成员函数:GetCurrentTime,返回系统的当前时间。CTime另一个成员函数:Format,用来对CTime类型的时间对象进行格式化,得到时间的字符串。格式:%y表示年,%m表示月,%d表示天,%H表示24小时制,%I表示12小时制。
将字符串显示到状态栏窗格上:CStatusBar类的SetPaneText函数
BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE );
第一个参数是指示数组中的索引,第二个参数是窗格上面显示的文本;第三个参数默认为TRUE,表示设置窗格文本后,该窗格是无效的。
在框架类OnCreate函数最后加上:
CTime t=CTime::GetCurrentTime();
CString str=t.Format("%H:%M:%S");
m_wndStatusBar.SetPaneText(4,str);
由于在指示数组中时钟字符串索引为4。运行结果如图。
如果不知道窗格字符串ID在indicate数组中的索引,可以利用CStatusBar类成员函数:CommandToIndex函数获取指定ID资源的索引
CTime t=CTime::GetCurrentTime();
CString str=t.Format("%H:%M:%S");
int index;
index=m_wndStatusBar.CommandToIndex(IDS_TIMER);
m_wndStatusBar.SetPaneText(index,str);
但是时间字符串显示地并不完整,需要将窗格的宽度加大一些。因此需要调用CStatusBar类另一个成员函数:SetPanInfo。
BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE );
第一个参数是窗格字符串索引;第二个参数为指定窗格设置新的ID;第三个指示窗格的样式;第四个参数指定窗格新的宽度。
CTime t=CTime::GetCurrentTime();
CString str=t.Format("%H:%M:%S");
CClientDC dc(this);
//获取字符串的宽度
CSize sz=dc.GetTextExtent(str);
int index;
index=m_wndStatusBar.CommandToIndex(IDS_TIMER); m_wndStatusBar.SetPaneInfo(index,IDS_TIMER,SBPS_NORMAL,sz.cx);
m_wndStatusBar.SetPaneText(index,str);
运行结果如图。
但是这是一个静止的时间,要想实时显示时间,需要在OnTimer定时器消息响应函数加入显示时钟窗格字符串的代码。
void CMainFrame::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
static int index=0;
SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcons[index]);
index=++index%3;
CTime t=CTime::GetCurrentTime();
CString str=t.Format("%H:%M:%S");
CClientDC dc(this);
//获取字符串的宽度
CSize sz=dc.GetTextExtent(str);
m_wndStatusBar.SetPaneInfo(4,IDS_TIMER,SBPS_NORMAL,sz.cx);
m_wndStatusBar.SetPaneText(4,str);
CFrameWnd::OnTimer(nIDEvent);
}
9.6进度栏编程
MFC中,进度栏也有一个相关的类:CProgressCtrl,派生于CWnd类,它也是一个窗口类。
在程序中使用进度栏,首先构造一个CProgressCtrl对象,然后调用CProgressCtrl类的Create函数创建进度栏控件。
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
第一个指进度栏控件的类型,除了窗口的所有类型,它也有自己的类型:PBS_VERTICAL表示垂直放置;PBS_SMOOTH表示水平放置。
第二个参数表示进度栏控件的大小和位置。
第三个控件表示进度栏的父窗口。
第四个控件是进度栏的ID。
9.6.1在窗口中创建进度栏
首先为框架类添加一个一个CProgressCtrl类型的成员变量:m_progress。
然后在框架类OnCreate函数之中创建一个进度栏,该消息响应函数中添加代码:
m_progress.Create(WS_CHILD|WS_VISIBLE,CRect(100,100,200,120),this,123);
运行结果如图。
利用CProgressCtrl类型的成员函数:SetPos可以设置进度栏当前进度。在上述代码之后添加:
m_progress.SetPos(50);
运行结果如图。
也可以设置一个垂直的进度栏:
m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_VERTICAL,CRect(100,100,200,120),this,123);
9.6.2在状态栏的窗格中创建进度栏
在状态栏的窗格中显示进度栏,首先得获得该窗格的区域。这里用CStatusBar类的GetItemRect成员函数来完成:
void GetItemRect( int nIndex, LPRect lprect) const;
第一个参数是窗格的索引,第二个参数指示窗格的矩形区域。
在框架类OnCreate函数中修改:
CRect rect;
m_wndStatusBar.GetItemRect(5,&rect); m_progress.Create(WS_CHILD|WS_VISIBLE,rect,this,123);
m_progress.SetPos(50);
字符串为进度栏的窗格的索引为5。
运行发现,在状态栏上面并未创建进度栏。
原因是此时状态栏的初始化工作,即窗格的摆放操作还没有完成。只有框架类的OnCreate函数完成之后,才能获得状态栏上窗格的矩形区域。
为了避免自定义的消息与已有的消息冲突,可以利用Windows提供的一个常量:WM_USER,小于这个常量的值都是Windows系统保留的消息。
首先在框架类的头文件中定义一条自定义消息:
#define UM_PROGRESS WM_USER+1
接着为这条自定义消息添加消息响应函数原型的声明:
//{{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宏来实现这一功能:
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_TIMER()
ON_COMMAND(IDM_TEST, OnTest)
ON_COMMAND(IDM_VIEW_NEWTOOLBAR, OnViewNewtoolbar)
ON_UPDATE_COMMAND_UI(IDM_VIEW_NEWTOOLBAR, OnUpdateViewNewtoolbar)
//}}AFX_MSG_MAP
ON_MESSAGE(UM_PROGRESS,OnProgress)
最后就是添加消息响应函数实现:
void CMainFrame::OnProgress()
{
CRect rect;
m_wndStatusBar.GetItemRect(5,&rect); m_progress.Create(WS_CHILD|WS_VISIBLE,rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
}
将框架类之前创建进度栏的代码注释起来,但是在框架类OnCreate函数的最后发送UM_PROGRESS消息:
SendMessage(UM_PROGRESS);
运行,发现还是在相应的窗格上没有出现进度栏,原因就是,发送完该消息时,程序直接执行相应的消息处理函数,而此时OnCreate函数并没有执行完毕。
因此不能使用SendMessage函数,而应该使用PostMessage,该函数把消息放到消息队列中,立即返回;执行完OnCreate函数后,再取出UM_PROGRESS消息,执行相应的消息响应函数。
将SendMessage换成PostMessage,运行如图。
如果想要连续平滑的效果,在创建进度栏时候,就需要设置PBS_SMOOTH类型。如图。
但是窗口尺寸发生变化时,进度栏显示的位置就会发生错误,如图。
这就需要我们在窗口尺寸发生变化时,重新获取索引号为5的窗格,将进度栏移动到该窗格中。由于窗口第一次显示就需要调用OnPaint函数,因此不需要发送自定义消息,注释PostMessage这一行。
为框架类添加WM_PAINT消息响应函数:
void CMainFrame::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect rect;
m_wndStatusBar.GetItemRect(5,&rect);
m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,rect,&m_wndStatusBar,123);
m_progress.SetPos(50);
// Do not call CFrameWnd::OnPaint() for painting messages
}
运行程序,会弹出一个非法操作框,原因就是之前程序已经创建了一个进度栏与m_progress对象相关联了。这里不能再一次创建进行关联。因此需要增加一个判断:
void CMainFrame::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
CRect rect;
m_wndStatusBar.GetItemRect(5,&rect);
if(!m_progress.m_hWnd)
{ m_progress.Create(WS_CHILD|WS_VISIBLE|PBS_SMOOTH,rect,&m_wndStatusBar,123);
}
else
{
m_progress.MoveWindow(rect);
}
m_progress.SetPos(50);
// Do not call CFrameWnd::OnPaint() for painting messages
}
这里用到了一个MoveWindow函数,将进度栏移动到目标矩形区域中。运行如图。
下面让进度栏动起来,即在进度栏上以某种显示方式不断增加当前位置,这可以通过CProgressCtrl类的StepIt成员函数完成;而每次前进的步长可以通过CProgressCtrl类的SetStep来设置;还可以通过CProgressCtrl类的SetRange成员函数来设置进度栏的范围,默认范围是0~100。
现在实现每隔一秒,就前进一步,在框架类的OnTimer函数倒数第二行添加:
m_progress.StepIt();
运行如图。
9.7在状态栏上显示鼠标的位置
要捕获与鼠标相关的消息,应该在视类中添加WM_MOUSEMOVE消息的响应函数。
void CStyleView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CString str;
str.Format("x=%d,y=%d",point.x,point.y);
((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);
CView::OnMouseMove(nFlags, point);
}
因为视类用到框架类的成员变量,所以要将m_wndStatusBar设置为public类型。而且在源文件中还需要包含"MianFrm.h"头文件,SetWindowText(str)可以直接将鼠标坐标信息放置到状态栏的第一个窗格中。
第二种方法就是利用框架类成员函数:SetMessageText函数,该函数的作用就是将ID为0的状态栏窗格设置一个字符串。
((CMainFrame*)GetParent())->SetMessageText(str);
9.8启动画面
由于VC++6.0添加的控件只有在WindowsXP下才可以用,这里不做具体分析。