通用控件的6.0版(comctl32.dll)实现了一种新方法,用于子类化旧版本Windows上不可用的控件。实现子类化的最佳方法是什么,以便它可以在支持任一版本的公共控件库的系统上运行?
答案 0 :(得分:4)
首先,有一个article on MSDN讨论了在您应该熟悉的版本6.0和之前的子类控件中发生的更改。
维护向后兼容性的最佳方法是为子类控件创建包装函数。这将要求您动态加载comctl32.dll版本6上的子类控件所需的函数。这是一个如何完成的粗略示例。
typedef BOOL (WINAPI *LPFN_SETWINDOWSUBCLASS)(HWND, SUBCLASSPROC, UINT_PTR, DWORD_PTR);
typedef LRESULT (WINAPI *LPFN_DEFSUBCLASSPROC)(HWND, UINT, WPARAM, LPARAM);
typedef BOOL (WINAPI *LPFN_REMOVEWINDOWSUBCLASS)(HWND, SUBCLASSPROC, UINT_PTR);
typedef BOOL (WINAPI *LPFN_INITCOMMONCONTROLSEX)(LPINITCOMMONCONTROLSEX);
typedef struct SubclassStruct {
WNDPROC Proc;
} SubclassStruct;
LPFN_SETWINDOWSUBCLASS SetWindowSubclassPtr = NULL;
LPFN_REMOVEWINDOWSUBCLASS RemoveWindowSubclassPtr = NULL;
LPFN_DEFSUBCLASSPROC DefSubclassProcPtr = NULL;
LPFN_INITCOMMONCONTROLSEX InitCommonControlsExPtr = NULL;
HMODULE ComCtlModule = NULL;
int Subclasser_Init(void)
{
INITCOMMONCONTROLSEX CommonCtrlEx = {0};
ComCtlModule = LoadLibrary("comctl32.dll");
if (ComCtlModule == NULL)
return FALSE;
SetWindowSubclassPtr = (LPFN_SETWINDOWSUBCLASS)GetProcAddress(ComCtlModule, "SetWindowSubclass");
RemoveWindowSubclassPtr = (LPFN_REMOVEWINDOWSUBCLASS)GetProcAddress(ComCtlModule, "RemoveWindowSubclass");
DefSubclassProcPtr = (LPFN_DEFSUBCLASSPROC)GetProcAddress(ComCtlModule, "DefSubclassProc");
InitCommonControlsExPtr = (LPFN_INITCOMMONCONTROLSEX)GetProcAddress(ComCtlModule, "InitCommonControlsEx");
if (InitCommonControlsExPtr != NULL)
{
CommonCtrlEx.dwSize = sizeof(CommonCtrlEx);
InitCommonControlsExPtr(&CommonCtrlEx);
}
return TRUE;
}
int Subclasser_Uninit(void)
{
if (ComCtlModule != NULL)
FreeLibrary(ComCtlModule);
return TRUE;
}
LRESULT CALLBACK Subclasser_SharedSubclassProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam, UINT_PTR SubclassId, DWORD_PTR RefData)
{
SubclassStruct *Subclass = (SubclassStruct *)SubclassId;
return CallWindowProc(Subclass->Proc, hWnd, Message, wParam, lParam);
}
int Subclasser_SetProc(HWND hWnd, WNDPROC Proc, WNDPROC *OriginalProc, void *Param)
{
SubclassStruct *Subclass = NULL;
int Result = TRUE;
SetLastError(0);
if (SetWindowLongPtr(hWnd, GWLP_USERDATA, (__int3264)(UINT_PTR)Param) == 0)
{
if (GetLastError() > 0)
return FALSE;
}
if (SetWindowSubclassPtr!= NULL)
{
Subclass = (SubclassStruct*)malloc(sizeof(SubclassStruct));
Subclass->Proc = Proc;
*OriginalProc = (WNDPROC)Subclass;
Result = SetWindowSubclassPtr(hWnd, Subclasser_SharedSubclassProc, (UINT_PTR)Subclass, NULL);
}
else
{
*OriginalProc = (WNDPROC)(void *)GetWindowLongPtr(hWnd, GWLP_WNDPROC);
SetLastError(0);
if (SetWindowLongPtr(hWnd, GWLP_WNDPROC, (__int3264)(intptr)Proc) == 0)
{
if (GetLastError() > 0)
Result = FALSE;
}
}
if (Result == FALSE)
return FALSE;
return TRUE;
}
int Subclasser_UnsetProc(HWND hWnd, WNDPROC Proc, WNDPROC *OriginalProc)
{
SubclassStruct *Subclass = NULL;
int Result = TRUE;
if (RemoveWindowSubclassPtr != NULL)
{
if (*OriginalProc != NULL)
{
Subclass = (SubclassStruct *)*OriginalProc;
Proc = Subclass->Proc;
}
Result = RemoveWindowSubclassPtr(hWnd, Subclasser_SharedSubclassProc, (UINT_PTR)Subclass);
free(Subclass);
}
else
{
SetLastError(0);
if (SetWindowLongPtr(hWnd, GWLP_WNDPROC, (__int3264)(UINT_PTR)*OriginalProc) == 0)
{
if (GetLastError() > 0)
Result = FALSE;
}
}
SetLastError(0);
if (SetWindowLongPtr(hWnd, GWLP_USERDATA, 0) == 0)
{
if (GetLastError() > 0)
Result = FALSE;
}
*OriginalProc = NULL;
if (Result == FALSE)
return FALSE;
return TRUE;
}
LRESULT Subclasser_DefProc(WNDPROC OriginalProc, HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
if (OriginalProc == NULL)
return DefWindowProc(hWnd, Message, wParam, lParam);
if (DefSubclassProcPtr != NULL)
return DefSubclassProcPtr(hWnd, Message, wParam, lParam);
return CallWindowProc(OriginalProc, hWnd, Message, wParam, lParam);
}
唯一的其他示例可以在OpenJDK中找到。它的一个缺点是它使用WindowProc作为子类ID,如果你在具有相同WindowProc函数的对话框上继承多个控件,它将崩溃。在上面的示例中,我们分配一个名为SubclassStruct的新内存结构,并将其地址作为子类ID传递,以保证您子类的控件的每个实例都具有唯一的子类ID。
如果在多个应用程序中使用子类化函数,则有些使用comctl32.dll< 6和一些使用comctl32.dll> = 6,你可以通过获取comctl32.dll的文件版本信息来检测加载了哪个版本的公共控件库。这可以通过使用GetModuleFileName
和GetFileVersionInfo
来完成。
另外,如果你在comctl32.dll 6.0的子类回调中使用SetWindowWord / GetWindowWord,例如在下面的Dobbs博士文章中 Writing Windows Custom Controls,那么当comctl32.dll<时,你需要有条件地使用这些代码块。 6,因为它们无法在版本6或更高版本上运行,并且会导致应用程序崩溃。