[MFC]Shapes程序(2):菜单资源

时间:2022-11-21 05:01:18

1. 资源文件、资源脚本和资源编译器:

    1) 资源文件是指程序中用到的菜单、图标、位图和字符串等,这些资源文件一般在AppWizard的作用下自动生成在res目录下;

    2) 资源文件的后缀通常为.ico(图标)、.rc2(菜单、字符串等),都是二进制对象,程序运行时将被链接到程序的.exe文件,在执行时被显示在程序界面中;

    3) 编译资源脚本:起始这些资源文件是通过编程实现的,即先要编写一个资源脚本.rc文件,然后再用微软提供的资源编译器rc.exe将.rc文件编译成相应的资源文件最红再通过link.exe链接到程序的.exe文件中去的,和普通程序的开发过程一样;

    4) 一般.rc资源脚本和源文件放在一块儿,经过rc.exe编译生成的资源文件放在同一的res文件夹下;


2.  资源脚本的基本语法:

    1) 先看一个典型的资源脚本,它定义了一个菜单:

IDR_MAINFRAME MENU PRELOAD DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "$New\tCtrl+N",ID_FILE_NEW
MENUITEM "&Open...\tCtrl+O",ID_FILE_OPEN
MENUITEM "&Save\tCtrl+S",ID_FILE_SAVE
MENUITEM "Save &As...",ID_FILE_SAVE_AS
MENUITEM SEPARATOR
MENUITEM "Recent File",ID_FLE_MRU_FILE1,GRAYED
MENUITEM SEPARATOR
MENUITEM "E&xit",ID_APP_EXIT
END

POPUP "&Edit"
BEGIN
MENUITEM "&Undo\tCtrl+Z",ID_EDIT_UNDO
MENUITEM SEPARATOR
MENUITEM "Cu&t\tCtrl+X",ID_EDIT_CUT
MENUITEM "&Copy\tCtrl+C",ID_EDIT_COPY
MENUITEM "&Paste\tCtrl+V",ID_EDIT_PASTE
END

POPUP "&View"
BEGIN
MENUITEM "&Toolbar",ID_VIEW_TOOLBAR
MENUITEM "&Status Bar",ID_VIEW_STATUS_BAR
END

POPUP "&Help"
BEGIN
MENUITEM "&About MyApp...",ID_APP_ABOUT
END
END
    2) 其中所有用ID_前缀标识的宏都将出现在应用程序的编写中用到,作为资源对象的唯一标识存在;

    3) 最外层的BEGIN-END定义的是整个菜单资源,其中IDR_MAINFRAME就是用户给这个资源取的宏名,IDR即ID Resource的缩写,而关键字MENU表示该宏定义的是一个菜单资源,关键字PRELOAD和DISCARDABLE表示资源的属性:

PRELOAD:表示程序运行之前先将该资源装入内存以调高程序打开时的心理速率,提高用户体验;

DISCARDABLE:表示在内存不足时可以将该资源从内存中卸载(即删除,而不是交换到硬盘),当需要用到时再直接从程序的.exe文件中重新加载该资源,从而避免额外的对硬盘的访问以加速;

!这两个关键字是16位时代的产物,32位下不起作用;

    4) 内层的每个BEGIN-END定义的都是一个顶层菜单项,之所以叫”顶层“,是因为这些项目都直接显示在菜单栏中,而那些点击顶层菜单项弹出的子菜单中的项目才是真正的菜单项,首先这里先要搞清楚顶层菜单项和菜单项之间概念上的区别;

    5) POPUP关键字定义了一个可以弹出子菜单的顶层菜单项,而里面的每个MENUITEM关键字定义了子菜单中的各个菜单项;

!注意:凡是用MENUITEM定义的菜单项必须要具有一个ID来标识,因为MENUITEM定义的对象是要进行消息处理的,比如鼠标点击了某个MENUITEM,程序要求对该消息做出响应,那么首先你要如何标识点击该MENUITEM时产生的消息呢?这些MENUITEM都不是固定的,可以人为的增加和减少的,所以这样的消息不可能由MFC预定义,后面将学到,这些消息将用这些MENUITEM的ID(即后面的整型ID宏)来表示;

!顶层菜单项不需要定义ID,因为它的工作是由定义其的关键字决定,例如POPUP就是指弹出子菜单,关键一旦固定下来,那么点击它做出的相应也是固定不变的,所以用不着用户自己去响应,所以,只有那些有变数的(即行为是根据用户需求设定的才需要ID标识);

!菜单资源是必须具有ID的,因为应用程序加载它使用的函数需要根据资源ID来加载;


3. 菜单项文本:

    1) 菜单项文本就是指菜单上显示的文本,在资源脚本中使用双引号""来定义,里面有普通的文本(字母)也有一些特殊符号,具有特殊含义(比如转义符号等);

    2) &:该符号后面紧跟的字母定义了和Alt键一起使用的快捷键,但是Alt只在弹出顶层菜单项的子菜单时有作用,例如上面的脚本,Alt-F将先弹出File顶层菜单项的子菜单,然后再按X就能选中Exit菜单项并执行该菜单命令;

    3) Windows在显示菜单按钮的时候在&修饰的字母低下加下划线,可以是用户很明白的它是个快捷键;

    4) 如果在同一菜单下有多个菜单项被分配了同一个快捷键,则快捷方式将在这些菜单项之间循环,直到按下Enter键才会选中某个菜单项;

    5) 如果顶层菜单项并不弹出一个子菜单而是直接执行一个菜单命令,则正文文本末尾一定要加一个!,例如:

POPUP "&File"
[...]
POPUP "&Edit"
[...]
MENUITEM "E&xit!", ID_APP_EXIT

!可以看到MENUITEM可以定义在任何级别上,但是这种方式被大家公认为是糟糕的,因为用户已经习惯了顶层菜单项弹出子菜单的动作了!

    6) 省略号...:表示菜单项被选中后还需要进一步输入,比如上面的Save As就表示点击后还需要进一步输入(这里是文件路径以及文件名)才能执行接下来的动作,Windows要求,所有需要等待用户进一步输入的菜单项一律都要使用省略号!!!

    7) \t:就是C语言中的普通的转义字符,表示制表,在文本中仅仅就是空开一段距离,和普通C语言编程没什么区别,\t后面写的是该菜单项的加速键按法,这是Windows程序的通用做法;

!在菜单文本中往往需要标识一下该菜单项的加速键,注意!加速键和快捷键的区别,快捷键就是用键盘代替鼠标输入,而加速键就是比快捷键更快捷的按键法,所以叫加速,比如上面退出,快捷键就是Alt-F+X,需要三个键,但是可以定义加速键,比如Ctrl+Q,两个键就搞定了,明显比快捷键更快,加速键也是需要通过资源脚本定义的,这个后面会讲;

!当加速键脚本定义好后,就可以将加速键的按法写在菜单项文本中用以提示用户,可以使用户更快地掌握程序的用法;

!统一用\t为不是空格来隔开名称与加速键,因为菜单项文本中的字符是按比例隔开的,使用空格没意义;


4. 命令ID:

    1) 资源脚本中出现的ID号不仅仅用于表示资源对象,某些ID也是Windows应用程序消息传递的依据;

    2) 前面讲述的资源ID就用于加载时表示资源,而MENUITEM的ID严谨的说其实应该是命令ID,因为点击MENUITEM都会产生相应的菜单命令消息让用户处理,而为了标识这些命令,才使用它们的ID来区别,所有ID都应该是唯一的!!

    3) 各种ID的命名规则:规则不是固定的,但是良好的命名习惯可以时程序更健壮

资源ID:以IDR开头,表示ID Resource,即资源对象的最顶层单位

命令ID(即菜单项ID):以ID或者IDM打头,后者即ID Menu的缩写,中间跟所属的顶层菜单项文本名,最后再跟此菜单项本身的名称,比如

POPUP "&Edit"
BEGIN
MENUITEM "&Undo", ID_EDIT_UNDO

!所有的ID宏都是大写的;

    4) 命令ID的范围:

1~0x7FFF:用户可以随意使用,可以避免Win95遗留的错误,推荐这样使用;

0x8000~0xDFFF:MFC Technical Note #20建议用户自定义的范围

0xE000~0xEFFF:预留给MFC

0xF000及以上:留给Windows系统


5. 菜单项属性:

    1) 可以看到在File-Recent File菜单项的ID后还有一个关键字GRAYED,这个就是菜单项的属性了;

    2) 资源定义时各项用空格隔开,而菜单项定义时文本、ID、属性用逗号,隔开,而且顺序最好是文本、ID、属性,不要打乱;

    3) 菜单项的属性往往决定了菜单项的状态,GRAYED表示将该项灰化,用以提示用户该项不起作用,不能点击,还可以用CHECKED,它将菜单项开始处添加一个复选标记,用于表示该项的选中状态(选中还是没选中);


6. 资源头文件:

    1) 资源脚本中定义的资源就好比.cpp中定义的类实体,.cpp需要相应的.h声明,那么资源对象同样需要声明,因此也需要一个资源的.h头文件;

    2) AppWizard自动生成的项目中包含一个Resource.h,里面包含了所有资源ID的宏定义,比如#define IDR_MAINFRAME 128;

    3) 但是也有一些资源对象的ID不需要用户自己编写在Resource.h中,因为这些资源对象都是常用的,MFC为了方便就已经在预定义头文件Afxres.h中定义过了,比如ID_EDIT_CUT、ID_EDIT_COPY、ID_EDIT_PASTE等,这些常用的ID;

    4) 在正常的.cpp文件中include资源的.h头文件,就可以在.cpp文件中用资源对象的ID来访问资源了;


7. 加载并挂接菜单:

    1) 加载菜单是指将菜单资源装载到内存中去,而挂接菜单是将菜单挂到框架窗口上并显示出来;

    2) 有两种方法可以在加载的同时并挂接:

         i. 使用Frame的Create函数:

BOOL CFrameWnd::Create(
LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle = WS_OVERLAPPEDWINDOW,
const RECT& rect = rectDefault,
CWnd* pParentWnd = NULL,
LPCTSTR lpszMenuName = NULL,
DWORD dwExStyle = 0,
CCreateContext* pContext = NULL
);
!各参数和CWnd的Create函数差不多,其中lpszMenuName是菜单资源的字符串名称,如果之定义了整型ID,则需要使用MAKEINTRESOURCE宏将整型ID转换成MFC可以识别的资源的字符串ID,例如,MAKEINTRESOURCE(IDR_MAINFRAME);

!dwExStyle是扩展样式,和CWnd的CreateEx中第一个参数意义相同,这里先不用,因此填NULL即可;

         ii. 使用Frame的LoadFrame函数,该函数使用较广泛,可以在创建窗口资源的同时并加载挂接菜单资源,之所以LoadFrame更常用是因为该函数加载图标和其它资源:

virtual BOOL CFrameWnd::LoadFrame(
UINT nIDResource,
DWORD dwDefaultStyle = WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,
CWnd* pParentWnd = NULL,
CCreateContext* pContext = NULL
);
!nIDResource:即整型资源ID,该函数实现中使用了MAKEINTRESOURCE,因此传参时不必再使用宏;

!其中dwDefaultStyle是窗口默认的风格,其中FWS_ADDTOTITLE表示标题栏将会自动显示窗口中正在处理的文档名,其中FWS_是Frame Window Style的缩写;

    3) AppWizard自动生成的代码使用LoadFrame加载并挂接菜单,与其他各函数调用顺序是:Frame构造函数 -> LoadFrame -> PreCreateWindow -> OnCreate,顺序很奇葩,目前不理解,就先理解成LoadFrame主要用于加载菜单资源即可,一般该函数都用在App的.cpp文件中的InitInstance函数中,可以看一下Shapes.cpp文件中的该函数:

BOOL CShapesApp::InitInstance()
{
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.

// Change the registry key under which our settings are stored.
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization.
SetRegistryKey(_T("Local AppWizard-Generated Applications"));


// To create the main window, this code creates a new frame window
// object and then sets it as the application's main window object.

CMainFrame* pFrame = new CMainFrame; // 创建窗口框架
m_pMainWnd = pFrame;

// create and load the frame with its resources

// 在一般情况下尽量少用MFC预定义的东西做事情,因此这里用自己定义的pFrame来调用相关函数
pFrame->LoadFrame(IDR_MAINFRAME,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
NULL);

// The one and only window has been initialized, so show and update it.
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();

return TRUE;
}
!其中SW_SHOW是专属于ShowWindow函数的宏,前缀SW_是Show Window的缩写,常用的样式有以下几种:

SW_SHOW:显示窗口,并使用其原有的大小和位置

SW_HIDE:隐藏窗口最小化与任务栏,并且顶层窗口的位置让给了其它窗口

SW_MINIMIZE:窗口最小化,但依然是顶层窗口