多语言中的自定义快捷键实现

来源:互联网 发布:阿里云虚拟机怎么打开 编辑:程序博客网 时间:2024/06/16 11:10
一、问题的提出
一般的商业性软件的基本功能要求之一是实现自定义快捷键来提高易用性,这样用户可以根据自己习惯的更改快捷键,在长期使用中逐渐熟练并加快软件的使用速度,提高使用效率。
但这种快捷键要求与菜单提示相一致,并随快捷键的更换而菜单相应的修改显示,这有点麻烦,如果是多语言程序,牵扯的细节更多。本文就来论述相关的技术实现原理和相关实现细节。
 
二、界面预览
为了为对下文有直观的认识,首先预览下程序的界面实现
 
三、解决思路
基本思路比较简单:程序实现主体有一份快捷键资源,一般为Accelerator/IDR_MAINFRAME,在程序初始化中载入快捷键资源m_hAccelTable,并拷贝此资源到m_hAccelDefault,再从注册表中载入设置的快捷键键值,赋值给m_hAccelTable,所有的快捷键修改操作都是针对m_hAccelTable,用完再保存到注册表中。如果要重置所有快捷键,则载入m_hAccelDefault,。
 
对于快捷键资源的修改,因为与菜单资源互动,所以比较复杂一些。要定义一个快捷键资源控制类CAccelControl,这个类负责自定义快捷键从注册表中的载入载出。同步菜单和同步快捷键资源以及查找相关命令ID。
 
在文章后面的附注上有相关虚拟键与Modifier键值的解释。
 
四、具体流程实现
1)      初始化快捷键控制类CAccelControl
从注册表中取得相关键值,为其分配足够的尺寸,然后把键值内容复制到其成员变量ACCEL * m_pAccelTable句柄中,
m_pAccelControl = new CAccelControl;
if(!m_pAccelControl) {
     return -1;
}
m_pAccelControl->ReadProfile(_T("Settings"));
 
2)     初始化程序中的快捷键资源,完成两个成员变量的赋值,并同步菜单资源
if( m_hAccelTable != NULL ) {
      return FALSE;
}
 
m_hAccelTable = ::LoadAccelerators(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_RACCEL));
m_hAccelDefault = m_hAccelTable;
if(m_Control.GetCount())
{
      m_hAccelTable = *m_xprControl.m_pAccelControl;
      m_Control.UpdateMenuKeys(m_Menu.GetSafeHmenu());
}
3)     初始化程序中的快捷键资源,完成两个成员变量的赋值,并同步菜单资源
if( m_hAccelTable != NULL ) {
      return FALSE;
}
 
m_hAccelTable = ::LoadAccelerators(::AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_RACCEL));
m_hAccelDefault = m_hAccelTable;
if(m_Control.GetCount())
{
      m_hAccelTable = *m_xprControl.m_pAccelControl;
      m_Control.UpdateMenuKeys(m_Menu.GetSafeHmenu());
}
4)     同步菜单资源
这段代码封装在CAccelControl中实现。
因为菜单中的tooltips与快捷键要相匹配,并随快捷键的修改而动态修改,所以处理起来有些麻烦。
从主程序中传递来主程序的菜单句柄,快捷键资源句柄初始化类相关成员,通过递归所有菜单和其子菜单来处理’/t’,菜单项对应的快捷键ID,如果有就增加到菜单项上的字符串上,并通过ModifyMenu反映在菜单显示上。这段代码比较繁杂,所以只给出部分实现。
void CAccelControl::UpdateMenuKeys(HMENU hMenu)
{
      XHotkeyPage dlg;
      dlg.init((HMENU)1, m_hSysAccel, m_hSysAccel);
 
      // 递归所有菜单(子项)
      int nItems = GetMenuItemCount(hMenu);
      MENUITEMINFO mi;
      mi.cbSize = sizeof(MENUITEMINFO);
      mi.fMask = MIIM_ID | MIIM_SUBMENU;
      TCHAR buf[512];
      CString name;
      for( int i=0; i<nItems; i++ )
      {
           // 得到位置
           GetMenuItemInfo(hMenu, i, TRUE, &mi);
           if(mi.hSubMenu) {
                 UpdateMenuKeys(mi.hSubMenu);
           }
           .....
      }
}
5)     对话框界面显示
在对话框实现函数ShowOptionDialog中,初始化传递过来的菜单句柄,原始快捷键资源句柄,由注册表载入的快捷键句柄。
hotkeyPage.init(m_hMenu, m_hAccelTable, m_hAccelDefault);在这个函数里完成对话框中相关变量的初始化。在DoModal之后,重新取得键值,赋值并更新菜单
if( hotkeyPage.m_nAccel )
{
      if(m_pAccelControl->SetAccelTable(hotkeyPage.m_pNewAccels, hotkeyPage.m_nAccel))
      {
           // 使用新键值
           m_hAccelTable = *m_pAccelControl;
           m_pAccelControl->UpdateMenuKeys(m_hMenu);
      }
}
6)     程序实现流程
上面的流程比较乱,现在给出程序实现流程
1.             主程序CMainFrame负责程序的初始化,快捷键资源的载入,类的初始化和界面显示
2.             针对主界面的主控制类由CControl**来处理,负责处理控制代码,和如主对话框数据交换
3.             针对快捷键资源的载入出,同菜单资源的同步等由CAccelControl实现
4.             快捷键对话框界面在对话框类CHotKey**中实现
5.             代码如果再次优化,可把3并到2中
 
五、后续
1)  ACCEL转化为字符串
ACCEL转化为字符串要与FCONTROL/FALT/FSHIFT/FVIRTKEY比较,其中的FVIRTKEY还要判断是否是扩展键,并通过GetKeyNameText获得字符串。
2)  MAKELPARAM(m_pNewAccels[i].fVirt, m_pNewAccels[i].key) 组成的DWORD值中转换为CHotKeyCtrl可以显示的值
需要通过HOTKEYF_CONTROL/FCONTROL,FALT/HOTKEY_ALT,FSHIFT/HOTKEY_SHIFT,以及扩展键值的HOTKEYF_EXT转化。
3) 注意FNOINVERT
MSDN中描述:Specifies that no top-level menu item is highlighted when the accelerator is used. If this flag is not specified, a top-level menu item will be highlighted, if possible, when the accelerator is used.
如果从DWORD值分析所得的值没有与FNOINVERT |一下,所设的新键值则会与快捷键资源IDR_MAINFRAME中重复
 
 
感觉还是没讲清楚。