6.4 在非对话框窗口中使用控件
控件并不是对话框独有的,事实上,很多非对话框窗口都可以使用控件.比较典型的应用是在表单视图、工具条和状态栏中使用控件.
6.4.1 在表单视图中使用控件
MFC提供了一个名为CFormView的特殊视图类,我们称其为表单视图.表单视图是指用控件来输入和输出数据的视图,用户可以方便地在表单视图中使用控件.表单视图具有对话框和滚动视图的特性,它使程序看起来象是一个具有滚动条的对话框.在有些情况下,用表单视图比用普通视图更符合用户的需要,例如,在向数据库输入数据时,显然用表单的形式可以更习惯些.
用AppWizard可以方便地创建基于表单视图的应用程序,只要在MFC AppWizard对话框的第六步先选择CView,然后在Base class栏中选择CFormView,AppWizard就会创建一个基于CFormView的应用程序.
读者可以按上述方法建立一个名为Test的应用程序.在Test工程的资源中,读者会发现一个ID为IDD_TEST_DIALOG的对话框模板,该对话框模板可供用户放置和安排控件.在程序运行时,框架根据该对话框模板创建CFormView对象,并根据模板的信息在表单视图中自动创建控件.与设计对话框类相类似,用户可以用ClassWizard为表单视图类加入与控件对应的成员变量,可以调用UpdateData在控件和成员变量之间交换数据,但对控件的初始化工作是在OnInitialUpdate函数而不是在OnInitDialog函数中进行的.
基于表单视图的应用程序与基于对话框的应用程序都是在应用程序中直接使用控件,但二者有很多不同之处.基于对话框的应用程序是用一个对话框来作为程序的主窗口的,因而程序的主窗口的特性与对话框类似,如窗口的大小不能改变,程序没有菜单条、工具条和状态栏等.基于表单视图的应用程序仍然是基于Doc/View框架结构的(见七、八、九章),只是视图被换成了表单视图,也就是说,应用程序的窗口可以改变大小,程序有菜单条、工具条和状态栏,且程序仍然可以Dov/View运行机制来处理文档.
表单视图比较简单,这里就不举例说明了.在第十章,读者会看到使用表单视图的例子.
6.4.2 在工具条和状态栏中使用控件
一个专业的程序常常会在工具条和状态栏中加入一些控件以方便用户的使用.例如,在Developer Studio的工具条中就有不少组合框,而在状态栏中则常常会显示一个进度条来表明工作的进度.
如果读者想在自己程序的工具条和状态栏中加入控件,则需要掌握一些技巧.在本小节,我们将结合一个具体实例来演示这些技巧.例程的名为CtrlInBar,其界面如图6.8所示.可以看出,该程序在工具条中创建了一个组合框,在状态栏中创建了一个进度条.
图6.8 CtrlInBar程序
现在让我们开始工作.首先,请读者用AppWizard建立一个名为CtrlInBar的单文挡MFC应用程序,然后,请按清单6.4修改源代码.注意在程序中编写了一个CToolBar类的派生类CMyToolBar,以及一个CStatusBar的派生类CMyStatusBar,这两个类与CMainFrame类在同一模块中.
清单6.4 在工具条和状态栏中创建控件的有关代码
// MainFrm.h : interface of the CMainFrame class
//
/////////////////////////////////////////////////////////////////////////////
#define IDC_MYCOMBO 100
class CMyToolBar : public CToolBar
{
public:
CComboBox m_ComboBox;
BOOL CreateComboBox(int nIndex);
};
#define ID_INDICATOR_PROGRESS 100
class CMyStatusBar : public CStatusBar
{
public:
CProgressCtrl m_Progress;
int m_nProgressPane;
BOOL CreateProgressCtrl(int nPane);
afx_msg void OnSize(UINT nType, int cx, int cy);
DECLARE_MESSAGE_MAP()
};
class CMainFrame : public CFrameWnd
{
. . . . . .
protected: // control bar embedded members
CMyStatusBar m_wndStatusBar;
CMyToolBar m_wndToolBar;
. . . . . .
};
// MainFrm.cpp : implementation of the CMainFrame class
//
. . . . . .
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_SEPARATOR
};
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_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_wndToolBar.CreateComboBox(0); //在工具条的最左边创建组合框
m_wndStatusBar.CreateProgressCtrl(1); //在第二个窗格创建进度条
m_wndToolBar.m_ComboBox.AddString("Item1");
m_wndToolBar.m_ComboBox.AddString("Item2");
m_wndToolBar.m_ComboBox.AddString("Item3");
m_wndToolBar.m_ComboBox.AddString("Item4");
m_wndStatusBar.m_Progress.SetRange(0,200);
m_wndStatusBar.m_Progress.SetPos(100);
. . . . . .
}
/////////////////////////////////////////////////////////////////////////////
//CMyToolBar
//参数nIndex是按钮的索引,函数将在该按钮的左侧创建组合框
BOOL CMyToolBar::CreateComboBox(int nIndex)
{
if(m_ComboBox.GetSafeHwnd()) //防止重复创建
return FALSE;
CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl();
TBBUTTON button;
CRect rect;
button.fsStyle=TBSTYLE_SEP;
ToolBarCtrl.InsertButton(nIndex,&button); //插入空位
ToolBarCtrl.InsertButton(nIndex,&button);
ToolBarCtrl.InsertButton(nIndex,&button);
//设置空位的宽度(处于中间的空位用来容纳组合框)
SetButtonInfo(nIndex+1,IDC_MYCOMBO,TBBS_SEPARATOR,100);
SetButtonInfo(nIndex, ID_SEPARATOR, TBBS_SEPARATOR, 12);
SetButtonInfo(nIndex+2, ID_SEPARATOR, TBBS_SEPARATOR, 12);
GetItemRect(nIndex+1, &rect); //获取中间空位的坐标
rect.top = 3;
rect.bottom = rect.top + 100;
if (!m_ComboBox.Create(CBS_DROPDOWNLIST|WS_VISIBLE|WS_TABSTOP,
rect, this, IDC_MYCOMBO))
return FALSE;
m_ComboBox.SetItemHeight(-1,15); //设置编辑框组件的高度
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
//CMyStatusBar
BEGIN_MESSAGE_MAP(CMyStatusBar, CStatusBar)
ON_WM_SIZE()
END_MESSAGE_MAP()
//参数nPane是窗格的索引,函数将在该窗格内创建进度条控件
BOOL CMyStatusBar::CreateProgressCtrl(int nPane)
{
if(m_Progress.GetSafeHwnd()) //防止重复创建
return FALSE;
//设置该窗格的宽度为200
SetPaneInfo(nPane,GetItemID(nPane),SBPS_NORMAL,200);
CRect rect(0,0,1,1);
if(!m_Progress.Create(WS_CHILD|WS_VISIBLE,rect,this,
ID_INDICATOR_PROGRESS))
return FALSE;
m_nProgressPane=nPane;
return TRUE;
}
void CMyStatusBar::OnSize(UINT nType, int cx, int cy)
{
CStatusBar::OnSize(nType, cx, cy);
if(m_Progress.GetSafeHwnd()==NULL) return;
CRect rect;
GetItemRect(m_nProgressPane,&rect);
m_Progress.MoveWindow(rect); //调整控件的位置和尺寸
}
CMyToolBar类可以在工具条中指定按钮的左边放置一个下拉列表式组合框,并在组合框的两端留出空位.该类的CreateComboBox成员负责创建组合框,参数nIndex是工具条按钮的索引,需注意的是工具条的一个空位也要占有一个索引.在CreateComboBox中,主要调用了下列函数:
调用CToolBar::GetToolBarCtrl返回一个CToolBarCtrl对象.从4.0版开始,CToolBar类是在新控件类CToolBarCtrl类的基础上实现的,后者具有更强大的功能.例如CToolBarCtrl提供了CToolBar没有的InsertButton成员函数.
调用CToolBarCtrl::InsertButton在nIndex索引处插入三个空位.
调用CToolBar::SetButtonInfo设置空位的宽度,其中中间的空位有100像素宽,用来容纳组合框.
调用CToolBar::GetItemRect获得中间空位的坐标.
调用CComboBox::Create函数创建组合框.注意rect对象说明的是包括列表框在内的组合框的尺寸.
调用CComboBox::SetItemHeight设置编辑框组件的高度.
CMyStatusBar类可以在指定的状态栏窗格中放置一个进度条.该类的CreateProgressCtrl成员负责创建进度条,参数nPane是窗格的索引.在该函数中主要调用了下列函数:
调用CStatusBar::SetPaneInfo设置窗格的宽度为200.在调用该函数时,先调用CStatusBar::GetItemID返回窗格的ID.
调用CProgressCtrl::Create创建控件.
大家可能会奇怪,CProgressCtrl::Create创建的控件只有1×1大小.这是由于在调用该函数创建控件时,状态栏的大小往往并未确定.这时如果调用CStatusBar::GetItemRect,只能得到0坐标,而不能得到正确的窗格坐标,所以程序只好先创建一个1×1的控件.
工具条中按钮和控件的尺寸及其相对于工具条的位置不会随外界因素发生变化,而状态栏则不同,当用户改变了框架窗口的宽度时,状态栏的宽度也会随之改变,并且它会重新调整各窗格的大小和位置,此时如果不及时调整进度条的坐标,那么进度条与所在窗格之间将发生错位.调整进度条的大小和位置的工作由CMyStatusBar::OnSize函数完成.当窗口的尺寸发生改变后,窗口会收到WM_SIZE消息,OnSize是WM_SIZE消息的处理函数.在CMyStatusBar::OnSize函数中,先调用CStatusBar::GetItemRect获得进度条所在窗格的坐标,然后调用CWnd::MoveWindow来调整进度条控件的坐标.
在窗口形成时,也会收到WM_SIZE消息,这时OnSize函数可以及时调整进度条的大小和位置.