多語言中的自定義快捷鍵實現

一、問題的提出
一般的商業性軟件的基本功能要求之一是實現自定義快捷鍵來提高易用性,這樣用戶可以根據自己習慣的更改快捷鍵,在長期使用中逐漸熟練並加快軟件的使用速度,提高使用效率。
但這種快捷鍵要求與菜單提示相一致,並隨快捷鍵的更換而菜單相應的修改顯示,這有點麻煩,如果是多語言程序,牽扯的細節更多。本文就來論述相關的技術實現原理和相關實現細節。
 
二、界面預覽
爲了爲對下文有直觀的認識,首先預覽下程序的界面實現
 
三、解決思路
基本思路比較簡單:程序實現主體有一份快捷鍵資源,一般爲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中重複
 
 
感覺還是沒講清楚。
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章