#include "alfpriv.h" #include /* ALF App and Window */ static void ALF_InitializeWindowPriv(HWND hwnd, ALFWindowPriv *priv, void *closure) { priv->vtbl = (ALFWindowVTable*)GetClassLongPtr(hwnd, 0); priv->app = (ALFAPP)GetClassLongPtr(hwnd, sizeof(void*)); priv->closure = closure; ALF_ListInit(&priv->widgets); priv->defid = (WORD)-1; } static void ALF_DestroyWindowPriv(ALFWindowPriv *priv) { if (priv->vtbl->postdestroy) priv->vtbl->postdestroy(priv->closure); ALF_FOR_LIST(ALFWidgetPriv, list, &priv->widgets, w) { HeapFree(GetProcessHeap(), 0, w); } ALF_ListInit(&priv->widgets); HeapFree(GetProcessHeap(), 0, priv->layout.columns); HeapFree(GetProcessHeap(), 0, priv->layout.rows); HeapFree(GetProcessHeap(), 0, priv); } int ALF_CentipointsToPxPriv(ALFWindowPriv *priv, int cptValue) { return MulDiv(cptValue, priv->fonts.dpi, 7200); } static void ALF_UpdateFontForWidget(ALFWindowPriv *priv, ALFWidgetPriv *widget) { if (widget->hwnd && (widget->flags & ALF_MESSAGEFONT) == ALF_MESSAGEFONT) { SendMessage(widget->hwnd, WM_SETFONT, (WPARAM)priv->fonts.hMessageFont, (LPARAM)NULL); // XXX: Invalidating should IMHO be the decision of the control, but at // least the commctl32 V5 static control doesn't do it. InvalidateRect(widget->hwnd, NULL, TRUE); } } void ALF_UpdateFonts(HWND win) { SendMessage(win, ALF_WM_UPDATEFONTS, 0, 0); } void ALF_UpdateFontsPriv(HWND win, ALFWindowPriv *priv) { priv->fonts.dpi = priv->app->compatFn->GetDpiForWindow(win); NONCLIENTMETRICS ncm; ZeroMemory(&ncm, sizeof(ncm)); ncm.cbSize = sizeof(ncm); if (priv->app->compatFn->SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0, priv->fonts.dpi)) { priv->fonts.lfMessageFont = ncm.lfMessageFont; } else { // FIXME! fallback to default font, 8pt MS Shell Dlg ZeroMemory(&priv->fonts.lfMessageFont, sizeof(priv->fonts.lfMessageFont)); priv->fonts.lfMessageFont.lfHeight = -MulDiv(8, priv->fonts.dpi, 72); lstrcpy(priv->fonts.lfMessageFont.lfFaceName, TEXT("MS Shell Dlg")); } if (priv->fonts.hMessageFont) { DeleteObject(priv->fonts.hMessageFont); } priv->fonts.hMessageFont = CreateFontIndirect(&priv->fonts.lfMessageFont); ALF_FOR_LIST(ALFWidgetPriv, list, &priv->widgets, i) { ALF_UpdateFontForWidget(priv, i); } if (priv->vtbl->updatefonts) { priv->vtbl->updatefonts(priv->closure, win, &priv->fonts); } } void ALF_RecalculateLayout(HWND hwnd) { SIZE dummy; SendMessage(hwnd, ALF_WM_QUERYSIZE, 0, (LPARAM)&dummy); SendMessage(hwnd, ALF_WM_APPLYLAYOUT, 0, 0); } static void ALF_CalculateSizes(ALFWindowPriv *win) { for (int i = 0; i < win->layout.nColumns; ++i) { ZeroMemory(&win->layout.columns[i], sizeof(win->layout.columns[i])); } for (int i = 0; i < win->layout.nRows; ++i) { ZeroMemory(&win->layout.rows[i], sizeof(win->layout.rows[i])); } ALF_FOR_LIST(ALFWidgetPriv, list, &win->widgets, c) { while ((int)c->x >= win->layout.nColumns) { // FIXME! overflow, use reallocarray(2) equivalent if (win->layout.nColumns == 0) { win->layout.nColumns = 1; win->layout.columns = (ALFRowOrColumn*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.nColumns*sizeof(win->layout.columns[0])); } else { win->layout.nColumns *= 2; win->layout.columns = (ALFRowOrColumn*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.columns, win->layout.nColumns*sizeof(win->layout.columns[0])); } } while ((int)c->y >= win->layout.nRows) { // FIXME! overflow, use reallocarray(2) equivalent if (win->layout.nRows == 0) { win->layout.nRows = 1; win->layout.rows = (ALFRowOrColumn*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.nRows*sizeof(win->layout.rows[0])); } else { win->layout.nRows *= 2; win->layout.rows = (ALFRowOrColumn*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.rows, win->layout.nRows*sizeof(win->layout.rows[0])); } } // TODO: ignore spanning cell SIZE qs = { ALF_CentipointsToPxPriv(win, c->cptWidth), ALF_CentipointsToPxPriv(win, c->cptHeight) }; if ((c->flags & ALF_QUERYSIZE) == ALF_QUERYSIZE) { SendMessage(c->hwnd, ALF_WM_QUERYSIZE, 0, (LPARAM)&qs); } qs.cx += ALF_CentipointsToPxPriv(win, c->cptMarginLeft); qs.cx += ALF_CentipointsToPxPriv(win, c->cptMarginRight); qs.cy += ALF_CentipointsToPxPriv(win, c->cptMarginTop); qs.cy += ALF_CentipointsToPxPriv(win, c->cptMarginBottom); if (qs.cx > win->layout.columns[c->x].minWidth) win->layout.columns[c->x].minWidth = qs.cx; if (qs.cy > win->layout.rows[c->y].minWidth) win->layout.rows[c->y].minWidth = qs.cy; // TODO: expand flag } // TODO: second pass for spanning cells // update min width bookkeeping win->layout.totalMinWidth = 0; win->layout.occupiedColumnCount = 0; for (int i = 0; i < win->layout.nColumns; ++i) { if (win->layout.columns[i].minWidth > 0) { win->layout.totalMinWidth += win->layout.columns[i].minWidth; win->layout.occupiedColumnCount++; // TODO: expand flag } } win->layout.totalMinHeight = 0; win->layout.occupiedRowCount = 0; for (int i = 0; i < win->layout.nRows; ++i) { if (win->layout.rows[i].minWidth > 0) { win->layout.totalMinHeight += win->layout.rows[i].minWidth; win->layout.occupiedRowCount++; // TODO: expand flag } } // TODO: split here into allocation function } static void ALF_ApplyLayout(HWND hwnd, ALFWindowPriv *win) { // allocate cell positions // distribute free space int extraWidth = 0; int extraHeight = 0; RECT client; if (GetClientRect(hwnd, &client)) { if (client.right - client.left > win->layout.totalMinWidth) extraWidth = client.right - client.left - win->layout.totalMinWidth; if (client.bottom - client.top > win->layout.totalMinHeight) extraHeight = client.bottom - client.top - win->layout.totalMinHeight; } int extraWidthPart = 0; int extraWidthError = 0; if (win->layout.occupiedColumnCount) { extraWidthPart = extraWidth / win->layout.occupiedColumnCount; extraWidthError = extraWidth - extraWidthPart * win->layout.occupiedColumnCount; } for (int i = 0; i < win->layout.nColumns; ++i) { win->layout.columns[i].allocatedWidth = win->layout.columns[i].minWidth; if (win->layout.columns[i].minWidth > 0) { win->layout.columns[i].allocatedWidth += extraWidthPart; if (extraWidthError) { win->layout.columns[i].allocatedWidth++; extraWidthError--; } } if (i == 0) { win->layout.columns[i].allocatedPosition = 0; } else { win->layout.columns[i].allocatedPosition = win->layout.columns[i-1].allocatedPosition + win->layout.columns[i-1].allocatedWidth; } } int extraHeightPart = 0; int extraHeightError = 0; if (win->layout.occupiedRowCount) { extraHeightPart = extraHeight / win->layout.occupiedRowCount; extraHeightError = extraHeight - extraHeightPart * win->layout.occupiedRowCount; } for (int i = 0; i < win->layout.nRows; ++i) { win->layout.rows[i].allocatedWidth = win->layout.rows[i].minWidth; if (win->layout.rows[i].minWidth > 0) { win->layout.rows[i].allocatedWidth += extraHeightPart; if (extraHeightError) { win->layout.rows[i].allocatedWidth++; extraHeightError--; } } if (i == 0) { win->layout.rows[i].allocatedPosition = 0; } else { win->layout.rows[i].allocatedPosition = win->layout.rows[i-1].allocatedPosition + win->layout.rows[i-1].allocatedWidth; } } HDWP hdwp = BeginDeferWindowPos(win->layout.occupiedColumnCount * win->layout.occupiedRowCount); ALF_FOR_LIST(ALFWidgetPriv, list, &win->widgets, c) { int marginleft = ALF_CentipointsToPxPriv(win, c->cptMarginLeft); int marginright = ALF_CentipointsToPxPriv(win, c->cptMarginRight); int margintop = ALF_CentipointsToPxPriv(win, c->cptMarginTop); int marginbottom = ALF_CentipointsToPxPriv(win, c->cptMarginBottom); RECT r = { 0,0,0,0 }; r.left = win->layout.columns[c->x].allocatedPosition + marginleft; r.right = r.left + win->layout.columns[c->x].allocatedWidth - marginleft - marginright; r.top = win->layout.rows[c->y].allocatedPosition + margintop; r.bottom = r.top + win->layout.rows[c->y].allocatedWidth - margintop - marginbottom; if (c->flags & ALF_APPLYSIZE) { hdwp = (HDWP)SendMessage(c->hwnd, ALF_WM_APPLYSIZE, (WPARAM)hdwp, (LPARAM)&r); } else { hdwp = DeferWindowPos(hdwp, c->hwnd, 0, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOACTIVATE | SWP_NOZORDER); } } EndDeferWindowPos(hdwp); } static void ALF_InternalAddWidget(HWND hwnd, ALFWindowPriv *priv, ALFAddWidgetParams *params) { ALFWidgetPriv *w = (ALFWidgetPriv*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWidgetPriv)); w->hwnd = params->hwnd; w->x = params->x; w->y = params->y; w->cptWidth = params->width; w->cptHeight = params->height; w->flags = params->flags; w->cptMarginTop = params->margins[0]; w->cptMarginRight = params->margins[1]; w->cptMarginBottom = params->margins[2]; w->cptMarginLeft = params->margins[3]; if (GetParent(w->hwnd) != hwnd) SetParent(w->hwnd, hwnd); // TODO: QUERYUISTATE on parent, then replicate in child. But wait, XP appears to do this automatically!?? ALF_ListInsert(priv->widgets.prev, &w->list); ALF_UpdateFontForWidget(priv, w); } static void ALF_ApplyFocus(HWND hwnd, ALFWindowPriv *priv, BOOL restoringFocus) { if (priv->hwndFocus) { if (!restoringFocus && SendMessage(priv->hwndFocus, WM_GETDLGCODE, 0, 0) & DLGC_HASSETSEL) SendMessage(priv->hwndFocus, EM_SETSEL, 0, -1); if (GetForegroundWindow() == hwnd) SetFocus(priv->hwndFocus); } else { priv->hwndFocus = GetNextDlgTabItem(hwnd, NULL, FALSE); if (priv->hwndFocus) ALF_ApplyFocus(hwnd, priv, FALSE); } } LRESULT ALF_DefWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ALFWindowPriv *priv = (ALFWindowPriv*)GetWindowLongPtr(hwnd, 0); if (msg == ALF_WM_QUERYSIZE) { ALF_CalculateSizes(priv); SIZE *ps = (SIZE*)lparam; ps->cx = priv->layout.totalMinWidth; ps->cy = priv->layout.totalMinHeight; return 0; } if (msg == ALF_WM_APPLYLAYOUT) { ALF_ApplyLayout(hwnd, priv); return 0; } if (msg == ALF_WM_GETMODALRESULT) { return (LRESULT)priv->modalResult; } if (msg == ALF_WM_SETMODALRESULT) { priv->modalResult = (int)wparam; return 0; } if (msg == ALF_WM_CENTIPOINTTOPX) { return (LRESULT)ALF_CentipointsToPxPriv(priv, (int)wparam); } if (msg == ALF_WM_ADDWIDGET) { ALF_InternalAddWidget(hwnd, priv, (ALFAddWidgetParams *)lparam); return 0; } if (msg == ALF_WM_SETFOCUS) { priv->hwndFocus = (HWND)lparam; ALF_ApplyFocus(hwnd, priv, FALSE); return 0; } if (msg == ALF_WM_GETAPPLICATION) { return (LRESULT)priv->app; } if (msg == ALF_WM_UPDATEFONTS) { ALF_UpdateFontsPriv(hwnd, priv); return 0; } if (msg == WM_ACTIVATE) { if (!HIWORD(wparam)) { // if !minimized if (LOWORD(wparam)) { ALF_ApplyFocus(hwnd, priv, TRUE); } else { priv->hwndFocus = GetFocus(); } } return 0; } if (msg == WM_SIZE) { ALF_ApplyLayout(hwnd, priv); return 0; } if (msg == WM_GETMINMAXINFO) { RECT tmp; ZeroMemory(&tmp, sizeof(tmp)); tmp.right = priv->layout.totalMinWidth; tmp.bottom = priv->layout.totalMinHeight; if (priv->app->compatFn->AdjustWindowRectExForDpi( &tmp, GetWindowLong(hwnd, GWL_STYLE), GetMenu(hwnd) != NULL, GetWindowLong(hwnd, GWL_EXSTYLE), priv->fonts.dpi)) { MINMAXINFO *i = (MINMAXINFO *)lparam; i->ptMinTrackSize.x = tmp.right - tmp.left; i->ptMinTrackSize.y = tmp.bottom - tmp.top; return 0; } } if (msg == WM_CREATE) { if (priv->vtbl->create) { priv->vtbl->create(priv->closure, hwnd); } BOOL alwaysUnderline = FALSE; SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &alwaysUnderline, 0); if (!alwaysUnderline) { SendMessage(hwnd, WM_UPDATEUISTATE, MAKEWPARAM(UIS_INITIALIZE, 0), 0); } ALF_UpdateFontsPriv(hwnd, priv); ALF_CalculateSizes(priv); ALF_ApplyLayout(hwnd, priv); } if (msg == WM_DESTROY && priv->vtbl->destroy) { priv->vtbl->destroy(priv->closure, hwnd); } if (msg == WM_CLOSE) { if (!priv->vtbl->close || !priv->vtbl->close(priv->closure, hwnd)) { priv->modalResult = 2; ShowWindow(hwnd, FALSE); } return TRUE; } if (msg == WM_NCDESTROY) { SetWindowLongPtr(hwnd, 0, 0); ALF_DestroyWindowPriv(priv); } if (msg == WM_DPICHANGED) { ALF_UpdateFontsPriv(hwnd, priv); ALF_CalculateSizes(priv); RECT *r = (RECT*)lparam; SetWindowPos(hwnd, NULL, r->left, r->top, r->right-r->left, r->bottom-r->top, SWP_NOACTIVATE|SWP_NOZORDER); } if (msg == WM_THEMECHANGED || msg == WM_SETTINGCHANGE) { ALF_UpdateFontsPriv(hwnd, priv); ALF_CalculateSizes(priv); ALF_ApplyLayout(hwnd, priv); } if (msg == DM_GETDEFID) { if (priv->defid == (WORD)-1) { return 0; } else { return MAKELONG(priv->defid, DC_HASDEFID); } } if (msg == DM_SETDEFID) { if (priv->defid != (WORD)-1) { HWND hwndOld = ALF_WidgetHwndById(hwnd, priv->defid); if (hwndOld && SendMessage(hwndOld, WM_GETDLGCODE, 0, 0) & DLGC_DEFPUSHBUTTON) { SendMessage(hwndOld, BM_SETSTYLE, BS_PUSHBUTTON, TRUE); } } priv->defid = (WORD)wparam; HWND hwndNew = ALF_WidgetHwndById(hwnd, priv->defid); if (hwndNew && SendMessage(hwndNew, WM_GETDLGCODE, 0, 0) & DLGC_UNDEFPUSHBUTTON) { SendMessage(hwndNew, BM_SETSTYLE, BS_DEFPUSHBUTTON, TRUE); } return TRUE; } return DefWindowProc(hwnd, msg, wparam, lparam); } void ALF_AddWidget(HWND win, UINT x, UINT y, HWND widget, UINT minWidth, UINT minHeight, DWORD flags) { ALFAddWidgetParams params; ZeroMemory(¶ms, sizeof(params)); params.hwnd = widget; params.x = x; params.y = y; params.width = minWidth; params.height = minHeight; params.flags = flags; ALF_AddWidgetEx(win, ¶ms); } void ALF_AddWidgetEx(HWND win, const ALFAddWidgetParams *p) { SendMessage(win, ALF_WM_ADDWIDGET, 0, (LPARAM)p); } static LRESULT CALLBACK ALF_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == WM_NCCREATE) { CREATESTRUCT *cs = (CREATESTRUCT*)(void*)lparam; ALFWindowPriv *priv = (ALFWindowPriv*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWindowPriv)); SetWindowLongPtr(hwnd, 0, (LONG_PTR)priv); ALF_InitializeWindowPriv(hwnd, priv, cs->lpCreateParams); } ALFWindowPriv *priv = (ALFWindowPriv*)(void*)GetWindowLongPtr(hwnd, 0); if (priv != NULL) { if (priv->vtbl->message) { return priv->vtbl->message(priv->closure, hwnd, msg, wparam, lparam); } else { return ALF_DefWindowProc(hwnd, msg, wparam, lparam); } } else { return DefWindowProc(hwnd, msg, wparam, lparam); } } ALFAPP ALF_CreateApplication(HINSTANCE hInstance) { ALFAPP app = (ALFAPP)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(struct ALFAppPriv)); app->hInstance = hInstance; INITCOMMONCONTROLSEX icc; ZeroMemory(&icc, sizeof(icc)); icc.dwSize = sizeof(icc); icc.dwICC = ICC_WIN95_CLASSES; InitCommonControlsEx(&icc); app->compatFn = ALF_CreateCompatFuncTable(); return app; } LPTSTR ALF_RegisterWindowClass(ALFAPP app, const ALFWindowClassParams *params) { WNDCLASS cls; ZeroMemory(&cls, sizeof(cls)); const TCHAR *classNamePtr = params->className; TCHAR classNameBuf[256]; if (!classNamePtr) { ZeroMemory(classNameBuf, sizeof(classNameBuf)); classNamePtr = classNameBuf; UUID uuid; UuidCreate(&uuid); #ifdef UNICODE unsigned short *uuidstr = NULL; #else unsigned char *uuidstr = NULL; #endif UuidToString(&uuid, &uuidstr); lstrcpy(classNameBuf, TEXT("ALFWindow.")); lstrcat(classNameBuf, (LPCTSTR)uuidstr); RpcStringFree(&uuidstr); } cls.style = params->classStyle; cls.hInstance = app->hInstance; cls.hCursor = LoadCursor(NULL, (LPTSTR)IDC_ARROW); cls.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); cls.lpszClassName = classNamePtr; cls.cbWndExtra = sizeof(void*); cls.cbClsExtra = sizeof(void*)*2; cls.lpfnWndProc = DefWindowProc; ATOM classatom = RegisterClass(&cls); if (!classatom) return NULL; ALFWindowVTable *pvtbl = (ALFWindowVTable*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWindowVTable)); *pvtbl = params->vtbl; // XXX: This could have been a HWND_MESSAGE window, but Win95 doesn't support these HWND tmp = CreateWindowEx(0, MAKEINTATOM(classatom), TEXT("dummy"), 0, 0, 0, 0, 0, NULL, 0, app->hInstance, 0); SetClassLongPtr(tmp, 0, (LONG_PTR)pvtbl); SetClassLongPtr(tmp, sizeof(void*), (LONG_PTR)app); SetClassLongPtr(tmp, GCLP_WNDPROC, (LONG_PTR)ALF_WindowProc); DestroyWindow(tmp); return MAKEINTATOM(classatom); } HWND ALF_InstantiateWindow(ALFAPP app, LPCTSTR className, HWND hwndParent, void *closure) { return CreateWindowEx(0, className, TEXT("Window"), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, 300, 100, //FIXME hwndParent, NULL, app->hInstance, closure); } ALFAPP ALF_ApplicationFromWindow(HWND hwnd) { return (ALFAPP)SendMessage(hwnd, ALF_WM_GETAPPLICATION, 0, 0); } int ALF_ShowModal(HWND win) { MSG msg; ALF_SetModalResult(win, 0); // TODO: disable parent window ShowWindow(win, SW_SHOW); while (GetMessage(&msg, NULL, 0, 0) > 0) { // TODO: call application message hooks // TODO: call preprocess message hook if (!IsDialogMessage(win, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } int mr = ALF_GetModalResult(win); if (mr) return mr; } return 0; } void ALF_SetModalResult(HWND win, int result) { SendMessage(win, ALF_WM_SETMODALRESULT, (WPARAM)result, 0); } int ALF_GetModalResult(HWND win) { return (int)SendMessage(win, ALF_WM_GETMODALRESULT, 0, 0); } int ALF_CentipointsToPixels(HWND win, int cptValue) { return (int)SendMessage(win, ALF_WM_CENTIPOINTTOPX, (WPARAM)cptValue, 0); } void ALF_ResizeWindow(HWND win, int cptWidth, int cptHeight) { int pxwidth = ALF_CentipointsToPixels(win, cptWidth); int pxheight = ALF_CentipointsToPixels(win, cptHeight); ALF_ResizeWindowPx(win, pxwidth, pxheight); } void ALF_ResizeWindowPx(HWND win, int pxwidth, int pxheight) { MINMAXINFO tmp = { { 0, 0 }, { pxwidth, pxheight }, { 0, 0 }, { pxwidth, pxheight }, { pxwidth, pxheight } }; SendMessage(win, WM_GETMINMAXINFO, 0, (LPARAM)&tmp); if (tmp.ptMinTrackSize.x > pxwidth) pxwidth = tmp.ptMinTrackSize.x; if (tmp.ptMinTrackSize.y > pxheight) pxheight = tmp.ptMinTrackSize.y; SetWindowPos(win, NULL, 0, 0, pxwidth, pxheight, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER); } struct ALF_WidgetHwndById_Closure { HWND result; WORD needle; }; static BOOL CALLBACK ALF_WidgetHwndById_EnumChildProc(HWND hwnd, LPARAM lParam) { struct ALF_WidgetHwndById_Closure *closure = (struct ALF_WidgetHwndById_Closure*)lParam; if ((WORD)GetWindowLongPtr(hwnd, GWLP_ID) == closure->needle) { closure->result = hwnd; return FALSE; } return TRUE; } HWND ALF_WidgetHwndById(HWND win, WORD id) { struct ALF_WidgetHwndById_Closure closure = { 0, id }; EnumChildWindows(win, ALF_WidgetHwndById_EnumChildProc, (LPARAM)&closure); return closure.result; }