多語言中的自定義快捷鍵實現
阿新 • • 發佈:2019-02-01
一、問題的提出
一般的商業性軟體的基本功能要求之一是實現自定義快捷鍵來提高易用性,這樣使用者可以根據自己習慣的更改快捷鍵,在長期使用中逐漸熟練並加快軟體的使用速度,提高使用效率。
但這種快捷鍵要求與選單提示相一致,並隨快捷鍵的更換而選單相應的修改顯示,這有點麻煩,如果是多語言程式,牽扯的細節更多。本文就來論述相關的技術實現原理和相關實現細節。
二、介面預覽
為了為對下文有直觀的認識,首先預覽下程式的介面實現
三、解決思路
基本思路比較簡單:程式實現主體有一份快捷鍵資源,一般為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中重複
感覺還是沒講清楚。