第四章 装饰应用程序的外观
-
Upload
zbigniew-krol -
Category
Documents
-
view
51 -
download
5
description
Transcript of 第四章 装饰应用程序的外观
第四章 装饰应用程序的外观
创建友好 , 美观的用户界面是成功设计和编制一个 Windo
ws 应
用程序的重要组成部分,程序用户界面的设计编程工作包括菜单、控制栏( 其中包括工具栏、状态栏、对话框栏等) 创建和控制、对话框、帮助信息的制作和运行,以及框架、视图窗口的位置和大小控制等。其中对话框的制作和运行操作将在下一
章中作详细的描述。
在 AppWizard 创建的应用程序框架中,菜单,工具栏 , 状态栏
和视图窗口均可以缺省隶属于主框架窗口,并由主框架窗口管
理和控制。一个典型主框架窗口的组成以及与用户界面各个组
成部分的关系如下图所示:
⑴ SDI 应用程序框架
SDI主框架窗口
标题栏菜单栏工具栏窗口
状态栏窗口
视图窗口
子窗口
⑵ MDI 应用程序框架
MDI主框架窗口
标题栏菜单栏工具栏窗口
状态栏窗口
标题栏
MDI子框架窗口
视图窗口
子窗口
MFC AppWizard 所生成的应用程序主框架窗口可以选择拥有缺
省的基本菜单、相应的工具栏、状态栏和由系统自动确定框架
窗口位置和尺寸,但在实际编程中往往需要在此基础上修改和
添加自定义的菜单、工具栏、状态栏以及由用户确定框架窗口
位置和尺寸,使应用程序外观满足特定的程序功能、特定的用
户和特定的使用环境的需要。
MFC 为工具栏,状态栏,菜单,主框架窗口提供了相应的基类,以便实现对这些程序外观组成部分的创建和各种操作。这些类在 MFC 中的派生层次结构如下:
· 工具栏和状态栏分别由 CToolBar 和 CStatusBar 类描述,
它们都是控制栏类 CControlBar 的派生类,而 CControlBar
又是
CWnd 的派生类,因此,工具栏和状态栏本身也是一个能响
应消息的特定窗口,在主框架窗口中可以视为是子窗口。
CObject
CCmdTarget
CWnd
CControlBar
CToolBar
CStatusBar
CDialogBar
CReBar
· 菜单是由 CMenu 类描述,
它是直接从 CObject 类派生的,因此该类的对象只能用于完
成菜单的创建、跟踪、修改、销毁和发送命令消息,而本身
不能响应消息;在主框架窗中可以视为用于发送命令消息的
一个特定区域。
CObject
CMenu
· 主框架窗口是由 CFrameWnd (用于 SDI )或 CMDIFrameWnd
和
CMDIChildWnd (用于 MDI ),它们都是 CWnd 的直接或间接派
生类。 CObject
CCmdTarget
CWnd
CFrameWnd
CMDIFrameWnd
CMDIChildWnd
由 AppWizard 创建的文档视图结构的应用程序主框架窗口类
对象中缺省的工具栏被定义成名为 m_wndToolBar 的 CToolBar
类
对象成员,而状态栏被定义成名为 m_wndStatusBar 的 CStatusB
ar
类对象成员 。创建和显示工具栏和状态栏的操作应在程序主框
架窗类 CMainFrame 对象所关联的窗口已经创建完成(窗口句柄已有效) 但还未显示时进行,即在窗口创建消息 WM_CREA
TE
的响应成员函数 OnCreate 被调用中完成的。因此,需要在 C
MainFrame 类中重新定义 OnCreate 函数,以便在主框架窗口显
示之前添加任何需要附加的初始化工作,例如创建和显示工具
栏和状态栏、创建其他子窗口等。
与其它 Windows 资源所对应的 MFC 类一样, CMenu 类也是将菜
单的句柄和属性和对菜单属性的各种操作等封装了起来,并且
只有当菜单句柄有效时,对菜单属性的操作才是合法的。通常
情况下,菜单总是主框架窗口的一个区域,所以当主框架窗口
的产生函数或 LoadFrame 函数被调用时,与菜单资源相关联的
CMenu 对象作为框架窗口的保护成员被创建。
因此,通常情况下,编程者只需要通过设计菜单的静态资
源,而且无须通过 CMenu 类对象的调用对象的行为,就能满足
程序对主菜单的一般设计编程需求。但在程序需要动态创建新
菜单或修改已有菜单时,则必须调用 CMenu 对象的行为。
如果你需要修改主框架窗口的菜单,你可能需要通过窗口类
CWnd 的成员函数 GetMenu 获取保护成员 CMenu 对象的指针,
并通过该指针对该菜单对象进行各种需要的访问和操作,以便
满足特殊的菜单设计编程需求,例如:
· 动态切换不同菜单;
· 动态隐藏和显示菜单;
· 动态添加和删除菜单项;
· 动态禁止和激活菜单项;
· 动态为菜单项添加图形标签;
· 动态创建浮动的弹出式环境菜单。
对框架和视图窗口的外观操作,就是对它们的位置、大小、状态的控制,称之为放置( Placement )操作。视图窗口是包含在框架窗口中的,它随着框架窗口的变化而变化,换句话说,对视图窗口的放置操作是通过对框架窗口的放置操作实现的。虽然用户可以方便地通过框架窗口提供的最大化、最小化等窗口操作界面对框架和视图窗口的放置进行动态交互控制,但在不少情况下,还是需要在程序中通过代码对框架和视图窗口的放置进行定制操作。例如,希望程序在运行开始时能保持最近一次运行的框架和视图窗口的位置、大小、状态。实现这些放置操作是窗口创建过程中完成的,这对于 SDI 和 MDI 应用程序
是不完全一样的,对于 MDI ,不仅要考虑主框架窗口的放置,
还要考虑子框架窗口的放置。
本章将通过 5 类实例程序分别讲述如何实现上述外观设计:
·第 1 类实例讲解工具栏和状态栏编程;
·第 2 类实例讲解 CDialogBar 编程;
·第 3 类实例讲解 CReBar 编程;
·第 4 类实例讲解菜单编程;
·第 5 类实例讲解具有持续特性的定制框架窗口类编程。
4.1 创建浮动工具栏
4.1.1 利用 AppWizard 自动创建工具栏
在 MFC 中,工具栏资源和工具栏类 CToolBar 是实现工
具栏的
两个要素。创建工具栏对象的基本步骤为:
· 创建工具栏资源;
· 创建一个 CToolBar 对象;
· 调用 CToolBar::Create 函数创建工具栏窗口;
· 调用 CToolBar::LoadToolBar 载入工具栏资源。
在使用 AppWizard 生成的默认配置的应用程序框架中包括了
能创建一个缺省工具栏的四步操作的所有代码,因此修改工具
栏中的按钮只需要修改缺省工具栏资源就可以实现。而如果需
要创建缺省工具栏以外的工具栏,则必须在 AppWizard 生成的框
架基础上,效仿上述缺省资源和代码添加相应的自定义资源和
代码。为此,分析创建缺省工具栏的代码是十分必要的。
创建一个 SDI 应用程序项目 “ DefaultDefault” (使用 AppWizard 的缺
省选择),查询所创建的应用程序框架的代码,可以发现与工
具栏有关的资源和代码有:
1 在资源中添加了工具栏资源 IDR_MAINFRAME :
2 在 CMainWnd 的定义中添加了定义工具栏对象成员的代码:
CToolBar m_wndToolBar;
3 在重新定义的 CMainWnd::OnCreate 中添加了创建工具栏 , 装
载工具资源和初始化工具栏的缺省代码。
分析 CMainWnd::OnCreate ,该虚函数首先调用了基类中定义
的版本 CFrameWnd::OnCreate 进行基类部分的初始化工作,
然后可以添加主框架窗口所需要的任何初始化代码,其中包
括了对工具栏的创建和初始化操作:
⑴ 创建工具栏窗口
创建工具栏窗口的函数是 CToolBar::Create 或 CToolBar::Creat
eEx
它们的原型分别为:
BOOL Create( CWnd *pParentWnd, // 父窗口指针
DWORD dwStyle = WS_CHILD| WS_VISIBLE|CBRS_TOP, // 风格
UINT nID=AFX_IDW_TOOLBAR /* 工具栏子窗口 ID */ );
BOOL CreateEx(CWnd* pParentWnd, //父窗口指针
DWORD dwCtrlStyle = TBSTYLE_FLAT, // 工具栏控件风格
DWORD dwStyle = WS_CHILD| WS_VISIBLE|CBRS_ALIGN_TOP,
CRect rcBorders=CRect(0,0,0,0), // 工具栏的矩形边界
UINT nID=AFX_IDW_TOOLBAR /* 工具栏子窗口 ID */ );
其中: · pParentWnd 是指向工具栏的父窗口,即主框架窗口对象的
指针。 · dwCtrlStyle 和 dwStyle 的设置是决定所创建的工具栏的外观 和状态的重要因素,可设置的样式值包括 CWnd 的
样式和 CToolBar 的样式两部分(祥见 MSDN 的有关部分)。 · 如果创建成功,返回真实的;否则返回错误的。例如,在本例中实现创建工具栏窗口的缺省代码为:
m_wndToolBar.CreateEx( this, TBSTYLE_FLAT, WS_CHILD |
WS_VISIBLE| CBRS_TOP|CBRS_GRIPPER | CBRS_TOOL
TIPS |
CBRS_FLYBY|CBRS_SIZE_DYNAMIC );
注意,该调用中第 4 参数 rcBorders 和第 5 参数 nID 使用了缺省
值 CRect(0,0,0,0) 和 AFX_IDW_TOOLBAR 。
⑵ 加载工具栏资源
为工具栏对象加载资源的函数为 CToolBar::LoadToolBar ,其
原型为:
BOOL LoadToolBar ( LPCTSTR lpszResourceName /* 资源名 */ );
BOOL LoadToolBar ( UINT nIDResource /* 资源 ID */ );
本例中实现加载工具栏资源的代码为:
m_wndToolBar.LoadToolBar ( IDR_MAINFRAME );
IDR_MAINFRAME 是工具栏资源的 ID 。如果资源加载成功,则
返回 TRUE 的;否则返回 FALSE 的。
⑶ 设置工具栏停靠特性
实现设置的函数为 CControlBar::EnableDocking ,其原型为:
void EnableDocking ( DWORD dwStyle );
参数为停靠样式,种类和取值参考 MSDN 中的相关部分。
本例中实现设置的代码为:
m_wndToolBar.EnableDocking ( CBRS_ALIGN_ANY );
参数 CBRS_ALIGN_ANY 所指示的停靠样式是允许工具栏停靠
到主框架窗口中的任何一边。
⑷ 设置主框架窗口的工具栏停靠特性
实现该设置的函数为 CFrameWnd::EnableDocking ,其原型:
void EnableDocking ( DWORD dwdocStyle );
参数为停靠样式,种类和取值参考 MSDN 中的相关部分。
本例中实现设置的代码为:
EnableDocking ( CBRS_ALIGN_ANY );
参数 CBRS_ALIGN_ANY 所指示的停靠样式是使框架窗口的任
何一边都可以停放控制栏。在拥有多个控制栏的框架窗口中
该函数只需调用一次。
⑸ 把工具栏停靠在主框架窗口中的确定位置
实现停靠操作的函数为 CFrameWnd::DockControlBar ,原型:
void DockControlBar ( CControlBar*pBar, UINT nDockBarID=0,
LPCRECT lpRect=NULL );
本例中实现停靠的代码为:
DockControlBar (&m_wndToolBar);
使工具栏按照创建工具栏窗口时的默认位置(框架窗口工作
区的顶部),实现停靠操作。
4.1.2 添加自定义工具栏 创建一个名为 "TbTb" 的 SDI 应用程序项目,参照由 App
Wizard
缺省创建的工具栏添加自定义工具栏。1 自定义工具栏资源 使用资源编辑器添加一个标识为 ID_TOOLBAR1 的工具栏
资源 其中包括两个按钮:
2. 添加自定义工具栏的程序代码⑴ 在 CMainFrame 类中增加一个 CToolBar 类的保护成员对象: CToolBar m_wndToolBar1;
⑵ 在 CMainFrame::OnCreate 添加对应于 m_wndToolBar1 的创建、
加载资源和初始化代码:
标识 操作提示文本ID_LINE
ID_CIRCLE
绘制一条直线 \n 直线绘制一个圆 \n 圆
…
if ( !m_wndToolBar1.CreateEx ( this , TBSTYLE_FLAT,WS_CHILD |
WS_VISIBLE | CBRS_TOP|CBRS_GRIPPER |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMI
C) ||
!m_wndToolBar1.LoadToolBar ( IDR_TOOLBAR1 ) )
{
TRACE0 (" Failed to create toolbar\n ");
return -1; // fail to create
}
…
m_wndToolBar1.EnableDocking (CBRS_ALIGN_ANY);
…
DockControlBar (&m_wndToolBar1);
此时编译运行应用程序,可以看到新增加的自定义工具栏, 但工具栏中的按钮是灰色的,即不能对它进行任何操作。
4.1.3 自定义工具栏的命令响应及其用户界面更新在 CTbView 类中,添加工具栏按钮 ID_LINE 和 ID_CIRCL
E 的命令消息和界面更新命令消息的响应。1 为 CTbView 类增加保护数据成员和枚举: int m_nDraw; // 指示当前的绘图类别。
enum{LINE,CIRCLE}; // 定义可以使用的绘图类别。
2 在 CTbView 的构造函数中对 m_nDraw 进行初始化: m_nDraw = LINE;
3. 使用 ClassWizard 为命令消息 ID_LINE 和 ID_CIRCLE 定义命令
响应函数和用户界面更新函数。 在类定义文件中添加了这些成员函数的原型:
afx_msg void OnLine ();
afx_msg void OnCircle ();
afx_msg void OnUpdateLine (CCmdUI*pCmdUI);
afx_msg void OnUpdateCircle (CCmdUI*pCmdUI);
在类实现文件中添加了上述成员函数的操作定义如下:
void CTbView::OnLine()
{
m_nDraw = LINE;
}
void CTbView::OnCircle()
{
m_nDraw = CIRCLE;
}
void CTbView::OnUpdateLine (CCmdUI*pCmdUI)
{
pCmdUI->SetCheck(m_nDraw == LINE);
}
void CTbView::OnUpdateCircle(CCmdUI*pCmdUI)
{
pCmdUI->SetCheck(m_nDraw == CIRCLE);
}
4 编译运行 "TbTb"
4.1.4 自定义工具栏的隐藏 / 显示
要使自定义的工具栏能像缺省工具栏那样具有隐藏和显示
操
作功能必须添加以下资源和代码:
1 在主菜单 IDR_MAINFRAME 的“查看”弹出菜单中增加新的菜单
项 " 自定义工具栏 " ,其标识值为 ID_VIEW_TOOLBAR1
2 在主框架窗口类 CMainFrame 中,为 ID_VIEW_TOOLBAR1 定义
命令响应函数和用户界面更新函数
在类定义文件中添加这些成员函数的原型:
afx_msg void OnViewToolbar1();
afx_msg void OnUpdateViewToolbar1( CCmdUI* pCmdUI );
在类实现文件中添加上述成员函数的操作定义:
void CMainFrame::OnViewToolbar1()
{
ShowControlBar( &m_wndToolBar1,
m_wndToolBar1.IsWindowVisible() ? FALSE : TRUE, FALSE );
}
void CMainFrame::OnUpdateViewToolbar1( CCmdUI* pCmdUI )
{
pCmdUI->SetCheck( m_wndToolBar1.IsWindowVisible( ) );
}
3 编译运行 "TbTb"
4.1.5 动态删除和添加工具栏中按钮 如果希望通过程序动态地从已经创建的工具栏中删除一些按
钮或向工具栏中添加一些按钮,则需要使用 CToolBar 类对象中
的 CToolBarCtrl 类对象成员来实现,而这个对象成员可以通过调用 CToolBar::GetToolBarCtrl 函数获得实例 "Tb1Tb1" 是在实例
"Tb"
的基础上增加了通过程序代码对工具栏中的按钮进行动态删除和添加的功能:1 在 CMainFrame 类定义中增加下列保护数据成员: // for saving buttons of the m_wndToolBar
TBBUTTON m_tbButtons[11];
// for saving the most and least count of buttons
int m_nMost, m_nLeast;
2 在 CMainFrame::OnCreate 中增加下列初始化代码:
// Save some TBBUTTONs for restoring later
m_nMost = m_wndToolBar.GetToolBarCtrl().GetButtonCount();
// Save the most count of buttons
for(int I = 2; I < m_nMost - 1; i++)
m_wndToolBar.GetToolBarCtrl().GetButton(i, &m_tbButtons[i]);
// Delete some buttons from m_wndToolBar.
DeleteTBButtons(2,m_nMost - 3);
// Save the least count of buttons
m_nLeast = m_wndToolBar.GetToolBarCtrl().GetButtonCount();
3 为 CMainFrame 定义删除和恢复工具栏按钮的保护成员函数: 在定义文件中增加这两个函数的原型声明: void RecoverTBButtons ( int index,int count );
void DeleteTBButtons ( int index, int count );
在实现文件中增加这两个函数的定义代码: void CMainFrame::DeleteTBButtons( int index,int count)
{
for ( int I = index; I < index + count; i++)
m_wndToolBar.GetToolBarCtrl().DeleteButton(index);
// 从工具栏中顺序删除索引为 index 的按钮。ShowControlBar(&m_wndToolBar, TRUE, TRUE);
// 重新显示变化后的工具栏。
}
void CMainFrame::RecoverTBButtons(int index, int count)
{
for (int I = index; I < index + count; i++)
// 将删除的按钮顺序恢复插入到工具栏中。
m_wndToolBar.GetToolBarCtrl().InsertButton(i, &m_tbButtons[i]);
// 重新显示变化后的工具栏。
ShowControlBar(&m_wndToolBar, TRUE, TRUE);
}
4 在主菜单资源 IDR_MAINFRAME 的“查看”菜单中增加菜单项:
“删除工具栏中的按钮”和“恢复工具栏中的按钮”,它们的标识
为: ID_DELETE_BUTTONS 和 ID_RECOVER_BUTTONS 。
5 使用 ClassWizard 在 CMainFrame 类中为 ID_DELETE_BUTTONS
和 ID_INSERT_BUTTONS 添加命令消息响应和用户界面更新函数。
在类定义文件中增加了:
afx_msg void OnDeleteButtons ();
afx_msg void OnRecoverButtons ();
afx_msg void OnUpdateDeleteButtons (CCmdUI* pCmdUI);
afx_msg void OnUpdateRecoverButtons (CCmdUI* pCmdUI);
在类实现文件中增加了命令映射项和消息响应函数:
ON_COMMAND(ID_DELETE_BUTTONS, OnDeleteButtons)
ON_COMMAND(ID_INSERT_BUTTONS, OnRecoverButtons)
ON_UPDATE_COMMAND_UI(ID_DELETE_BUTTONS,
OnUpdateDeleteButton
s)
ON_UPDATE_COMMAND_UI(ID_INSERT_BUTTONS,
OnUpdateRecoverButto
ns)
void CMainFrame::OnDeleteButtons()
{
DeleteTBButtons(2, m_nMost - 3);
}
void CMainFrame::OnRecoverButtons()
{
RecoverTBButtons(2, m_nMost - 3);
}
void CMainFrame::OnUpdateDeleteButtons(CCmdUI* pCmdUI)
{
pCmdUI->Enable(
m_wndToolBar.GetToolBarCtrl().GetButtonCount() == m_nM
ost );
}
void CMainFrame::OnUpdateRecoverButtons(CCmdUI* pCmdUI)
{
pCmdUI->Enable(
m_wndToolBar.GetToolBarCtrl().GetButtonCount() == m_nL
east );
}
6 编译运行 "Tb1"
4.2 创建自定义状态栏 分析 “ DefaultDefault” 项目中关于状态栏的代码。不难发现状态
栏也是窗口,该窗口一般分成几个窗格,在每个窗格中可以显示不同的信息。自动创建的缺省状态栏包括了四个窗格,分别显示菜单命令提示和键盘的大写、数字、滚屏锁定状态。 状态栏是由 CStatusBar 类实现的。状态栏的典型创建步骤:· 定义一个 CStatusBar 对象;· 为状态栏的每个窗格定义 ID ,并为每个窗格定义了标题字符 串资源(字符串资源和窗格的 ID 相同);· 调用 CStatusBar::Create 创建状态栏窗口;· 调用 CStatusBar::SetIndicators 函数分配窗格,并将状态栏的每 个窗格与对应 的字符串资源绑定。
4.2.1 利用 AppWizard 自动创建状态栏 使用 AppWizard 的缺省选择创建的程序框架能自动创建一
个
缺省状态栏。分析程序中与状态栏有关的代码,对于修改原有
的状态栏和创建自定义状态栏都是十分必要的。与缺省状态栏
有关的资源和代码包括:
1 CMainFrame 类中定义中添加了状态栏对象保护成员:
protected:
CStatusBar m_wndStatusBar;
…
2 在 CMainFrame 类实现文件中添加了把状态栏分割为窗格的全程静态数组定义 static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
其中: ·ID_SEPARATOR 对应的窗格用于显示菜单命令提示。 · 其他三项用于显示键盘相应的键状态,它们所对应的字符串在资源 String Table 中被定义为:
STRINGTABLE DISCARDABLE
BEGIN
…
ID_INDICATOR_CAPS "CAP"
ID_INDICATOR_NUM "NUM"
ID_INDICATOR_SCRL "SCRL"
…
END
· 确定状态栏窗格数目和内容的 indicators 由几个和哪些 ID
组
成取决于程序的需要。
· 如果 indicators 中加入新元素,则要为新元素指定 ID ,还要
根据需要,在字符串资源中定义 ID 标识的描述字符串。
3 创建状态栏窗口
与工具栏一样,状态栏创建操作也是在 CMainFrame::OnCre
ate
完成的。实现该操作的代码: 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 和 SetIndicators 函数的原型:
BOOL Create( CWnd* pParentWnd, // 父窗口指针
DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM,
UINT nID = AFX_IDW_STATUS_BAR /* 状态栏 ID */ );
其中状态栏样式参数包括如下三种: ·CBRS_TOP Control bar is at top of frame window.
·CBRS_BOTTOM Control bar is at bottom of frame window.
·CBRS_NOALIGN Control bar is not repositioned when the parent is
resized.
注意:状态栏不支持浮动。 BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );
这两个成员函数调用成功,返回 TRUE ,否则返回 FALS
E 。由
于状态栏不支持浮动,所以就不必设置其停靠特性。
4.2.2 自定义状态栏 实例 “ Sb” 是在实例 “ Default” 的基础上对状态栏 m_w
ndStatusBar
进行了如下修改:
·第一个窗格在保持原有功能的基础上,使它能够显示鼠标在
窗口工作区中移动时的位置信息。
· 将第四个窗格中显示的键状态修改为显示键盘的 <Shift> 键的
状态,即按下 <Shift> 键,窗格显示文本信息 “ SHIFT” ,释放
<Shift> 键,窗格中的文本信息 “ SHIFT” 消失。
·增加第五个窗格用于显示当前时间(时 : 分 :秒)。
1 在 indicators 数组中删除 ID_INDICATOR_SCRL ,并添加新元素
ID_INDICATOR_SHIFT 和 ID_INDICATOR_CLOCK : static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SHIFT,
ID_INDICATOR_CLOCK,
};
2 选择菜单命令 View->Resource Symbols ,在资源符号对话框中
为 ID_INDICATOR_SHIFT 和 ID_INDICATOR_CLOCK 定义 ID 值。
3 在资源 String Table 中为新增的 ID 添加字符串:
4 更新时间窗格
实现该功能首先要利用系统定时器产生变化的当前时间,并
将产生的时间转换成格式为“时 : 分 :秒” 的字符串;然后通过调用
CStatusBar::SetPaneText 函数将时间字符串显示在第五窗格中。
SetPaneText 的原型如下:
BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText,
BOOL bUpdate = TRUE );
标识值 字符串
ID_INDICATOR_SHIFT
ID_INDICATOR_CLOCK
SHIFT
00:00:00
⑴ 动态地获取当前时间可以通过定时读取系统当前时间实现,
为此可以使用 ClassWizard 为 CMainFrame 增加系统定时器窗口消
息 WM_TIMER 的映射项、 响应函数 OnTimer 的声明和定义。
⑵ 在 OnTimer 函数中添加操作代码:void CMainFrame::OnTimer( UINT nIDEvent )
{
CTime time;
time = CTime::GetCurrentTime();
CString s = time.Format( "%H:%M:%S" );
m_wndStatusBar.SetPaneText(
m_wndStatusBar.CommandToIndex(ID_INDICATOR_CLOCK), s);
CFrameWnd::OnTimer( nIDEvent );
}
⑶ 设置系统定时器
为了使系统能按照要求的时间间隔发出时间中断,需要在
CMainFame 构造函数或 OnCreate 中调用 CWnd::SetTimer 设置能指
定时间间隔发出中断的定时器。 SetTimer 的原型: UINT SetTimer( UINT nIDEvent, UINT nElapse,
void (CALLBACK EXPORT* lpfnTimer )
( HWND, UINT, UINT, DWORD) );
参数:
nIDEvent 定时事件标识,一个程序中可以有多个定时器。
nElapse 定时器的定时间隔。
lpfnTimer 指向自定义定时器设置函数, NULL 表示使用系统
定时器设置函数。
返回:
如果设置成功,返回定时事件标识;否则返回 0 。
本例中在 CMainFrame::OnCreate 中添加了如下设置代码:
m_nTimer = SetTimer(1, 1000, NULL); // 定时间隔为 1000ms
其中 m_nTimer 是在 CMainFrame 中添加的数据成员:
class CMainFrame public CFrameWnd
{
…
private:
UINT m_nTimer; // 用于保存定时器标识。
}
⑷ 已经设置成功的定时器在其所属窗口关闭撤消时,应该调用
CWnd::KillTimer 同时撤消,为此使用 ClassWizard 为 CMainFram
e 增
加主框架窗口关闭消息 WM_CLOSE 的映射项、响应函数 OnCl
ose
的声明和定义。
⑸ 为 OnClose 函数添加操作代码: void CMainFrame::OnClose()
{
KillTimer(m_nTimer);
CFrameWnd::OnClose();
}
5 更新 <Shift> 按键状态对应的窗格 该功能是通过添加 ID_INDICATOR_SHIFT 的用户界面更新
消息的映射项、响应函数 OnUpdateShift 的声明和定义实现的。完成这些编程只能手工进行,而不能使用 ClassWizard 。⑴ 在 CMainFrame 中加入消息处理函数 OnUpdateShift 的原型:
//}}AFX_MSG
afx_msg void OnUpdateShift(CCmdUI* pCmdUI);
DECLARE_MESSAGE_MAP()
⑵ 在 CMainFrame 类的消息映射表中加入映射条目://}}AFX_MSG_MAP
ON_UPDATE_COMMAND_UI( ID_INDICATOR_SHIFT,
OnUpdateShift)
END_MESSAGE_MAP()
⑶ 实现 CMainFrame::OnUpdateShift 函数定义: void CMainFrame::OnUpdateShift(CCmdUI* pCmdUI)
{
short flag = ::GetKeyState(VK_SHIFT);
if(flag < 0)
{
pCmdUI->Enable(TRUE);
}
else
{
pCmdUI->Enable(FALSE);
}
}
6 显示鼠标移动位置信息
由于当主框架窗口工作区的顶层窗口是视图窗口时,鼠标移
动发出的窗口消息 WM_MOUSEMOVE 只能被视图窗口捕获。
因此,对该消息的响应处理应该在视图类定义。
⑴ 用 ClassWizard 为 CSbView 添加窗口消息 WM_MOUSEMO
VE
的响应,并为响应函数 OnMouseMove 添加如下操作代码:void CSbView::OnMouseMove( UINT nFlags, CPoint point )
{
CMainFrame *pm = ( CMainFrame* )AfxGetMainWnd();
CStatusBar *psb =
( CStatusBar* )pm->GetControlBar(AFX_IDW_STATUS_B
AR);
CString str;
if( psb )
{
str.Format( "X=%d Y=%d", point.x, point.y );
psb->SetPaneText( 0, str );
}
CView::OnMouseMove( nFlags, point );
}
⑵ 由于 OnMouseMove 中使用了 CMainFrame ,所以需要在视图类
CSbView 的实现文件添加包含文件:
#include “MainFrm.h”
7 编译运行 “ SbSb”
4.3 创建 DialogBar
DialogBar 具备工具栏和对话框两者的特性。可视为 DialogBar
创建一个具有工具栏特点的非模态的“对话框” ,其中可以包含
能够与其他 Windows 对象交互的控件;并且作为 CControlBar
的
派生类对象,还具有绘制边界、停靠和浮动等功能。
本例中,将介绍如何在一个应用程序中创建一个 DialogBar 。
创建名为 “ DbDb” 的 SDI 应用程序项目,视图基类为 CEditVie
w ,资
源语言为英文。在该项目需要添加的 DialogBar 中有三个控件:
·按钮,用于发出清除视图窗口中文本的命令;
· 组合框,用于从其列表中选取文本内容显示在视图窗口中;
· 静态文本框,作为组合框的提示。
1 创建 DialogBar 资源
⑴ 添加一个对话框 IDD_DIALOG1 。
⑵ 修改对话框 IDD_DIALOG1:
· 对话框 ID 修改为 IDD_MYDLGBAR 。
· 对话框 style 选 Child ( 子窗口 ) , Border 选为 None ( 无边框 ) 。
·删除该对话框中缺省控件,添加下列控件:控件类型 控件名称 控件标识 控件属性按钮静态文本框组合框
Clear
Color
列表字串:Red
Green
Blue
ID_CLEARTEXT
IDC_STATIC
IDC_COMBO1
默认默认去除 Sort 选项
2 将 DialogBar 添加到主框架类中
⑴ 在 CMainFrame 类中增加 CDialogBar 类对象保护成员:
protected:
…
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
CDialogBar m_dlgbar;
…
⑵ 在 CMainFrame::OnCreate 中增加下面的操作代码: int CMainFrame::OnCreate( LPCREATESTRUCT lpCreateStruct )
{ …
if( !m_dlgbar.Create( this, IDD_MYDLGBAR, CBRS_TOP|CBRS_
GRIPPER|CBRS_TOOLTIPS|CBRS_FLYBY,
IDD_MYDLGBAR ) )
{
TRACE0( "Failed to create dialog bar\n" );
return -1; // fail to create
}
m_dlgbar.EnableDocking( CBRS_ALIGN_ANY );
DockControlBar( &m_dlgbar );
return 0;
}
3 使用 ClassWizard 在 CDbView 类中添加消息响应函数
⑴ 按钮 ID_CLEARTEXT 的消息响应函数 OnCleartext
void CDbView::OnCleartext()
{
SetWindowText(“”); // 清除视图窗口
}
⑵ 组合框 IDC_COMBO1 的选项处理函数 OnSelchangeCombo1
void CDbView::OnSelchangeCombo1()
{
CMainFrame *pm = ( CMainFrame* )AfxGetMainWnd();
CComboBox *pcb = ( CComboBox* )( CDialogBar* )(
pm->GetControlBar( IDD_MYDLGBAR ) )->
GetDlgItem( IDC_COMBO1 );
int index = pcb->GetCurSel();
CString str;
pcb->GetLBText( index, str );
this->SetWindowText( str );
}
4 对话框栏的隐藏 / 显示
要使自定义的对话框栏能像缺省工具栏那样具有隐藏和显示
操作功能须在 CMainFrame 的定义和实现文件中添加代码:
⑴ 在主菜单 IDR_MAINFRAME 的弹出菜单 View 中增加新的菜
单项 ID_VIEW_DIALOGBAR
⑵ 使用 ClassWizard 为 ID_VIEW_DIALOGBAR 定义命令响应函数
和用户界面更新函数: void CMainFrame::OnViewDialogbar()
{
ShowControlBar( &m_dlgbar,
m_dlgbar.IsWindowVisible() ? FALSE : TRUE, FALSE );
}
void CMainFrame::OnUpdateViewDialogbar( CCmdUI* pCmdUI )
{
pCmdUI->SetCheck( m_dlgbar.IsWindowVisible() );
}
5 编译运行 “ DbDb”
4.4 创建 ReBar
ReBar 又称为伸缩栏。它支持停靠,不仅可以移动,还可以
改变大小。伸缩栏可以包含多个段( Band )组成,每段可以由
手柄( Gripper Bar ),位图、文本标题和子窗口的组合而成,但
每段的子窗口只能有一个。
由于伸缩栏也是一种控制栏,它的创建与工具栏、状态栏等
有相似之处,影响伸缩栏的风格的样式通常包括三类:
·标准窗口风格,如 WM_CHILD 、 WM_VISIBLE 等;
· 通用控件风格,如 CCS_TOP 、 CCS_BOTTOM 、 CCS_VERT
等;
·伸缩栏控件风格,格式为 RBS_XXX ,常用的风格如下:
创建伸缩栏的步骤:
⑴ 创建伸缩栏各段的控件资源。
⑵ 定义一个 CReBar 类对象。
⑶ 调用 CReBar::Create 函数创建伸缩栏窗口。
⑷ 调用 CRebar::AddBar 或 CReBarCtrl::InsertBand 插入各段。
风格标识 风格含义
RBS_BANDBORDER
RBS_VERTICALGRIPPER
RBS_AUTOSIZE
伸缩栏控件绘制相邻段之间的线条
手柄在垂直伸缩栏中垂直显示
当控制栏改变大小时,伸缩栏改变段的布局
4.4.1 利用 AppWizard 生成的伸缩栏 只要在创建项目过程的 step 4 of 6 对话框中选定工具栏风格
为 Internet Explorer Rebar 而不选 Normal 就可以在所创建的应用程
序中生成一个伸缩栏,而不是一般工具栏。本例中的伸缩栏由
一个工具栏和一个 对话框栏组成。
4.4.2 手动生成伸缩栏1 创建一个名为 “ RbRb” 的 SDI 应用程序。使用英文为资源语言。
2 生成一个 DialogBar 资源
⑴ 添加一个对话框模板资源 IDD_DIALOG1 。
⑵ 修改对话框模板资源 IDD_DIALOG1 :
· 修改对话框 ID 为 IDD_MYDLGBAR ;
· 将对话框的 Style 选为 Child , Border 选为 None ;
· 在对话框中添加标识为 ID_BUTTON1 的按钮 BUTTON1 和标
识为 ID_BUTTON2 的按钮 BUTTON2 。
3 使用 ClassWizard 为对话框资源 IDD_MYDLGBAR 定义相应的类
CMyDlgBar 。由于在 VC 6.0 中 ClassWizard 不支持 CDialogBar ,所
以在创建 CMyDlgBar 的过程中先选基类为 CDialog ,待创建后,
再用 CDialogBar 替换 CMyDlgBar 的定义和实现代码中所有的
CDialog 。注意,构造函数的初始化表代码需要去掉。CMyDlgBar::CMyDlgBar( CWnd* pParent /*=NULL*/ )
{
//{{AFX_DATA_INIT(CMyDlgBar)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}
4 在 CMyDlgBar 中使用 ClassWizard 为按钮 BUTTON1 和 BU
TTON2
的 BN_CLICKED 消息添加响应函数:
5 将 DialogBar 添加到主框架窗口类中
⑴ 在 CMainFrame 的定义文件添加包含文件:
#include “MyDlgBar.h”
⑵ 在 CMainFrame 类中添加 CMyDlgBar 类对象的保护成员:
CMyDlgBar m_mydlgbar;
⑶ 在 CMainFrame::OnCreate 中增加创建 DialogBar 的代码:
…
if( !m_mydlgbar.Create( this, IDD_MYDLGBAR,
WS_CHILD|WS_VISIBLE, IDD_MYDLGBAR ) )
{
TRACE0( "Failed to create dialog bar\n" );
return -1;
}
…
6 创建其他段( Band )
创建将加入伸缩栏其他段( Band )的控件。本例要加入的另
一个控件就是已经自动创建的工具栏,所以无须增加代码,但
需要将设置工具栏停靠特性的语句注解掉或删除。
7 创建伸缩栏窗口
⑴ 为 CMainFrame 增加一个 CReBar 类对象的保护成员:
CReBar m_myReBar;
⑵ 在 CMainFrame::OnCreate 中增加创建伸缩栏窗口的代码:
…
if( !m_myReBar.Create( this ) )
{
TRACE0( "Failed to create rebar\n" );
return -1;
}
…
8 在 CMainFrame::OnCreate 增加将已经成功创建的工具栏和
DialogBar 添加到伸缩栏中的代码: …
m_myReBar.AddBar( &m_wndToolBar, "MAP", NULL,
RBBS_GRIPPERALWAYS|RBBS_FIXEDBM
P );
m_myReBar.AddBar( &m_mydlgbar, "MAP", NULL,
RBBS_GRIPPERALWAYS|RBBS_FIXEDBM
P );
…
9 编译运行“ Rb”
此时,发现 DialogBar 的按钮的灰色的,这是因为我们对按钮
的响应处理是定义在 CMyDlgBar 中的,而命令处理过程没有将消
息传给 DialogBar ,从而搜索 CMyDlgBar 的消息映射表所致。解决
的方法是重载 CMainFrame::OnCmdMsg ,将消息传给 CMyDlgBar
类对象: BOOL CMainFrame::OnCmdMsg( UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo )
{
if( m_mydlgbar.OnCmdMsg( nID, nCode, pExtra, pHandlerInfo ) )
return TRUE;
return CFrameWnd::OnCmdMsg( nID, nCode, pExtra, pHandlerInfo );
}
4.5 菜单界面操作 在本节中,通过一个示例程序介绍如何使用菜单类 CMenu
对
框架窗口中的菜单进行各种灵活的操作,以便达到改善界面的
目的。这些操作主要有:
· 不同菜单之间的切换;
·隐藏和显示菜单;
· 添加和删除菜单项;
· 为菜单项添加图象标签;
· 创建浮动的弹出式环境菜单。
首先通过 AppWizard 创建一个名为“ MenuMenu” 的 SDI 应用程序。在
创建过程中,除了在 Step 1 of 6 对话框中选择 Single Document
和
英语作为资源语言外,其余均为默认选项。根据示例程序要达
到的目的,在已经产生的程序框架中进行如下的编程工作:
4.5.1 添加资源1 在框架默认的主菜单 IDR_MAINFRAME 中增加子菜单 Menu
用
于发出实现对菜单的各项操作的命令:
菜单中的各项命令要完成的操作如下表:
菜单项命令名 菜单项标识 命令操作描述
Menu Disappear ID_MENU_DISAPPEAR 隐藏当前的框架主菜单
Menu Change ID_MENU_CHANGE 切换当前的框架主菜单
Default Menu ID_MENU_DEFAULT 恢复程序运行初始时的默认主菜单
Append Popup in Top ID_POPUP_APPEND_TOP 在主菜单顶层添加弹出式子菜单
Append Popup in Submenu
ID_POPUP_APPEND_SUB 在 Menu 子菜单中添加弹出式子菜单
Insert New Item ID_ITEM_INSERT 在菜单的特定位置插入新的菜单项
Remove Specified Item ID_ITEM_REMOVE 从菜单中删除已插入的特定菜单项
Add Bitmap to Item ID_BITMAP_ADD_ITEM 将位图作为标签加到特定菜单项
2 添加一个新的主菜单 IDR_NEWMENU 用于与程序框架默认的
主菜单 IDR_MAINFRAME 之间的切换。该菜单资源是在复制
IDR_MAINFRAME 的基础上删除其中的 Edit 子菜单,以示两个菜
单之间的区别。
3 增加一个新菜单 IDR_POPUP 作为动态加入到主菜单的弹出式
子菜单以及浮动的环境菜单。该菜单资源实际上是在复制主菜
单中的 Edit 子菜单的基础上,增加 Redo 和 Select All 菜单命令以
及 Select All 命令与其他命令之间的分隔线。
4 在工具条中增加一个工具按钮 ID_MENU_APPEAR 用于当前主
菜单被隐藏后,恢复主菜单操作,并且为此按钮定义一个键盘
加速键 Ctrl+M 。注意 , 该按钮没有对应的菜单项。
5 增加位图 IDB_PRINT 作为加入到特定菜单项 ID_FILE_PRINT
的
位图标签和插入的图形菜单项。
6 为准备插入的菜单项确定标识值: ID_ITEM_SEPARATOR 、
ID_NEW_ITEM1 和 ID_NEW_ITEM2 。
4.5.2 实现菜单的隐藏、显示和切换1 使用 ClassWizard 为菜单项标识值为: ID_MENU_DISAPPEAR 、ID_MENU_APPEAR 、 ID_MENU_CHANGE 和 ID_MENU_DEFAULT
的命令定义响应函数并建立消息映射: 在 CMainFrame 类的头文件 MainFrm.h 中增加了如下消息
响应函数和用户界面更新函数的原型声明: afx_msg void OnMenuDisappear();
afx_msg void OnUpdateMenuDisappear(CCmdUI* pCmdUI);
afx_msg void OnMenuAppear();
afx_msg void OnUpdateMenuAppear(CCmdUI* pCmdUI);
afx_msg void OnMenuChange();
afx_msg void OnMenuDefault();
在源文件 MainFrm.cpp 中加入了上述函数的空定义体并且在
CMainFrame 类的消息映射表中增加了下列消息映射条目:
ON_COMMAND( ID_MENU_DISAPPEAR, OnMenuDisappear )
ON_UPDATE_COMMAND_UI( ID_MENU_DISAPPEAR,
OnUpdateMenuDisappear )
ON_COMMAND( ID_MENU_APPEAR, OnMenuAppear )
ON_UPDATE_COMMAND_UI( ID_MENU_APPEAR,
OnUpdateMenuAppear )
ON_COMMAND( ID_MENU_CHANGE, OnMenuChange )
ON_COMMAND( ID_MENU_DEFAULT, OnMenuDefault )
2 在 CMainFrame 类中加入与这些操作有关的保护数据成员: HMENU m_hOldMenu; // 用于保存程序框架旧的主菜单句柄
HMENU m_hNewMenu; // 用于保存程序框架新的主菜单句柄
HMENU m_hCurMenu; // 用于保存程序框架当前的主菜单句柄
CMenu m_CurMenu; // 辅助菜单对象用于各类菜单操作
在 CMainFrame 类构造函数中对这些数据成员进行初始化: m_hCurMenu = NULL; // 使该菜单句柄无效
CMenu menu;
menu.LoadMenu( IDR_NEWMENU ); // 装载用于切换操作的菜单资源
m_hNewMenu = menu.Detach(); // 获得用于切换操作的菜单句柄
注意:在构造函数中添加语句: m_bAutoMenuEnable = FALSE;
这将使得命令用户界面更新消息失效,即使没有对应的命令消
息响应函数,菜单项也不会变灰(无效)。
3 完成各个响应函数的编码:
⑴ 隐藏主菜单:void CMainFrame::OnMenuDisappear()
{
m_hCurMenu = GetMenu()->Detach();
// 保存主菜单句柄,并使其与主菜单对象分离。
SetMenu( NULL ); // 设置框架的主菜单对象为空,使菜单消失。
}
void CMainFrame::OnUpdateMenuDisappear( CCmdUI* pCmdUI )
{
pCmdUI->Enable( m_wndToolBar.IsWindowVisible() );
// 当工具栏可见时,该命令有效。
}
⑵ 显示主菜单:void CMainFrame::OnMenuAppear()
{
ASSERT(m_hCurMenu);
m_CurMenu.Attach(m_hCurMenu);
// 将保存的主菜单句柄与当前菜单对象相关 SetMenu(&m_CurMenu); // 设置菜单对象,使菜单显
示。 m_hCurMenu = NULL; // 将保存主菜单句柄的变量
置空}
void CMainFrame::OnUpdateMenuAppear(CCmdUI* pCmdUI)
{
pCmdUI->Enable(m_hCurMenu != NULL);
// 当主菜单被隐藏的状态时,该命令有效。}
⑶ 切换主菜单:void CMainFrame::OnMenuChange()
{
CString str;
CMenu *curMenu = GetMenu();
ASSERT_VALID(curMenu);
curMenu->GetMenuString(1, str, MF_BYPOSITION);
// 获取主菜单顶层位置为 1 的子菜单项字串 if(str == “&Edit”) // 判断当前主菜单是否为缺省主菜单 {
ASSERT(m_hNewMenu);
m_hOldMenu = curMenu->Detach();
// 分离并保存缺省主菜单句柄
m_CurMenu.Attach(m_hNewMenu);
// 切换当前的主菜单为新主菜单
}
else
{
ASSERT(m_hOldMenu);
m_hNewMenu = curMenu->Detach();
// 分离并保存当前的主菜单句柄
m_CurMenu.Attach(m_hOldMenu);
// 切换当前的主菜单为缺省主菜单
}
SetMenu(&m_CurMenu);
// 设置框架窗口的当前菜单对象,显示切换后的的菜单。
}
⑷ 恢复框架默认的主菜单:void CMainFrame::OnMenuDefault()
{
CString str;
CMenu *curMenu = GetMenu();
ASSERT_VALID(curMenu);
curMenu->GetMenuString(1, str, MF_BYPOSITION);
if(str == "&Edit")
m_hOldMenu = curMenu->Detach();
else
m_hNewMenu = curMenu->Detach();
m_CurMenu.LoadMenu(IDR_MAINFRAME); // 加载缺省菜单资源
SetMenu(&m_CurMenu);
// 设置框架窗口的当前菜单对象,显示切换后的的菜单。}
4.5.3 在当前主菜单中进行各类添加和菜单项的删除 为菜单项 ID_POPUP_APPEND_TOP 、 ID_POPUP_APPED_S
UB 、
ID_ITEM_INSERT 和 ID_BITMAP_ADD_ITEM 添加响应函数并建立消
息映射:
在 CMainFrame 类的头文件 “ MainFrm.h” 中增加了如下消息响应函
数的原型声明:
afx_msg void OnPopupAppendTop();
afx_msg void OnPopupAppendSub();
afx_msg void OnItemInsert();
afx_msg void OnItemRemove();
afx_msg void OnBitmapAddItem();
在源文件 “ MainFrm.cpp” 中加入了上述函数的空定义体并且在
CMainFrame 类的消息映射表中增加了下列消息映射条目:
ON_COMMAND(ID_POPUP_APPEND_TOP, OnPopupAppendTop)
ON_COMMAND(ID_POPUP_APPEND_SUB, OnPopupAppendSub)
ON_COMMAND(ID_ITEM_INSERT, OnItemInsert)
ON_COMMAND(ID_ITEM_REMOVE, OnItemRemove)
ON_COMMAND(ID_BITMAP_ADD_ITEM, OnBitmapAddItem)
1 在 CMainFrame 类中加入与操作有关的保护数据成员:
CString m_NewItem; // 用于保存插入菜单项的标题字串
CBitmap m_Bitmap; // 用于保存作为菜单项标签和图形菜单项的
位图
CMenu m_PopupMenu; // 用于保存被插入的弹出式菜单对象
在 CMainFrame 类的构造函数中对这些数据成员进行初始化:
m_PopupMenu.LoadMenu(IDR_POPUP);
m_NewItem = "&New Menu Item";
m_Bitmap.LoadBitmap(IDB_PRINT);
2 完成各个响应函数的编码:
⑴ 在主菜单的顶层添加弹出式子菜单:
void CMainFrame::OnPopupAppendTop()
{
CMenu *curMenu;
HMENU hMenu;
CString str;
curMenu = GetMenu();
ASSERT_VALID( curMenu );
for( UINT i = 0; i < curMenu->GetMenuItemCount(); i++ )
{
curMenu->GetMenuString( i, str, MF_BYPOSITION );
if( str == "&Edit" )
return; // 如果当前主菜单顶层中已有菜单项 Edit 及子菜单,返回
}
hMenu = m_PopupMenu.GetSubMenu( 0 )->GetSafeHmenu( );
// 安全获取将被插入的弹出式子菜单句柄 ASSERT( hMenu );
m_PopupMenu.GetMenuString( 0, str, MF_BYPOSITION );
// 获取待插入子菜单的顶层位置为 0 的菜单项字串
curMenu->AppendMenu( MF_POPUP, ( int )hMenu, str );
// 在主菜单的顶层添加菜单项和相连接的子菜单
return;
}
⑵ 在 Menu 子菜单中添加弹出式子菜单: void CMainFrame::OnPopupAppendSub()
{ CMenu *curMenu, *subMenu;
HMENU hMenu;
INT nPos;
CString str;
curMenu = GetMenu();
ASSERT_VALID( curMenu );
for( nPos = 0; nPos < curMenu->GetMenuItemCount(); nPos++ )
{
curMenu->GetMenuString( nPos, str, MF_BYPOSITION );
if( str == "&Menu" ) // 找到Menu 子菜单 {
subMenu = curMenu->GetSubMenu( nPos );
// 获取主菜单顶层中位置为 nPos 的子菜单
for( UINT i = 0; i < subMenu->GetMenuItemCount(); i++ )
{
subMenu->GetMenuString( i, str, MF_BYPOSITION );
if( str == "&Edit" ) return;
// 如果当前子菜单中已有菜单项 Edit 及子菜单,返回 }
hMenu = m_PopupMenu.GetSubMenu( 0 )->GetSafeHmenu( );
// 安全获取将被插入的弹出式子菜单句柄
ASSERT( hMenu );
m_PopupMenu.GetMenuString( 0, str, MF_BYPOSITION );
// 获取待插入的弹出式子菜单的顶层位置为 0 的菜单项字串
subMenu->AppendMenu( MF_POPUP, ( int )hMenu, str );
// 在子菜单中添加菜单项和相连接的子菜单 return;
}
}
}
⑶ 在 Menu 子菜单中插入菜单项: void CMainFrame::OnItemInsert()
{ CMenu *curMenu = GetMenu();
ASSERT_VALID( curMenu );
curMenu->InsertMenu ( ID_ITEM_INSERT, MF_SEPARATOR,
ID_ITEM_SEPARATOR );
// 在 ID_ITEM_INSERT 菜单项之前插入一条分隔线 curMenu->InsertMenu( ID_ITEM_INSERT, MF_STRING,
ID_NEW_ITEM1, m_NewItem );
// 在 ID_ITEM_INSERT 菜单项之前插入一条新的菜单项
curMenu->InsertMenu( ID_ITEM_INSERT, MF_STRING,
ID_NEW_ITEM2, &m_Bitmap );
// 在 ID_ITEM_INSERT 菜单项之前插入一个图形菜单项
curMenu->InsertMenu( ID_ITEM_INSERT, MF_SEPARATOR,
ID_ITEM_SEPARATOR );
// 标识为 ID_ITEM_INSERT 菜单项之前插入一条分隔线
}
⑷ 从 Menu 子菜单中删除新插入的菜单项 void CMainFrame::OnItemRemove()
{
CMenu *curMenu = GetMenu();
ASSERT_VALID( curMenu );
curMenu->RemoveMenu(
ID_ITEM_SEPARATOR, MF_BYCOMMAND); // 删除分隔线
curMenu->RemoveMenu(ID_NEW_ITEM1, MF_BYCOMMAND);
//删除指定菜单项
curMenu->RemoveMenu(ID_NEW_ITEM2, MF_BYCOMMAND);
//删除指定菜单项
curMenu->RemoveMenu (
ID_ITEM_SEPARATOR, MF_BYCOMMAND); // 删除分隔线
}
⑸ 为已有的特定菜单项增加位图标签:
void CMainFrame::OnBitmapAddItem()
{
CMenu *curMenu = GetMenu();
ASSERT_VALID( curMenu );
curMenu->SetMenuItemBitmaps(
ID_FILE_PRINT, MF_BYCOMMAND, &m_Bitmap, &m_Bitm
ap );
}
4.5.4 添加浮动的弹出式环境菜单1 使用 ClassWizard 为 CMainFrame 类添加 WM_CONTEXTMENU
窗
口消息的消息映射和响应函数(用于当前框架窗口中显示浮动
弹出式环境菜单)。该添加操作导致在 CMainFrame 类的头文件
MainFrm.h 中增加了如下消息响应函数的原型:
afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
在 CMainFrame 类的实现源文件 MainFrm.cpp 中添加了上述函数的
空定义体并且在消息映射表中增加了下面的消息映射条目:
ON_WM_CONTEXTMENU()
2 完成响应函数的编码: void CMainFrame::OnContextMenu(CWnd* pWnd, CPoint point)
{
ASSERT(m_PopupMenu.GetSafeHmenu());
m_PopupMenu.GetSubMenu(0)->TrackPopupMenu(
TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, th
is);
// 鼠标右键触发,以鼠标位置左对齐浮动弹出式环境菜单
}
4.5.5 编译运行 “ Menu”
小结:
1 由 AppWizard 生成的 MFC 文档视图程序框架都包含了缺省的基
本菜单资源和与菜单资源相关的菜单对象。对菜单初始状态
的修改和添加只需要编辑菜单资源就可以实现,而不需要直
接使用菜单类对象,为编程人员提供了方便安全的手段。
2 菜单类 CMenu 所提供的功能为程序员提供了动态、灵活地使
用菜单、修改菜单的方法,能满足界面中对菜单的特殊要求。
3 使用 CMenu 对象时,通过该对象对一个菜单进行操作之前必
须与该指定的菜单句柄相关联,而在变换所关联的菜单之前
应该与原来的菜单句柄先分离。
4 在框架创建过程中动态修改菜单形态的操作应安排在菜单即
将被显示之前,如果完成这类操作的代码安排的不恰当,将
不起作用,甚至(在框架的菜单对象还没有被创建时)会引
起错误。在 SDI 程序框架中应安排在主框架窗口的成员函数
OnCreate 中,而在 MDI 程序框架中,应安排在多文档子框架
窗口的 ActivateFrame 成员函数中。
5 菜单对象一般是和主框架窗口相关联的,而不是和视图窗口
相联系的。因此,如果需要在视图窗口或其它类对象中对菜
单进行操作,必须获取主框架窗口类对象,再通过主框架窗
口类对象获取菜单类对象。
4.6 具有持续特性的框架窗口 本节我们介绍如何定义一个具有持续特性的 SDI 框架窗
口类
CPersistentFrame 。该类是由 MFC 基类 CFrameWnd 派生,它能够
记忆最近关闭的框架窗口的以下信息:
· 窗口尺寸
· 窗口位置
·最大化状态
·最小化状态
· 工具栏和状态栏是否处于允许状态及它们的位置
由 CPersistentFrame 的派生类创建的框架窗口,在应用程序结
束时,上述信息就会被保存在 Windows 注册表中或应用程序自
己的定制初始化文件( .ini )中。当应用程序重新被启动时,它
就会从注册表或初始化文件中读出以上信息,并将框架窗口的
上述信息恢复到最近一次应用程序退出时的状态。
4.6.1 CPersistentFrame 类描述在 CPersistentFrame 类的定义中包括了 4 个新成员函数:
· 构造函数
·析构函数
· 消息响应函数 OnDestroy ,用于 WM_DESTROY 消息的处理,
即在程序退出前将框架窗口的上述参数保存到磁盘上的系统
注册表或定制的初始化文件中;
· 重定义基类 CFrameWnd 的虚成员函数 ActivateFrame ,用于从
磁盘上的系统注册表或定制的初始化文件中读取上述框架窗
口参数,并以此设置框架窗口相应的显示状态。
为了实现读、写系统注册表或定制的初始化文件( .ini )中的
相应描述信息,需要为 CPersistentFrame 类添加下列静态常量数
据成员:static const CRect s_rectDefault; // 窗口位置参数
static const char s_profileHeading[]; // 信息索引头描述串
static const char s_profileRect[]; // 窗口位置参数描述串
static const char s_profileIcon[]; // 图标显示状态描述串
static const char s_profileMax[]; // 最大化状态描述串
static const char s_profileTool[]; // 工具栏状态描述字串
static const char s_profileStatus[]; // 状态栏状态描述串
和用于存放框架窗口首次被激活标志的非静态私有属性:BOOL m_bFirstTime;
上述静态数据成员分别被初始化为:const CRect CPersistentFrame::s_rectDefault( 10, 10, 500, 400 );
const char CPersistentFrame::s_profileHeading[] = "Window size";
const char CPersistentFrame::s_profileRect[] = "Rect";
const char CPersistentFrame::s_profileIcon[] = "icon";
const char CPersistentFrame::s_profileMax[] = "max";
const char CPersistentFrame::s_profileTool[] = "tool";
const char CPersistentFrame::s_profileStatus[] = "status";
各个成员函数的定义如下:CPersistentFrame::CPersistentFrame()
{
m_bFirstTime = TRUE; // 设置首次激活标志值为“真”
}
CPersistentFrame::~CPersistentFrame()
{
}
消息响应函数 OnDestroy 和虚成员函数 ActivateFrame 的操作代码
在 Visual C++ 6.0 和 Visual C++ .NET 中的操作代码略有差异。两种
版本顺序描述如下:
void CPersistentFrame::OnDestroy() // VC++ 6.0 版本
{
CString strText;
BOOL bIconic, bMaximized;
WINDOWPLACEMENT wndpl; // 存放窗口参数的数据结构变量
wndpl.length = sizeof( WINDOWPLACEMENT );
BOOL bRet = GetWindowPlacement( &wndpl ); // 获取窗口位置状态
if( wndpl.showCmd == SW_SHOWNORMAL ) // 窗口显示为一般状态
{
bIconic = FALSE;
bMaximized = FALSE;
}
else if ( wndpl.showCmd == SW_SHOWMAXIMIZED ) // 窗口最大化
{
bIconic = FALSE;
bMaximized = TRUE;
}
else if ( wndpl.showCmd == SW_SHOWMINIMIZED ) // 窗口最小化
{
bIconic = TRUE;
if ( wndpl.flags ) // 最小化之前的窗口状态bMaximized = TRUE;
else
bMaximized = FALSE;
}
// 将窗口参数及工具栏和状态栏的状态写入注册表或定制初始化文件 strText.Format( "%04d %04d %04d %04d",
wndpl.rcNormalPosition.left,
wndpl.rcNormalPosition.top,
wndpl.rcNormalPosition.right,
wndpl.rcNormalPosition.bottom );
AfxGetApp()->WriteProfileString(
s_profileHeading, s_profileRect, strText ); // 保存窗口位置和大小
AfxGetApp()->WriteProfileInt(
s_profileHeading, s_profileIcon, bIconic ); // 保存最小化状态信息
AfxGetApp()->WriteProfileInt(
s_profileHeading,s_profileMax, bMaximized ); // 保存最大化状态信息
SaveBarState( AfxGetApp()->m_pszProfileName ); // 保存控制栏状态 CFrameWnd::OnDestroy(); // 调用基类的 WM_DESTROY 响应函数}
void CPersistentFrame::OnDestroy() // VC++ .NET 版本
{
CString strText;
BOOL bIconic, bMaximized;
WINDOWPLACEMENT wndpl; // 存放窗口参数的数据结构变量
wndpl.length = sizeof( WINDOWPLACEMENT );
BOOL bRet = GetWindowPlacement( &wndpl ); // 获取窗口位置状态
if( wndpl.showCmd == SW_SHOWNORMAL ) // 窗口显示为一般状态
{
bIconic = FALSE;
bMaximized = FALSE;
}
else if ( wndpl.showCmd == SW_SHOWMAXIMIZED ) // 窗口最大化
{
bIconic = FALSE;
bMaximized = TRUE;
}
else if ( wndpl.showCmd == SW_SHOWMINIMIZED ) // 窗口最小化
{
bIconic = TRUE;
if ( wndpl.flags ) // 最小化之前的窗口状态
bMaximized = TRUE;
else
bMaximized = FALSE;
}
// 将窗口参数及工具栏和状态栏的状态写入注册表或定制初始化文件 strText.Format( _T( "%04d %04d %04d %04d" ),
wndpl.rcNormalPosition.left,
wndpl.rcNormalPosition.top,
wndpl.rcNormalPosition.right,
wndpl.rcNormalPosition.bottom );
AfxGetApp()->WriteProfileString( (LPCTSTR) s_profileHeading,
(LPCTSTR) s_profileRect, strText ); // 保存窗口位置和大小 AfxGetApp()->WriteProfileInt( (LPCTSTR) s_profileHeading,
(LPCTSTR) s_profileIcon, bIconic ); // 保存最小化状态信息 AfxGetApp()->WriteProfileInt( (LPCTSTR) s_profileHeading,
(LPCTSTR) s_profileMax, bMaximized ); // 保存最大化状态信息 SaveBarState( AfxGetApp()->m_pszProfileName ); // 保存控制栏状态 CFrameWnd::OnDestroy(); // 调用基类的 WM_DESTROY 响应函数}
void CPersistentFrame::ActivateFrame(int nCmdShow) // VC++ 6.0
版本
{
CString strText;
BOOL bIconic, bMaximized;
UINT flags;
WINDOWPLACEMENT wndpl;
CRect rect;
if ( m_bFirstTime ) // 是否首次激活
{
m_bFirstTime = FALSE; // 设置首次激活标志为“非”
strText = AfxGetApp()->GetProfileString( s_profileHeading, s_pr
ofileRect ); // 从注册表中读取窗口位置字串
if ( !strText.IsEmpty() )
{ // 将所读字串转换成窗口的位置参数
rect.left = atoi( ( const char* ) strText );
rect.top = atoi( ( const char* ) strText + 5 );
rect.right = atoi( ( const char* ) strText + 10 );
rect.bottom = atoi( ( const char* ) strText + 15 );
}
else
rect = s_rectDefault; // 使用窗口的缺省位置参数
bIconic = AfxGetApp()->GetProfileInt( s_profileHeading,
s_profileIcon, 0); // 从注册表或初始化文件中读取图标状态
bMaximized = AfxGetApp()->GetProfileInt( s_profileHeading,
s_profileMax, 0); // 从注册表或初始化文件中读取最大化状态
if ( bIconic ) // 显示状态是否为最小化
{
nCmdShow = SW_SHOWMINNOACTIVE;
// 设置窗口为最小化
if ( bMaximized ) // 恢复状态是否为最大化?
flags = WPF_RESTORETOMAXIMIZED;
// 设置最小化窗口的恢复状态为最大化
else // 恢复状态为非最大化?
flags = WPF_SETMINPOSITION;
// 设置最小化窗口的恢复状态为有确定位置的窗口
}
else // 显示状态为非最小化?
{
if ( bMaximized ) // 显示状态是否为最大化?
{
nCmdShow = SW_SHOWMAXIMIZED; // 显示状态为最大化
flags = WPF_RESTORETOMAXIMIZED; // 恢复状态为最大化
}
else // 显示状态为非最大化?
{
nCmdShow = SW_NORMAL; // 显示状态为通常状态
flags = WPF_SETMINPOSITION; // 恢复状态为确定位置的窗口
}
}
// 以下 6 句为将要显示的窗口的 Placement 结构参数赋值。 wndpl.length = sizeof( WINDOWPLACEMENT );
wndpl.showCmd = nCmdShow;
wndpl.flags = flags;
wndpl.ptMinPosition = CPoint(0, 0);
wndpl.ptMaxPosition = CPoint(-::GetSystemMetrics(SM_CXBORDE
R),
-::GetSystemMetrics( SM_CYBORDER ) );
wndpl.rcNormalPosition = rect;
LoadBarState( AfxGetApp()->m_pszProfileName );
// 从注册表或定制初始化文件中读取控制栏状态 BOOL bRet = SetWindowPlacement( &wndpl );
// 将已经赋值的窗口参数结构变量传递给将要显示的窗口
}
CFrameWnd::ActivateFrame( nCmdShow );
}
void CPersistentFrame::ActivateFrame(int nCmdShow) // VC++ .NET
版本
{
CString strText;
BOOL bIconic, bMaximized;
UINT flags;
WINDOWPLACEMENT wndpl;
CRect rect;
if ( m_bFirstTime ) // 是否首次激活
{
m_bFirstTime = FALSE; // 设置首次激活标志为“非”
strText = AfxGetApp()->GetProfileString((LPCTSTR)s_profileHeading,
(LPCTSTR) s_profileRect ); // 从注册表中读取窗口位置字串
if ( !strText.IsEmpty() )
{ // 将所读字串转换成窗口的位置参数
rect.left = _wtoi( (LPCTSTR) strText );
rect.top = _wtoi( (LPCTSTR) strText + 5 );
rect.right = _wtoi( (LPCTSTR) strText + 10 );
rect.bottom = _wtoi( (LPCTSTR) strText + 15 );
}
else
rect = s_rectDefault; // 使用窗口的缺省位置参数
bIconic = AfxGetApp()->GetProfileInt( (LPCTSTR)s_profileHeading,
(LPCTSTR) s_profileIcon, 0); // 从注册表中读取图标状态
bMaximized=AfxGetApp()->GetProfileInt(
(LPCTSTR) s_profileHeading, (LPCTSTR) s_profileMax, 0);
// 从注册表中读取最大化状态
if ( bIconic ) // 显示状态是否为最小化
{
nCmdShow = SW_SHOWMINNOACTIVE;
// 设置窗口为最小化
if ( bMaximized ) // 恢复状态是否为最大化?
flags = WPF_RESTORETOMAXIMIZED;
// 设置最小化窗口的恢复状态为最大化
else // 恢复状态为非最大化?
flags = WPF_SETMINPOSITION;
// 设置最小化窗口的恢复状态为有确定位置的窗口
}
else // 显示状态为非最小化?
{
if ( bMaximized ) // 显示状态是否为最大化?
{
nCmdShow = SW_SHOWMAXIMIZED; // 显示状态为最大化
flags = WPF_RESTORETOMAXIMIZED; // 恢复状态为最大化
}
else // 显示状态为非最大化?
{
nCmdShow = SW_NORMAL; // 显示状态为通常状态
flags = WPF_SETMINPOSITION; // 恢复状态为确定位置的窗口
}
}
// 以下 6 句为将要显示的窗口的 Placement 结构参数赋值。 wndpl.length = sizeof( WINDOWPLACEMENT );
wndpl.showCmd = nCmdShow;
wndpl.flags = flags;
wndpl.ptMinPosition = CPoint(0, 0);
wndpl.ptMaxPosition = CPoint(-::GetSystemMetrics(SM_CXBORDER),
-::GetSystemMetrics( SM_CYBORDER ) );
wndpl.rcNormalPosition = rect;
LoadBarState( AfxGetApp()->m_pszProfileName );
// 从注册表或定制初始化文件中读取控制栏状态 BOOL bRet = SetWindowPlacement( &wndpl );
// 将已经赋值的窗口参数结构变量传递给将要显示的窗口
}
CFrameWnd::ActivateFrame( nCmdShow );
}
描述窗口的位置、大小、状态的数据结构如下:typedef struct tagWINDOWPLACEMENT { /* wndpl */
UINT length; // 结构长度(字节数)
UINT flags; // 窗口恢复标志 ( WPF_RESTORETOMAXI
MIZED
// 或 WPF_SETMINPOSITION )
UINT showCmd; // 窗口的显示状态 ( 取值范围见函数 ShowWindow
// 的参数取值范围 )
POINT ptMinPosition; // 指定窗口最小化时的窗口左上角位置
POINT ptMaxPosition; // 指定窗口最大化时的窗口左上角位置
RECT rcNormalPosition; // 指定窗口通常恢复位置的坐标
} WINDOWPLACEMENT;
4.6.2 使用 CPersistentFrame 类1 将 CPersistentFrame 类添加到已创建项目中,方法分两种:
⑴ 如果 CPersistentFrame 类首次添加,则需要进行以下工作:
方法一
· 将 CPersistentFrame 的头文件和源文件复制到项目目录中;
· 通过菜单 Project->Add to Project->Files 选中 CPersistentFrame
的头文件和源文件,完成 CPersistentFrame 类加入项目。
方法二
· 通过菜单 Project->Add to Project->Files 将 CPersistentFrame
的
头文件和源文件从所在目录中选入到项目目录,从而完成将 CPersistentFrame 加入项目。
· 将 CPersistentFrame 作为 Gallery 组件保存,以备将来使用。
具体方法如下:
在 ClassView 中选中 CPersistentFrame 后,点击鼠标右键弹
出环境菜单,选择 Add to Gallery 菜单项即可。参见下图:
⑵ 如果 CPersistentFrame 类已经是 Gallery 组件,则只需在项目中
使用菜单命令 Project->Add to Project->Components and Controls 将
CPersistentFrame 类添加到项目中。具体方法是在 Components a
nd
Controls Gallery 对话框中选择 CPersistentFrame 类所在的目录:
注意:对话框中的目录名 Persistent Frame SDI 是经过更改的,以
便识别。在接下来显示的对话框中,选择要添加类的源代码文
件( .ogx )(本例中 Persistent Frame.ogx )后,确认插入:
2 将项目主框架 CMainFrame 的基类改为 CPersistentFrame 类: class CMainFrame : public CPersistentFrame
并在 MainFrm.h 的前部添加语句: #include “Persist.h” // 包含 PersistentFrame 类的定义文
件
3 在 CMainFrame 定义和实现中用 CPersistentFrame 替换所有原来
的基类 CFrameWnd 。4 重新建立类信息文件 .clw ,使其包含 CPersistentFrame 类。
具体方法如下: · 从项目目录中删除 .clw 文件,以便重新建立类信息文件; · 从菜单 View 中选择 ClassWizard 命令 ;
·按照随后显示的提示信息确认; 在出现 Select Source File 对话框后,确认所有的头文件和源
文件都列在 Files in Project 中,如下图:
选择点击 OK ,就可以产生新的类信息文件 .clw 了。
5 编译测试添加了 CPersistentFrame 类的应用程序。
6 检查系统注册表或定制初始化文件的有关信息。
4.6.3 示例程序测试 CPersistentFrame 类的作用。
1 使用 AppWizard 创建名为 “ PFrmSDIPFrmSDI” 的 SDI 应用程序项目,选
择英文为资源语言。
2 按上述步骤 1至 4 ,将 CPersistentFrame 类添加到项目中,并
将 CPFrmSDIApp::InitInstance 中的代码行:
SetRegistryKey (_T("Local AppWizard – Generated Application"));
替换为: SetRegistryKey (_T("Persistent Frame Demo")); // 设置注册关键字
3 编译测试 CPersistentFrame 类的功能。
4 检查系统注册表:
4.6.4 用于 MDI 应用程序的持续框架类 适用于 MDI 应用程序的具有持续特性的通用框架类与适
用于SDI 应用程序的具有持续特性的通用框架类 CPersistentFrame 有着类似之处,但也存在一些不同点,需要从 MDI 主框架和 MD
I
子框架两个方面考虑对这类通用类的设计:1 用于 MDI 框架的 CPersistentMainFrame 类应该是 CMDIFrame
Wnd
类的派生类。由于主框架对象只被创建一次,所以需要判别首次创建,并保存和恢复的框架窗口的特性: · 窗口尺寸 · 窗口位置 ·最大化状态 ·最小化状态 · 工具栏和状态栏是否处于允许状态及它们的位置
2 用于 MDI 子框架的 CPersistentChildFrame 类应是 CMDIChild
Wnd
类的派生类。子框架窗口首次创建时,子框架窗口的特性应该
从系统注册表或初始化文件中得到恢复。而如果当子框架窗口
创建时,在主框架窗口中已经有子框架窗口存在的话,子框架
窗口的特性应该由主框架窗口管理,与已经存在子框架窗口特
性一致。因此,判别是否从系统注册表或定制初始化文件中恢
复子框架窗口的特性的依据是主框架窗口中是否有子框架窗口
存在(调用 CMDIFrameWnd::MDIGetActive 可以获得主框架窗口中
被激活的子框架窗口的指针)。需要保存和恢复的框架窗口的
特性:
· 窗口尺寸
· 窗口位置
·最大化状态
·最小化状态
在本教材的程序代码附录( PersistentFrame\Mdi 子目录)中的
persist.h 和 persist.cpp 文件中给出了 CPersistentMainFrame 类和
CPersistentChildFrame 类的原型声明和定义。阅读这些代码和试
创建一个使用 CPersistentMainFrame 类和 CPersistentChildFrame
类
的 MDI 应用程序,并测试它的效果作为课后练习。