***简 介***
本文首先简要介绍了一下Windows中的几个与加速键表有关的API函数及结构。然后对在WIN32位程序中实现加速键表进行了探讨,分别就API下的程序设计及MFC下的程序设计进行了叙述。
对于运行时可编辑的加速键表仅在MFC下进行了详细描述。包括其实现原理,并引导大家建立了一个用于编辑加速键的对话框,含详细的代码。关于在API下实现运行时的可编辑加速键表不再叙述,可参考MFC下的代码。
我们通常希望将编辑过的加速键表保存起来,以便下次运行程序时保持我们编辑后的风格。在本文的最后,介绍了如何将加速键表保存至文件中,并从文件中读取我们保存的加速键表。你若是有意将加速键表保存至注册表或其它什么地方,可参考其它的有关资料。本人建议保存至文件比较恰当。
本文介绍的所有方法及代码都是在 Windows98SE + Microsoft Visual C++ 6.0 中进行编制和调试的。
一、与加速键表有关的几个API函数和结构。
操作加速键表使用的几个API函数(关于这几个函数的详细说明请参考有关书籍):
HACCEL LoadAccelerators(HINSTANCE hInstance, LPCTSTR lpTableNAme);
LoadAccelerators函数从程序的资源中加载一个加速键表,加载成功后返回一个加速健表的句柄。其中:
hInstance 应用程序的实例句柄。
lpTableName 指向加速键表名称字符串的指针。
HACCEL CreateAcceleratorTable(LPACCEL lpaccl, int cEntries);
CreateAcceleratorTable函数根据一个ACCEL结构数组创建一个加速键表。该函数与LoadAccelerators不同的是:LoadAccelerators函数加载的加速键表在程序结束后系统会自动将该加速键表从内存中清除,但CreateAcceleratorTable函数创建的加速键表需要使用函数DestoryAceleratorTable函数进行清除。
lpaccl 一个指向ACCEL结构数组的指针。
cEntries 数组中元素的个数。
BOOL DestoryAcceleratorTable(HACCEL hAccel);
DestoryAcceleratorTable函数清除由CreateAcceleratorTable函数创建的加速键表,成功则返回TRUE。其中:
hAccel 需要清除的加速键表句柄。
int TranslateAccelerator(HWND hWd, HACCEL hAccTable, LPMSG lpMsg);
TranslateAccelerator函数负责翻译加速键。其中:
hWnd 窗口句柄,翻译后的消息将被发往该窗口
hAccTable 加速键表句柄。
lpMsg 指向MSG结构的指针。
ACCEL结构的定义:
typedef struct tagACCEL{
BYTE fVirt;
WORD key;
WORD cmd;
}ACCEL,*LPACCEL;
其中:
fVirt 加速键的标记。
key 键的代码。如fVirt成员包含FVIRTKEY标志,则key指一个虚键码,否则是一个ASCII码。
cmd 命令ID号,该参数将被放入WM_COMMAND或WM_SYSCOMMAND消息的wParam参数的低位字发至窗口。
二、在windows下如何使用加速键表。
在window下使用加速键表一般有两种方法:1,创建一个加速键资源,在程序中使用API函数LoadAccelerators来将加速键表加载入内存。并在消息循环中使用API函数TranslateAccelerator来翻译该加速键表。2、在程序中填充一个ACCEL数组。然后调用API函数CreateAcceleratorTable来创建加速表,翻译加速键同上,但不要忘记在退出程序前使用API函数DestoryAcceleratorTable来清除它。下面分别给出一个例子:
/*例1:使用LoadAccelerators。
假设你已经建立了一个加速键资源,ID为IDR_ACCEL。
假设你已经定义了初始化函数InitApplication(HINSTANCE hInstance,int nCmdShow),
该函数执行注册窗口类和创建窗口操作。
*/
#include <windows.h>
#include "rc/resource.h"
BOOL InitApplication(HINSTANCE hInstance,int nCmdShow);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HANDLE hAccelTable;
// 初始化应用程序,并生成主窗口.
if (!InitApplication(hInstance, nCmdShow))
{
return FALSE; // 初始化失败
}
//使用函数LoadAccelerators从程序资源中加载加速键表
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCEL));
// 取得并分发消息直到接收到 WM_QUIT 消息.
while (GetMessage(&msg, NULL, 0, 0))
{
//在分发消息前首先试着用加速键表进行翻译,如果是一个加速键则由
//TranslateAccelerator函数进行翻译,不再继续处理该消息。
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam; // Returns the value from PostQuitMessage
}
/*例2:使用CreateAcceleratorTable。
假设你已经定义了初始化函数InitApplication(HINSTANCE hInstance,int nCmdShow),
该函数执行注册窗口类和创建窗口操作。
*/
#include <windows.h>
#include "rc/resource.h"
#define ID_CMD_A 0x00000230
#define ID_CMD_B 0x00000231
#define ID_CMD_C 0x00000232
#define ID_CMD_D 0x00000233
#define ID_CMD_E 0x00000234
#define ID_CMD_F 0x00000235
#define ID_CMD_G 0x00000236
//定义了七个加速键,请在消息回调函数中处理这七个命令ID。
static ACCEL accel[]={
{FVIRTKEY|FCONTROL,VK_F5,ID_CMD_A},
{FVIRTKEY|FCONTROL,VK_F6,ID_CMD_B},
{FVIRTKEY|FCONTROL,VK_HOME,ID_CMD_C},
{FVIRTKEY|FCONTROL,VK_END,ID_CMD_D},
{FVIRTKEY|FCONTROL,"G",ID_CMD_E},
{FVIRTKEY|FCONTROL,VK_SPACE,ID_CMD_F},
{FVIRTKEY|FCONTROL,"K",ID_CMD_G},
};
BOOL InitApplication(HINSTANCE hInstance,int nCmdShow);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
HANDLE hAccelTable;
// 初始化应用程序,并生成主窗口.
if (!InitApplication(hInstance, nCmdShow))
{
return FALSE; // 初始化失败
}
//使用函数CreateAcceleratorTable从数组accel中加载加速键表
hAccelTable = CreateAcceleratorTable(accel, sizeof(accel)/sizeof(ACCEL));
// 取得并分发消息直到接收到 WM_QUIT 消息.
while (GetMessage(&msg, NULL, 0, 0))
{
//在分发消息前首先试着用加速键表进行翻译,如果是一个加速键则由
//TranslateAccelerator函数进行翻译,不再继续处理该消息。
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//删除加速键表
DestoryAcceleratorTable(hAccelTable);
return msg.wParam; // Returns the value from PostQuitMessage
}
在MFC程序设计中,有关于加速键表的操作已经被CFrameWnd类进行了封装。通常,我们的程序的主框架类CMainFrame从CFrameWnd类派生(SDI界面程序),或者从CMDIFrameWnd类派生(MDI界面程序),而CMDIFrameWnd类也是从CFrameWnd类派生的。所以,我们并不用去关心那个资源号为IDR_MAINFRAME的加速键表是如何加载的,如果你只是需要一个这样的静态的加速键表的话。
那么我们能不能使用自己的加速键表呢?答案是:可以的。
创建加速键表的方法同前。由于在MFC中,WinMain函数被隐藏,我们不能直接修改WinMain函数,所以,加速键表的创建将在CMainFrame的OnCreate函数中创建。
1、在CMainFrame类中添加一个HACCEL类型保护成员变量:m_hMyAccel。在构造函数中初始化为NULL。
2、在CMainFrame::OnCreate函数的 “return 0;”句前增加创建加速键表的代码。返回的加速键表句柄保存在m_hMyAccel中:
方法1:(IDR_MYACCEL为你定义的加速键表资源号)
m_hMyAccel=LoadAccelerators(AfxGetApp()->m_hInstance,MAKEINTRESOURCE(IDR_MYACCEL));
方法2:(accel同前面的例子一样在本文件的开头部分进行定义)
m_hMyAccel=CreateAcceleratorTable(accel,sizeof(accel)/sizeof(ACCEL));
3、如果是使用CreateAccelTable函数创建的,则重载虚拟函数DestoryWindow()。在该函数的“return CMDIFrameWnd::DestroyWindow();”前增加如下代码:
if(m_hMyAccel!=NULL){
DestroyAcceleratorTable(m_hMyAccel);
m_hMyAccel=NULL;
}
通过前面的三步,加速键表已经能正确地被创建和删除了,但是它还没有工作。下面就是要让我们刚才所创建的加速键表工作起来。
在API程序设计中大家已经知道了加速键是如何工作的。也就是在消息还没有分发出去之前先检查是不是一个加速键。我们通过API函数TranslateAccelerator来实现。在MFC中,消息机制已经被封装了,我们不能去修改消息循环。但是,框架在分发消息前会调用虚拟函数PerTranslateMessage,并且如果该函数返回TRUE,则不再处理该消息。这正是我们所需要的。
让我们再回到CMainFrame类,生成PerTranslateMessage函数的覆盖版本。修改函数体如下:
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if(m_hMyAccel&&TranslateAccelerator(m_hWnd, m_hMyAccel, pMsg))
return TRUE;
return CMDIFrameWnd::PreTranslateMessage(pMsg);
}
好了,现在我们的加速键已经能正常地工作了。以上方法用在其它窗口同样有效(如对话框中,你不妨在对话框中试试)。
三、可在运行时编辑的加速键表。
通过上面的叙述,你可能已经对可编辑的加速键表有了一定的轮廓。其实其实现思想很简单:只要对一个ACCEL数组进行修改,然后重新生成加速键表就行了。
为了区分命令,我们为每个命令取一个名字。在CMainFrame类定义的前面加上下面的一个结构定义:
typedef struct{
char cCmd[32];
ACCEL accel;
}ACCELITEM,*LPACCELITEM;
我们将使用该结构来保存加速键表的数据。在CMainFrame类中添加两个保护成员变量:
LPACCELITEM m_lpAccel;
DWORD m_dwAccelCount;
在构造函数中将m_lpAccel初始化为NULL,m_dwAccelCount初始化为0。在数组accel的定义下面增加一个字符串数组的静态变量的定义(用来指定命令的名称,请仿照下面自己定义,个数多少不限,字符串长度不要超过31个字符):
static char strCmd[][32]={
"Command One",
"Command Two",
"Command Three",
"Command Four",
"Command Five"
};
在CMainFrame类中添加一个保护成员函数LoadAccel(),该函数用来将加速键表装入,定义如下:
BOOL CMainFrame::LoadAccel()
{
ASSERT(m_hActAccel==NULL);
ASSERT(m_lpAccel==NULL);
m_dwAccelCount=sizeof(accel)/sizeof(ACCEL);
m_lpAccel=new ACCELITEM[m_dwAccelCount];
memset(m_lpAccel,0,sizeof(ACCELITEM)*m_dwAccelCount);
DWORD dwCmdStr=sizeof(strCmd)/sizeof(char[32]);
for(DWORD dw=0;dw<m_dwAccelCount;dw++){
m_lpAccel[dw].accel=accel[dw];
strcpy(m_lpAccel[dw].cCmd,dw<dwCmdStr?strCmd[dw]:"Command Unknow");
}
m_hActAccel=CreateAcceleratorTable(accel,m_dwAccelCount);
return TRUE;
}
删除OnCreate函数中原来创建加速键表的代码,改成对LoadAccel()的调用:
LoadAccel();
在CMainFrame的析构函数中增加以下内容:
if(m_lpAccel)
delete[] m_lpAccel;
为了能够对加速键数据进行编辑,我们在工程中添加一个对话框资源,ID为“IDD_ACCELEDIT”,在对话框上放置三个成组框(Group Box),左边一个标题为“命令”;中间一个标题为“组合键”;右边一个标题为“虚键值”。在左边成组框中放置一个列表框(List Box),ID为“IDC_LST_CMD”。在中间成组框中放置三个核选框(Check Box),上下排列。上面的核选框的ID为“IDC_CHK_ALT”,标题为“Alt”,并勾选“Group”属性;中间的核选框的ID为“IDC_CHK_SHIFT”,标题为“Shift”,取消“Group”属性;下面的核选框的ID为“IDC_CHK_CTRL”,标题为“Ctrl”,取消“Group”属性。在右边的成组框中放置一个列表框,ID为“IDC_LST_KEY”。调整各个控件的尺寸及位置至合适。设置控件的TAB跳转顺序,从左至右、从上到下。依次为:左成组框、IDC_LST_CMD列表框、中成组框、IDC_CHK_ALT核选框、IDC_CHK_SHIFT核选框、IDC_CHK_CTRL核选框、右成组框、IDC_LST_KEY列表框、OK按钮、CANCEL按钮。
打开类向导,为对话框新建一个类,类名为“CDlgEditAccel”。为两个列表框和三个核选框映射变量,如下:
控件ID:ID_LST_CMD;类型:Control/CListBox;名称:m_LstCmd;
控件ID:ID_LST_KEY;类型:Control/CListBox;名称:m_LstKey;
控件ID:ID_CHK_ALT;类型:Control/CButton;名称:m_ChkAlt;
控件ID:ID_CHK_SHIFT;类型:Control/CButton;名称:m_ChkShift;
控件ID:ID_CHK_CTRL;类型:Control/CButton;名称:m_ChkCtrl;
在类CDlgEditAccel的定义前加入下面的定义:
typedef struct tag_KeyName{
CString m_strName;
WORD m_wID;
tag_KeyName(CString str,WORD id){m_strName=str,m_wID=id;}
}KEYNAME,*LPKEYNAME;
typedef struct tag_ActAccel{
CString m_strCmd;
ACCEL m_Accel;
}ACTACCEL,*LPACTACCEL;
class CActAccelList
{
public:
CActAccelList();
~CActAccelList();
protected:
LPACTACCEL m_lpActAccel;
int m_iCount;
public:
ACTACCEL& operator[](int index);
BOOL SetSize(int iSize);
int GetSize();
};
在文件DlgEditAccel.cpp文件中定义类CActAccelList的成员函数,代码如下:
CActAccelList::CActAccelList()
{
m_lpActAccel=NULL;
m_iCount=0;
}
CActAccelList::~CActAccelList()
{
if(m_iCount>0&&m_lpActAccel)
delete[] m_lpActAccel;
}
int CActAccelList::GetSize()
{
return m_iCount;
}
ACTACCEL& CActAccelList::operator []( int index)
{
if(!(index>=0&&index<m_iCount))
AfxThrowMemoryException();
return m_lpActAccel[index];
}
BOOL CActAccelList::SetSize(int iSize)
{
if(iSize<0)
return FALSE;
if(m_iCount>0&&m_lpActAccel)
delete[] m_lpActAccel;
m_iCount=0;
m_lpActAccel=new ACTACCEL[iSize];
if(m_lpActAccel==NULL)
return FALSE;
m_iCount=iSize;
return TRUE;
}
在DlgAccelEdit.cpp文件头部定义全局变量--数组key:
static KEYNAME key[]={
KEYNAME("Left mouse button",VK_LBUTTON),
KEYNAME("Right mouse button",VK_RBUTTON),
KEYNAME("Control-break processing",VK_CANCEL),
KEYNAME("Middle mouse button",VK_MBUTTON),
KEYNAME("Back Space",VK_BACK),
KEYNAME("Tab",VK_TAB),
KEYNAME("Clear",VK_CLEAR),
KEYNAME("Enter",VK_RETURN),
KEYNAME("Shift",VK_SHIFT),
KEYNAME("Ctrl",VK_CONTROL),
KEYNAME("Alt",VK_MENU),
KEYNAME("Pause",VK_PAUSE),
KEYNAME("Caps Lock",VK_CAPITAL),
KEYNAME("Esc",VK_ESCAPE),
KEYNAME("Space",VK_SPACE),
KEYNAME("Page Up",VK_PRIOR),
KEYNAME("Page Down",VK_NEXT),
KEYNAME("End",VK_END),
KEYNAME("Home",VK_HOME),
KEYNAME("Left",VK_LEFT),
KEYNAME("Up",VK_UP),
KEYNAME("Right",VK_RIGHT),
KEYNAME("Down",VK_DOWN),
KEYNAME("Select",VK_SELECT),
KEYNAME("Excute",VK_EXECUTE),
KEYNAME("Print Screen",VK_SNAPSHOT),
KEYNAME("Insert",VK_INSERT),
KEYNAME("Delete",VK_DELETE),
KEYNAME("Help",VK_HELP),
KEYNAME("0",'0'),
KEYNAME("1",'1'),
KEYNAME("2",'2'),
KEYNAME("3",'3'),
KEYNAME("4",'4'),
KEYNAME("5",'5'),
KEYNAME("6",'6'),
KEYNAME("7",'7'),
KEYNAME("8",'8'),
KEYNAME("9",'9'),
KEYNAME("A",'A'),
KEYNAME("B",'B'),
KEYNAME("C",'C'),
KEYNAME("D",'D'),
KEYNAME("E",'E'),
KEYNAME("F",'F'),
KEYNAME("G",'G'),
KEYNAME("H",'H'),
KEYNAME("I",'I'),
KEYNAME("J",'J'),
KEYNAME("K",'K'),
KEYNAME("L",'L'),
KEYNAME("M",'M'),
KEYNAME("N",'N'),
KEYNAME("O",'O'),
KEYNAME("P",'P'),
KEYNAME("Q",'Q'),
KEYNAME("R",'R'),
KEYNAME("S",'S'),
KEYNAME("T",'T'),
KEYNAME("U",'U'),
KEYNAME("V",'V'),
KEYNAME("W",'W'),
KEYNAME("X",'X'),
KEYNAME("Y",'Y'),
KEYNAME("Z",'Z'),
KEYNAME("Left windows",VK_LWIN),
KEYNAME("Right windows",VK_RWIN),
KEYNAME("Applications",VK_APPS),
KEYNAME("Numeric keypad 0", VK_NUMPAD0),
KEYNAME("Numeric keypad 1", VK_NUMPAD1),
KEYNAME("Numeric keypad 2", VK_NUMPAD2),
KEYNAME("Numeric keypad 3", VK_NUMPAD3),
KEYNAME("Numeric keypad 4", VK_NUMPAD4),
KEYNAME("Numeric keypad 5", VK_NUMPAD5),
KEYNAME("Numeric keypad 6", VK_NUMPAD6),
KEYNAME("Numeric keypad 7", VK_NUMPAD7),
KEYNAME("Numeric keypad 8", VK_NUMPAD8),
KEYNAME("Numeric keypad 9", VK_NUMPAD9),
KEYNAME("Multiply",VK_MULTIPLY),
KEYNAME("Add",VK_ADD),
KEYNAME("Separator",VK_SEPARATOR),
KEYNAME("Subtract",VK_SUBTRACT),
KEYNAME("Decimal Point",VK_DECIMAL),
KEYNAME("Divide",VK_DIVIDE),
KEYNAME("F1",VK_F1),
KEYNAME("F2",VK_F2),
KEYNAME("F3",VK_F3),
KEYNAME("F4",VK_F4),
KEYNAME("F5",VK_F5),
KEYNAME("F6",VK_F6),
KEYNAME("F7",VK_F7),
KEYNAME("F8",VK_F8),
KEYNAME("F9",VK_F9),
KEYNAME("F10",VK_F10),
KEYNAME("F11",VK_F11),
KEYNAME("F12",VK_F12),
KEYNAME("F13",VK_F13),
KEYNAME("F14",VK_F14),
KEYNAME("F15",VK_F15),
KEYNAME("F16",VK_F16),
KEYNAME("F17",VK_F17),
KEYNAME("F18",VK_F18),
KEYNAME("F19",VK_F19),
KEYNAME("F20",VK_F20),
KEYNAME("F21",VK_F21),
KEYNAME("F22",VK_F22),
KEYNAME("F23",VK_F23),
KEYNAME("F24",VK_F24),
KEYNAME("Attn",VK_ATTN),
KEYNAME("CrSel",VK_CRSEL),
KEYNAME("ExSel",VK_EXSEL),
KEYNAME("Erase",VK_EREOF),
KEYNAME("Play",VK_PLAY),
KEYNAME("Zoom",VK_ZOOM),
KEYNAME("Reserved for future use",VK_NONAME ),
KEYNAME("PA1",VK_PA1),
KEYNAME("Clear(OEM)",VK_OEM_CLEAR ),
KEYNAME("file://",'//'),
KEYNAME("-",'-'),
KEYNAME("=",'='),
KEYNAME("[",'['),
KEYNAME("]",']'),
KEYNAME(";",';'),
KEYNAME("\'",'\''),
KEYNAME(",",','),
KEYNAME(".",'.'),
KEYNAME("/",'/'),
KEYNAME("`",'`')
};
在类CDlgAccelEdit中响应Windows消息:WM_INITDIALOD,代码如下:
BOOL CDlgEditAccel::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
SetWindowText("Edit Accelerator Table");
int iCount=m_AccelList.GetSize();
int i;
for(i=0;i<iCount;i++){
m_LstCmd.AddString(m_AccelList[i].m_strCmd);
}
for(i=0;i<sizeof(key)/sizeof(KEYNAME);i++)
{
m_LstKey.AddString(key[i].m_strName);
}
m_LstCmd.SetCurSel(0);
OnSelchangeLstCmd();
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
为类CDglAccelEdit增加一个保护成员函数:
void SaveChange(int index=-1);
在文件DlgAccelEdit中定义该函数,代码如下:
void CDlgEditAccel::SaveChange(int index)
{
if(index>=0||(index=m_LstCmd.GetCurSel())>=0)
{
if(m_LstKey.GetCurSel()<0){
AfxMessageBox("你必需选择一个键码!");
return;
}
BYTE btCmp=((m_ChkAlt.GetCheck()==1)?FALT:NULL)|
((m_ChkCtrl.GetCheck()==1)?FCONTROL:NULL)|
((m_ChkShift.GetCheck()==1)?FSHIFT:NULL)|FVIRTKEY;
WORD wKey=key[m_LstKey.GetCurSel()].m_wID;
m_AccelList[index].m_Accel.fVirt=btCmp;
m_AccelList[index].m_Accel.key=wKey;
return;
}
}
响应列表框ID_LST_CMD的通知消息“LBN_SELCHANGE”,函数代码如下:
void CDlgEditAccel::OnSelchangeLstCmd()
{
// TODO: Add your control notification handler code here
int iCmd=m_LstCmd.GetCurSel();
WORD wKey=m_AccelList[iCmd].m_Accel.key;
BYTE btCmp=m_AccelList[iCmd].m_Accel.fVirt;
m_ChkAlt.SetCheck(btCmp&FALT);
m_ChkCtrl.SetCheck(btCmp&FCONTROL);
m_ChkShift.SetCheck(btCmp&FSHIFT);
int iCount=sizeof(key)/sizeof(KEYNAME);
int id=-1;
for(int i=0;i<iCount;i++)
{
if(key[i].m_wID==wKey){
id=i;
break;
}
}
m_LstKey.SetCurSel(id);
}
响应列表框ID_LST_KEY的通知消息“LBN_SELCHANGE”,函数代码如下:
void CDlgEditAccel::OnSelchangeLstKey()
{
SaveChange();
}
响应核选框ID_CHK_ALT的通知消息“BN_CLICKED”,函数代码如下:
void CDlgEditAccel::OnChkAlt()
{
SaveChange();
}
响应核选框ID_CHK_SHIFT的通知消息“BN_CLICKED”,函数代码如下:
void CDlgEditAccel::OnChkShift()
{
SaveChange();
}
响应核选框ID_CHK_CTRL的通知消息“BN_CLICKED”,函数代码如下:
void CDlgEditAccel::OnChkCtrl()
{
SaveChange();
}
至此,用于编辑加速键的对话框已经完成。我们现在要做的就是在程序中打开对话框来编辑了。让我们回到CMainFrame类中。我们将在该类中响应一个命令来打开编辑对话框,对加速键表进行编辑。完成后点按OK回到主程序中,然后更新加速键表。这样后,我们刚刚编辑好的加速键表就开始起作用了。
首先,打开菜单。在“查看”项后增加一个“工具(&T)”下拉菜单,在下拉菜单中增加一个了菜单:“编辑加速键表(&A)...”,ID为“ID_TOOL_ACCELEDIT”。打开类向导,选择CMainFrame类,响应“ID_TOOL_ACCELEDIT”命令。编辑响应函数如下:
void CMainFrame::OnToolAcceledit()
{
// TODO: Add your command handler code here
CDlgEditAccel dlg;
DWORD i;
dlg.m_AccelList.SetSize(m_dwAccelCount);
for(i=0;i<m_dwAccelCount;i++){
dlg.m_AccelList[i].m_strCmd=m_lpAccel[i].cCmd;
dlg.m_AccelList[i].m_Accel=m_lpAccel[i].accel;
}
if(IDOK==dlg.DoModal()){
ACCEL* pAccel=new ACCEL[m_dwAccelCount];
for(DWORD dw=0;dw<m_dwAccelCount;dw++){
m_lpAccel[dw].accel=pAccel[dw]=dlg.m_AccelList[dw].m_Accel;
}
if(m_hActAccel!=NULL)
DestroyAcceleratorTable(m_hActAccel);
m_hActAccel=CreateAcceleratorTable(pAccel,m_dwAccelCount);
}
}
在该文件的头部包含类CDlgEditAccel的头文件:
#include "DlgEditAccel.h"
至此,可编辑的加速键表已经完成了。不妨试试看。
三、将加速键表保存至文件,并在程序运行时自动从文件中加载。
上面我们实现了可编辑的加速键表。但是,当程序退出后,我们编辑过的加速键数据就消失了,下次运行程序时还是和以前一样了。将加速键表保存起来以备下次使用,这是我们所希望的。下面讨论的方法是使用文件来保存我们的加速键表。
首先,在MainFrm.cpp文件的头部定义一个全局字符串,也就是用于保存加速键的文件名:
static char strAccelFileName[]="Accel.cus";
定义一个DWORD值作为文件头,我们通过该DWORD值来判断是否是一个有效格式的文件:
#define ACCEL_FILE_HEAD 0x00434341
其次,为CMainFrame类添加一个保护的成员函数:“BOOL SaveAccel()”,编辑其代码如下:
BOOL CMainFrame::SaveAccel()
{
char lpAppName[256];
char lpAppPath[256];
char* lpFilePart=NULL;
GetModuleFileName(AfxGetInstanceHandle(),lpAppName,255);
GetFullPathName(lpAppName,255,lpAppPath,&lpFilePart);
TRACE1("Application File Name:%s.\n",lpAppName);
strcpy(lpFilePart,strAccelFileName);
TRACE1("Accelerator File Name:%s.\n",lpAppPath);
try
{
DWORD dwHead=ACCEL_FILE_HEAD;
DWORD dwCount=m_dwAccelCount;
CFile fAccel(lpAppPath,CFile::modeCreate|CFile::modeWrite);
fAccel.SeekToBegin();
fAccel.Write(&dwHead,sizeof(DWORD));
fAccel.Write(&dwCount,sizeof(DWORD));
for(DWORD dw=0;dw<dwCount;dw++)
{
fAccel.Write(m_lpAccel+dw,sizeof(ACCELITEM));
}
return TRUE;
}
catch(CFileException* e)
{
char buf[256];
char buf2[512];
int iError;
iError=e->m_cause;
e->GetErrorMessage(buf,256);
sprintf(buf2,"将加速键表保存到文件 \"%s\" 中时发生错误!\n"
"错 误 号:%d\n"
"错误描述:%s\n"
"\0",
strAccelFileName,iError,buf);
AfxMessageBox(buf2,MB_OK|MB_ICONSTOP|MB_DEFBUTTON1);
return FALSE;
}
}
再次,修改LoadAccel()函数如下:
BOOL CMainFrame::LoadAccel()
{
char lpAppName[256];
char lpAppPath[256];
char* lpFilePart=NULL;
GetModuleFileName(AfxGetInstanceHandle(),lpAppName,255);
GetFullPathName(lpAppName,255,lpAppPath,&lpFilePart);
TRACE1("Application File Name:%s.\n",lpAppName);
strcpy(lpFilePart,strAccelFileName);
TRACE1("Accelerator File Name:%s.\n",lpAppPath);
try
{
DWORD dwHead;
DWORD dwCount;
CFile fAccel(lpAppPath,CFile::modeRead);
fAccel.SeekToBegin();
if(fAccel.Read(&dwHead,sizeof(DWORD))!=sizeof(DWORD))
AfxThrowFileException(CFileException::endOfFile,12,lpAppPath);
if(dwHead!=ACCEL_FILE_HEAD)
AfxThrowFileException(CFileException::invalidFile,13,lpAppPath);
if(fAccel.Read(&dwCount,sizeof(DWORD))!=sizeof(DWORD))
AfxThrowFileException(CFileException::endOfFile,12,lpAppPath);
ASSERT(m_lpAccel==NULL);
m_lpAccel=new ACCELITEM[dwCount];
memset(m_lpAccel,0,sizeof(ACCELITEM)*dwCount);
m_dwAccelCount=dwCount;
for(DWORD dw=0;dw<dwCount;dw++)
{
if(fAccel.Read(m_lpAccel+dw,sizeof(ACCELITEM))!=sizeof(ACCELITEM))
AfxThrowFileException(CFileException::endOfFile,12,lpAppPath);
}
ACCEL* pAccel=new ACCEL[dwCount];
for(dw=0;dw<dwCount;dw++){
pAccel[dw]=m_lpAccel[dw].accel;
}
m_hActAccel=CreateAcceleratorTable(pAccel,dwCount);
return TRUE;
}
catch(CFileException* e)
{
char buf[256];
char buf2[512];
int iError;
iError=e->m_cause;
e->GetErrorMessage(buf,256);
sprintf(buf2,"从文件 \"%s\" 中加载加速键表时发生错误!\n"
"错 误 号:%d\n"
"错误描述:%s\n"
"是否生成默认的加速键表?\n\0",
strAccelFileName,iError,buf);
if(IDYES==AfxMessageBox(buf2,MB_YESNO|MB_SYSTEMMODAL|MB_ICONSTOP|MB_DEFBUTTON1))
{
ASSERT(m_hActAccel==NULL);
ASSERT(m_lpAccel==NULL);
m_dwAccelCount=sizeof(accel)/sizeof(ACCEL);
m_lpAccel=new ACCELITEM[m_dwAccelCount];
memset(m_lpAccel,0,sizeof(ACCELITEM)*m_dwAccelCount);
DWORD dwCmdStr=sizeof(strCmd)/sizeof(char[30]);
for(DWORD dw=0;dw<m_dwAccelCount;dw++){
m_lpAccel[dw].accel=accel[dw];
strcpy(m_lpAccel[dw].cCmd,dw<dwCmdStr?strCmd[dw]:"Command Unknow");
}
m_hActAccel=CreateAcceleratorTable(accel,m_dwAccelCount);
SaveAccel();
return TRUE;
}
return FALSE;
}
}
最后,在DestroyWindow()函数头部增加对SaveAccel()函数的调用:
SaveAccel();
...
好了,你自己的加速键数据已经能保存在文件中了,并能从中正确加载。如果文件不存在或程序读取时发现错误则提醒你是否建立缺省的加速键表,如你确认的话则生成缺省的加速键表并立刻保存至文件中。