1. 程式人生 > >[轉]可在執行時編輯的加速鍵表

[轉]可在執行時編輯的加速鍵表

***簡 介***

  本文首先簡要介紹了一下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();
    ...

  好了,你自己的加速鍵資料已經能儲存在檔案中了,並能從中正確載入。如果檔案不存在或程式讀取時發現錯誤則提醒你是否建立預設的加速鍵表,如你確認的話則生成預設的加速鍵表並立刻儲存至檔案中。
  由於水平有限,其中難免有誤,歡迎批評指正、發表你的意見。本人不勝感激。

posted on 2009-01-12 17:22 大海 閱讀(569) 評論(0)  編輯 收藏 引用 所屬分類: VC++