控制面板是Windows 95的控制中心,通過它可以完成添加新硬件設備、改變桌面設置、配置網絡協 議等多項工作。在Windows 95中,控制面板通常有20 多個組件,我們只要用鼠標雙擊任一組件的圖標, 就會彈出一個對話框,對話框包含有設置一些系統參數的選項,這些參數的絕大多數都存放在Windows 95 的注冊表中。
控制面板的主程序是CONTROL.EXE,它在啟動時自動Winndows\System目錄下查找并調入文件擴展名為CPL的控制面板組件??刂泼姘褰M件是可以擴充的,一些軟件在安裝過程中會自動加入新的控制面板組件。按照Windows用戶界面設計原則的規定,凡是影響到系統的整體行為和界面風格的各項參數都應該通 過控制面板來設置,因此掌握控制面板組件的編程方法是很有必要的。
控制面板組件的工作原理控制面板的各個組件都是一些特殊的動態鏈接庫,只不過它們的擴展名不是DLL,而是CPL,即Control Panel的縮寫。
CONTROL.EXE啟動后會依次調入在系統目錄下查找到的CPL 庫。一般來說,一個CPL庫只負責管理某一方面的設置,對應著控制面板中的一個組件(即一個圖標),但也有少數CPL庫支持多個組件。
每個CPL庫必須輸出一個叫CPlApplet()的函數供CONTROL.EXE調用,CPlApplet()具有以下原型:
typedef LONG (APIENTRY *APPLET_PROC)(HWND hwndCpl, UINT msg,
LONG lParam1,LONG lParam2);
容易看出,CPlApplet()與普通窗口處理函數的形式很相似,事實上,控制面板正是以發送消息的方式與CPL庫進行通信。參數hwndCpl為控制面板的窗口句柄,msg為消息標識,lParam1和lParam2為附加的兩個參數,具體的意義視msg的值而定。
控制面板用LoadLibrary()函數把CPL庫調入內存以后,立刻向CPlApplet()發送一條CPL_INIT消息,指示CPL庫作初始化工作。
因為這是唯一允許返回失敗信息的消息,所以CPL庫此時應該分配運行過程中需要的所有內存和資源,如果因為內存不夠或者其它原因不能繼續,就返回零值,控制面板將不再處理這個CPL庫,并自動卸下它。
所有CPL庫初始化完畢后,控制面板再向每個CPL庫的CPlApplet()函數發送一條CPL_GETCOUNT消息,此時CPL庫返回它所支持的組件數。接下來,控制面板再針對每一個組件向CPlApplet()函數發送多條CPL_NEWINQUIRE消息,目的是取得每個組件對應的圖標、名稱和提示信息,CPL庫可以在處理這條消息時依次初始化各個組件的對話框。在Windows3.x中,控制面板發送的是CPL_INQUIRE消息,考慮到兼容性的問題,這條消息在Windows95中被保留下來了,但是基于WIN32的CPL庫只需處理新的CPL_NEWINQUIRE消息。
進行到這一步后,控制面板顯示出所有組件的圖標,并開始接受用戶的選擇。當用戶雙擊某個組件的圖標時,控制面板向該組件所在的CPL庫發送一條CPL_DBLCLK消息,并指明用戶選擇的是該CPL庫中的第幾個組件,CPL庫在接到這條消息后從INI文件或Windows95的注冊表中讀出要處理的系統參數的原始值,并啟動相應的對話框,允許用戶改變設置。當用戶在修改過程中按下應用(Apply)按鈕后,CPL庫保存新的參數并返回到控制面板中;如果用戶取消了所作修改,CPL庫只需返回即可。
控制面板在被關閉時會對每個組件發送一條CPL_STOP消息,接著對每個CPL庫發送一條CPL_EXIT消息,此時CPL庫釋放在CPL_INIT消息中分配的內存和資源。最后控制面板依次卸下各個CPL庫并退出。
上面敘述的就是控制面板組件的工作原理,其中各條消息的具體參數定義請參考WIN32SDK。
利用VC++編寫控制面板組件編寫控制面板組件實際上是編寫DLL,利用Visual C++這個強大的可視化編程工具可以很方便地完成這項工作。MFC基本類庫為我們封裝了DLL的基本框架,我們只需編寫處理消息的CPlApplet()函數和各個組件的對話框即可。遺憾的是,MFC類庫中沒有現成的關于控制面板組件的類,為了充分利用C++語言可繼承性的優點,本文后面的程序給出了一個控制面板組件的基類CControlPanel,它的成員函數提供了處理各種CPL消息的缺省代碼,我們只要從這個基類派生出新的子類,并為需要處理的消息重載相應的代碼,就可以迅速建立一個控制面板組件。
利用Visual C++中編寫控制面板組件的步驟如下:
?。?、調用AppWizard建立一個新的項目,將應用程序類型為設使用MFC的DLL,并把MFC類庫作為靜態庫連接,按下Finish按鈕,讓AppWizard自動生成框架文件。
?。?、把本文后面的CtrlPan.CPP加入到項目中,把CPlApplet添加到DEF文件的輸出名表中,然后選擇Build菜單的Settings,修改輸出文件的擴展名為CPL。
?。?、從CControlPanel中派生出新的子類,并重載部分消息代碼。多數情況下只需要重載處理CPL_NEWINQUIRE和CPL_DBLCLK消息的函數就行了,如下所示:
如 果 要 在 一 個CPL 庫 中 支 持 多 個
組 件, 那 么 至 少 還 要 重 載OnGetCount() 函 數。
編 寫 消 息 處 理 代 碼,OnInquire() 函 數 負
責 返 回 組 件 的 各 種 信 息, 可 參 考 基 類 中 該 函 數 的 實 現
代 碼,OnDblClk() 函 數 負 責 讀 取 和 保 存 各 個 參 數, 并 調 用
對 話 框 讓 用 戶 選 擇。
設 計 對 話 框, 用ClassWizard 生 成 對 話 框
的 處 理 代 碼, 并 修 改 這 些 代 碼 使 之 符 合 要 求。
源 代 碼 編 寫 完 畢 后, 編 譯 連 接, 把
生 成 的CPL 文 件 拷 則 到SYSTEM 目 錄 下, 運 行 控 制 面 板 進 行
調 試。
調 試 正 確 后, 重 新 建 立CPL 庫 的Release
版。
// CtrlPan.h: 類CControlPanel 的 聲 明
#ifndef _CTRLPAN_H_
#define _CTRLPAN_H_
#include 〈CPL.H〉
//VC 提 供 的 頭 文 件
class CControlPanel
{
public:
CControlPanel();
virtual ~CControlPanel();
// 可 重 載 的 消 息 處 理 函 數
virtual LONG OnDblClk(HWND hwndCPl, UINT uAppNum, LONG lData);
virtual LONG OnExit();
virtual LONG OnGetCount();
virtual LONG OnInit();
virtual LONG OnInquire(UINT uAppNum, NEWCPLINFO* pInfo);
virtual LONG OnSelect(UINT uAppNum, LONG lData);
virtual LONG OnStop(UINT uAppNum, LONG lData);
virtual LONG OnExit();
// CPL 庫 的 輸 出 函 數
static LONG APIENTRY CPlApplet(HWND hwndCPl, UINT uMsg,
LONG lParam1, LONG lParam2);
static CControlPanel* m_pThis;
};
#endif // _CTRLPAN_H_
// CtrlPan.cpp, 定 義 了 類CControlPanel 的 缺 省 處 理 函 數
#include "stdafx.h"
#include "ctrlpan.h"
CControlPanel* CControlPanel::m_pThis = NULL;
CControlPanel::CControlPanel()
{ m_pThis = this; }
CControlPanel::~CControlPanel()
{ }
// CPL 庫 的 輸 出 函 數
LONG APIENTRY CControlPanel::CPlApplet(HWND hwndCPl, UINT uMsg,
LONG lParam1, LONG lParam2)
{
CControlPanel* pCtrl = m_pThis;
ASSERT(pCtrl); // 檢 查pCtrl 的 有 效 性
switch (uMsg) {
case CPL_DBLCLK:
return pCtrl->OnDblClk(hwndCPl, lParam1, lParam2);
case CPL_EXIT:
return pCtrl->OnExit();
case CPL_GETCOUNT:
return pCtrl->OnGetCount();
case CPL_INIT:
return pCtrl->OnInit();
case CPL_NEWINQUIRE:
return pCtrl->OnInquire(lParam1, (NEWCPLINFO*)lParam2);
case CPL_INQUIRE:
return 0; // 基 于WIN32 的CPL 庫 不 處 理 這 條 消 息
case CPL_SELECT:
return pCtrl->OnSelect(lParam1, lParam2);
case CPL_STOP:
return pCtrl->OnStop(lParam1, lParam2);
case CPL_EXIT:
retrun pCtrl->OnExit();
default: break;
}
return 1;
}
// 缺 省 的 消 息 處 理 函 數
LONG CControlPanel::OnDblClk(HWND hwndCPl, UINT uAppNum, LONG lData)
{ return 0; }
LONG CControlPanel::OnExit()
{ return 0; }
LONG CControlPanel::OnGetCount()
{ return 1; } // 缺 省 為 一 個 組 件
LONG CControlPanel::OnInit()
{ return 1; }
LONG CControlPanel::OnInquire(UINT uAppNum, NEWCPLINFO* pInfo)
{
// 填 充NEWCPLINFO 結 構, 結 構 的 定 義 請 參 考VC 的 聯 機 幫 助
pInfo->dwSize = sizeof(NEWCPLINFO);
pInfo->dwFlags = 0;
pInfo->dwHelpContext = 0;
pInfo->lData = 0;
pInfo->hIcon = ::LoadIcon(AfxGetResourceHandle(), MAKEINTRESOURCE(1));
strcpy(pInfo->szName, "Applet");
strcpy(pInfo->szInfo, "Default Control Panel Applet");
strcpy(pInfo->szHelpFile, "");
return 0;
}
LONG CControlPanel::OnSelect(UINT uAppNum, LONG lData)
{ return 1; }
LONG CControlPanel::OnStop(UINT uAppNum, LONG lData)
{ return 1; }
LONG CControlPanel::OnExit()
{ return 1; }