子類化和超類化(win32 SDK)
本文所提到的子類化和超類化中的“類”并不是C++中的“類”,而是windows32 SDK程式設計中的RegisterClass函數用到的類,比如一個視窗類,它是WNDCLASS或者是WNDCLASSEX這樣的一個結構。雖然它和C++中的“類”并不是一個概念,但是卻有許多相似之處,即它們都有“模闆”這樣的一個概念或者功能。這裡的子類化是對視窗功能的調整與擴充,而超類化是對類功能的調整與擴充。下面我們就用2個執行個體分别說明這兩個概念。
文本輸入框,我們可能都很熟悉,一般的文本輸入框可以輸入任何的字元,而當我們指定了ES_NUMBER風格之後,則該文本框隻能輸入數字,假設我們現在要求文本框隻能輸入16進制數,那麼該怎麼處理呢?如果我們重寫文本框控件的所有功能,顯然是吃力不讨好的事情,可是現有的文本框又不能滿足我們的要求,它的所有功能都被windows封裝起來了,沒法直接修改,這樣,一個好的方法就是我們繼承原來的文本框,但是在此基礎上進行小小的修改, 我們攔截windows發給文本框的WM_CHAR消息,然後判斷輸入的字元是不是'0'-'9','a'-'f'('A'-'F'),如果是,則我們将消息轉發給文本框的預設視窗處理過程,否則,我們将消息丢棄,這樣文本框就不能接收到除了16進制字元以外的字元消息了,即其它的字元被屏蔽了,但是其它的非WM_CHAR消息仍然丢給預設的視窗過程。
下面的程式,在16進制和10進制數之間進行互相的轉換。16進制文本編輯框是我們子類化的控件。代碼如下:
// resource.h
// 資源的ID值定義
#define IDD_MAIN 101
#define IDC_HEX 1001
#define IDC_DEC 1002
#define IDC_STATIC -1
// SubClass.rc
// 資源檔案
#include "resource.h"
#include "afxres.h"
// 對話框定義
// Dialog
//
IDD_MAIN DIALOG DISCARDABLE 0, 0, 130, 47
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Hex <> Dec"
FONT 10, "System"
BEGIN
LTEXT "Hex",IDC_STATIC,7,7,22,8
LTEXT "Dec",IDC_STATIC,7,29,22,8
EDITTEXT IDC_HEX,39,7,79,14,ES_AUTOHSCROLL
EDITTEXT IDC_DEC,39,26,79,14,ES_AUTOHSCROLL | ES_NUMBER
END
//SubClass.c
// 實作檔案
#include <windows.h>
#include "resource.h"
HWND hWinMain = NULL; // 主對話框句柄
DWORD dwOption = 0; // 标志位,用于控制WM_COMMAND消息的處理
WNDPROC lpOldProcEdit = NULL; // 16進制編輯框的預設視窗過程位址
char szFmtDec2Hex[] = "%08X";
char szFmtHex2Dec[] = "%u";
// 16進制編輯框允許輸入的字元,/x08表示的倒退鍵,用于删除一個字元
char szAllowedChar[] = "0123456789ABCDEFabcdef/x08";
// 新的16進制編輯框視窗處理過程,這裡根據需要隻攔截了WM_CHAR消息
// 其它的消息,仍然由原來的視窗處理過程來處理
LRESULT CALLBACK ProcEdit(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int i;
if ( uMsg == WM_CHAR )
{
for (i = 0; i < sizeof(szAllowedChar); i++ )
{
if ( szAllowedChar[i] == (char)wParam )
{ // 将小寫的'a'-'f'轉換為大寫
if ( wParam > '9' )
{
wParam &= ~0x20;
}
return CallWindowProc(lpOldProcEdit, hWnd, uMsg, wParam, lParam);
}
}
return TRUE;
}
return CallWindowProc(lpOldProcEdit, hWnd, uMsg, wParam, lParam);
}
// 将16進制數值轉換為10進制
void Hex2Dec()
{
char szBuffer[512];
unsigned long res = 0;
unsigned int nbase = 1;
int nLen;
int i;
unsigned int tmp;
GetDlgItemText(hWinMain, IDC_HEX, szBuffer, sizeof(szBuffer));
nLen = strlen(szBuffer);
nLen--;
for ( i = nLen; i >=0; i-- )
{
if ( szBuffer[i] >'9' )
tmp = szBuffer[i] - 'A' + 10;
else
tmp = szBuffer[i] - '0';
tmp *= nbase;
nbase <<= 4;
res += tmp;
}
wsprintf(szBuffer, szFmtHex2Dec, res);
SetDlgItemText(hWinMain, IDC_DEC, szBuffer);
}
// 将10進制數值轉換為16進制
void Dec2Hex()
{
char szBuffer[512];
unsigned int n = GetDlgItemInt(hWinMain, IDC_DEC, NULL, FALSE);
wsprintf(szBuffer, szFmtDec2Hex, n);
SetDlgItemText(hWinMain, IDC_HEX, szBuffer);
}
// 主對話框過程
LRESULT CALLBACK ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HWND hWndHex;
switch ( uMsg )
{
case WM_CLOSE:
EndDialog(hWnd, 0);
break;
case WM_INITDIALOG:
{
hWinMain = hWnd;
SendDlgItemMessage(hWnd, IDC_HEX, EM_LIMITTEXT, 8, 0);
SendDlgItemMessage(hWnd, IDC_DEC, EM_LIMITTEXT, 10, 0);
hWndHex = GetDlgItem(hWnd, IDC_HEX);
lpOldProcEdit = (WNDPROC)SetWindowLong(hWndHex, GWL_WNDPROC, (LONG)ProcEdit);
}
break;
case WM_COMMAND:
if ( !dwOption )
{
dwOption = 1;
if ( LOWORD(wParam) == IDC_HEX )
Hex2Dec();
else if ( LOWORD(wParam) == IDC_DEC )
Dec2Hex();
dwOption = 0;
}
break;
default:
return FALSE;
}
return TRUE;
}
int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd )
{
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)ProcDlgMain, (LPARAM)0);
return 0;
}
上面的程式在VC6++下調試通過。編譯方法是,在VC6++下,建立一個win32 application , 然後将上面3個檔案加入到工程,F7編譯即可。
上面的子類化隻是針對一個控件,可是當我們需要多個16進制輸入框的時候,該怎麼辦呢?如果一個一個的子類化,顯然是不合适的,那麼好的方法就是将該文本框這個“類”進行改寫, 讓它符合我們的需要,然後,所有的16進制輸入框都從這個“類”“繼承”而來。
下面的例子,用了5個16進制輸入框,每個輸入框都從我們的“類”“繼承”而來,擁有相同的功能,即隻能輸入16進制字元和倒退鍵,不能輸入其它的字元。具體實作代碼如下:
// resource.h
// 資源ID定義檔案
#define IDD_MAIN 101
// SuperClass.rc
// 資源定義檔案
#include "resource.h"
#include "afxres.h"
/
//
// Dialog
//
// 可以看出下面的對話框有5個自定義控件,而這個控件的“類”就是"HexEdit"
// "HexEdit"是我們自定義的一個"類"名,它繼承自“Edit”,它隻能輸入16進制
// 字元和倒退鍵,其它的功能跟一般的Edit文本輸入框沒有兩樣。
IDD_MAIN DIALOG DISCARDABLE 0, 0, 126, 113
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Hex Edit Box (Super Cls)"
FONT 10, "System"
BEGIN
CONTROL "", -1, "HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,7,9,104,14
CONTROL "", -1, "HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,7,49,104,14
CONTROL "", -1, "HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,7,29,104,14
CONTROL "", -1, "HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,7,69,104,14
CONTROL "", -1, "HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,7,89,104,14
END
// SuperClass.c
// 實作檔案
#include <windows.h>
#include "resource.h"
HINSTANCE hInst = NULL;
HWND hWinMain = NULL;
WNDPROC lpOldProcEdit = NULL;
char szAllowedChar[] = "0123456789abcdefgABCDEFG/x08";
char szEditClass[] = "Edit";
char szClass[] = "HexEdit";
//16進制文本框的新的視窗過程,隻處理WM_CHAR消息,其它的丢給預設的處理過程
LRESULT CALLBACK ProcEdit(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int i;
int nLen = strlen(szAllowedChar);
if ( uMsg == WM_CHAR )
{
for ( i = 0; i < nLen; i++ )
{
if ( szAllowedChar[i] == (char)wParam ) // wParam是字元的ASCII碼
{
// 如果字元大于‘9’,表示是'a'-'f' 或者是'A' - 'F'
// 無論大小寫,一律轉換為大寫
if ( wParam > '9' )
wParam &= ~0x20;
return CallWindowProc(lpOldProcEdit, hWnd, uMsg, wParam, lParam);
}
}
return TRUE;
}
return CallWindowProc(lpOldProcEdit, hWnd, uMsg, wParam, lParam);
}
// 超類化, 用GetClassInfoEx擷取原有的類“Edit”的所有資訊
// 然後用我們自定義的視窗處理過程取代原有的視窗過程
// 用我們自定義的類名“HexEdit”取代原有的類名“Edit”
// 然後用RegisterClassEx向windows注冊我們的新類
void SuperClass()
{
WNDCLASSEX stWC;
stWC.cbSize = sizeof(stWC);
GetClassInfoEx(NULL, szEditClass, &stWC);
lpOldProcEdit = stWC.lpfnWndProc;
stWC.lpfnWndProc = ProcEdit;
stWC.hInstance = hInst;
stWC.lpszClassName = szClass;
RegisterClassEx(&stWC);
}
LRESULT CALLBACK ProcDlgMain(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if ( uMsg == WM_CLOSE )
{
EndDialog(hWnd, 0);
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance, IN LPSTR lpCmdLine, IN int nShowCmd )
{
hInst = hInstance;
SuperClass();
DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)ProcDlgMain, (LPARAM)0);
return 0;
}
以上的編譯過程同上面的子類化程式。