// 14 june 2018 #include "uipriv_windows.hpp" #include "table.hpp" struct drawState { uiTable *t; uiTableModel *m; uiprivTableColumnParams *p; HDC dc; int iItem; int iSubItem; BOOL hasText; BOOL hasImage; BOOL selected; BOOL focused; RECT itemBounds; RECT itemIcon; RECT itemLabel; RECT subitemBounds; RECT subitemIcon; RECT subitemLabel; COLORREF bgColor; HBRUSH bgBrush; BOOL freeBgBrush; COLORREF textColor; HBRUSH textBrush; BOOL freeTextBrush; LRESULT bitmapMargin; int cxIcon; int cyIcon; RECT realTextRect; RECT focusRect; }; static HRESULT computeOtherRectsAndDrawBackgrounds(struct drawState *s) { RECT r; r = s->subitemLabel; if (!s->hasText && !s->hasImage) r = s->subitemBounds; else if (!s->hasImage && s->iSubItem != 0) // By default, this will include images; we're not drawing // images, so we will manually draw over the image area. // There's a second part to this; see below. r.left = s->subitemBounds.left; if (s->hasImage) if (FillRect(s->dc, &(s->subitemIcon), GetSysColorBrush(COLOR_WINDOW)) == 0) { logLastError(L"FillRect() icon"); return E_FAIL; } if (FillRect(s->dc, &r, s->bgBrush) == 0) { logLastError(L"FillRect()"); return E_FAIL; } UnionRect(&(s->focusRect), &(s->focusRect), &r); s->realTextRect = r; // TODO confirm whether this really happens on column 0 as well if (s->hasImage && s->iSubItem != 0) // Normally there's this many hard-coded logical units // of blank space, followed by the background, followed // by a bitmap margin's worth of space. This looks bad, // so we overrule that to start the background immediately // and the text after the hard-coded amount. s->realTextRect.left += 2; else if (s->iSubItem != 0) // In the case of subitem text without an image, we draw // text one bitmap margin away from the left edge. s->realTextRect.left += s->bitmapMargin; return S_OK; } static void centerImageRect(RECT *image, RECT *space) { LONG xoff, yoff; // first make sure both have the same upper-left xoff = image->left - space->left; yoff = image->top - space->top; image->left -= xoff; image->top -= yoff; image->right -= xoff; image->bottom -= yoff; // now center xoff = ((space->right - space->left) - (image->right - image->left)) / 2; yoff = ((space->bottom - space->top) - (image->bottom - image->top)) / 2; image->left += xoff; image->top += yoff; image->right += xoff; image->bottom += yoff; } static HRESULT drawImagePart(struct drawState *s) { uiTableData *data; IWICBitmap *wb; HBITMAP b; RECT r; UINT fStyle; HRESULT hr; if (s->p->imageModelColumn == -1) return S_OK; data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->imageModelColumn); wb = uiprivImageAppropriateForDC(uiTableDataImage(data), s->dc); uiFreeTableData(data); hr = uiprivWICToGDI(wb, s->dc, s->cxIcon, s->cyIcon, &b); if (hr != S_OK) return hr; // TODO rewrite this condition to make more sense; possibly swap the if and else blocks too // TODO proper cleanup if (ImageList_GetImageCount(s->t->imagelist) > 1) { if (ImageList_Replace(s->t->imagelist, 0, b, NULL) == 0) { logLastError(L"ImageList_Replace()"); return E_FAIL; } } else if (ImageList_Add(s->t->imagelist, b, NULL) == -1) { logLastError(L"ImageList_Add()"); return E_FAIL; } // TODO error check DeleteObject(b); r = s->subitemIcon; r.right = r.left + s->cxIcon; r.bottom = r.top + s->cyIcon; centerImageRect(&r, &(s->subitemIcon)); fStyle = ILD_NORMAL; if (s->selected) fStyle = ILD_SELECTED; if (ImageList_Draw(s->t->imagelist, 0, s->dc, r.left, r.top, fStyle) == 0) { logLastError(L"ImageList_Draw()"); return E_FAIL; } return S_OK; } // references for checkbox drawing: // - https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 // - https://blogs.msdn.microsoft.com/oldnewthing/20171201-00/?p=97505 static HRESULT drawUnthemedCheckbox(struct drawState *s, int checked, int enabled) { RECT r; UINT state; r = s->subitemIcon; // this is what the actual list view LVS_EX_CHECKBOXES code does to size the checkboxes // TODO reverify the initial size r.right = r.left + GetSystemMetrics(SM_CXSMICON); r.bottom = r.top + GetSystemMetrics(SM_CYSMICON); if (InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)) == 0) { logLastError(L"InflateRect()"); return E_FAIL; } r.right++; r.bottom++; centerImageRect(&r, &(s->subitemIcon)); state = DFCS_BUTTONCHECK | DFCS_FLAT; if (checked) state |= DFCS_CHECKED; if (!enabled) state |= DFCS_INACTIVE; if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) { logLastError(L"DrawFrameControl()"); return E_FAIL; } return S_OK; } static HRESULT drawThemedCheckbox(struct drawState *s, HTHEME theme, int checked, int enabled) { RECT r; SIZE size; int state; HRESULT hr; hr = GetThemePartSize(theme, s->dc, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, NULL, TS_DRAW, &size); if (hr != S_OK) { logHRESULT(L"GetThemePartSize()", hr); return hr; // TODO fall back? } r = s->subitemIcon; r.right = r.left + size.cx; r.bottom = r.top + size.cy; centerImageRect(&r, &(s->subitemIcon)); if (!checked && enabled) state = CBS_UNCHECKEDNORMAL; else if (checked && enabled) state = CBS_CHECKEDNORMAL; else if (!checked && !enabled) state = CBS_UNCHECKEDDISABLED; else state = CBS_CHECKEDDISABLED; hr = DrawThemeBackground(theme, s->dc, BP_CHECKBOX, state, &r, NULL); if (hr != S_OK) { logHRESULT(L"DrawThemeBackground()", hr); return hr; } return S_OK; } static HRESULT drawCheckboxPart(struct drawState *s) { uiTableData *data; int checked, enabled; HTHEME theme; HRESULT hr; if (s->p->checkboxModelColumn == -1) return S_OK; data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxModelColumn); checked = uiTableDataInt(data); uiFreeTableData(data); switch (s->p->checkboxEditableColumn) { case uiTableModelColumnNeverEditable: enabled = 0; break; case uiTableModelColumnAlwaysEditable: enabled = 1; break; default: data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn); enabled = uiTableDataInt(data); uiFreeTableData(data); } theme = OpenThemeData(s->t->hwnd, L"button"); if (theme != NULL) { hr = drawThemedCheckbox(s, theme, checked, enabled); if (hr != S_OK) return hr; hr = CloseThemeData(theme); if (hr != S_OK) { logHRESULT(L"CloseThemeData()", hr); return hr; } } else { hr = drawUnthemedCheckbox(s, checked, enabled); if (hr != S_OK) return hr; } return S_OK; } static HRESULT drawTextPart(struct drawState *s) { COLORREF prevText; int prevMode; RECT r; uiTableData *data; WCHAR *wstr; if (!s->hasText) return S_OK; prevText = SetTextColor(s->dc, s->textColor); if (prevText == CLR_INVALID) { logLastError(L"SetTextColor()"); return E_FAIL; } prevMode = SetBkMode(s->dc, TRANSPARENT); if (prevMode == 0) { logLastError(L"SetBkMode()"); return E_FAIL; } data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->textModelColumn); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); // These flags are a menagerie of flags from various sources: // guessing, the Windows 2000 source leak, various custom // draw examples on the web, etc. // TODO find the real correct flags if (DrawTextW(s->dc, wstr, -1, &(s->realTextRect), DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX | DT_EDITCONTROL) == 0) { uiprivFree(wstr); logLastError(L"DrawTextW()"); return E_FAIL; } uiprivFree(wstr); // TODO decide once and for all what to compare to here and with SelectObject() if (SetBkMode(s->dc, prevMode) != TRANSPARENT) { logLastError(L"SetBkMode() prev"); return E_FAIL; } if (SetTextColor(s->dc, prevText) != s->textColor) { logLastError(L"SetTextColor() prev"); return E_FAIL; } return S_OK; } // much of this is to imitate what shell32.dll's CDrawProgressBar does #define indeterminateSegments 8 static HRESULT drawProgressBarPart(struct drawState *s) { int progress; LONG indeterminatePos; HTHEME theme; RECT r; RECT rBorder, rFill[2]; int i, nFill; TEXTMETRICW tm; int sysColor; HRESULT hr; if (s->p->progressBarModelColumn == -1) return S_OK; progress = uiprivTableProgress(s->t, s->iItem, s->iSubItem, s->p->progressBarModelColumn, &indeterminatePos); theme = OpenThemeData(s->t->hwnd, L"PROGRESS"); if (GetTextMetricsW(s->dc, &tm) == 0) { logLastError(L"GetTextMetricsW()"); hr = E_FAIL; goto fail; } r = s->subitemBounds; // this sets the height of the progressbar and vertically centers it in one fell swoop r.top += (r.bottom - tm.tmHeight - r.top) / 2; r.bottom = r.top + tm.tmHeight; // TODO check errors rBorder = r; InflateRect(&rBorder, -1, -1); if (theme != NULL) { RECT crect; hr = GetThemeBackgroundContentRect(theme, s->dc, PP_TRANSPARENTBAR, PBBS_NORMAL, &rBorder, &crect); if (hr != S_OK) { logHRESULT(L"GetThemeBackgroundContentRect()", hr); goto fail; } hr = DrawThemeBackground(theme, s->dc, PP_TRANSPARENTBAR, PBBS_NORMAL, &crect, NULL); if (hr != S_OK) { logHRESULT(L"DrawThemeBackground() border", hr); goto fail; } } else { HPEN pen, prevPen; HBRUSH brush, prevBrush; sysColor = COLOR_HIGHLIGHT; if (s->selected) sysColor = COLOR_HIGHLIGHTTEXT; // TODO check errors everywhere pen = CreatePen(PS_SOLID, 1, GetSysColor(sysColor)); prevPen = (HPEN) SelectObject(s->dc, pen); brush = (HBRUSH) GetStockObject(NULL_BRUSH); prevBrush = (HBRUSH) SelectObject(s->dc, brush); Rectangle(s->dc, rBorder.left, rBorder.top, rBorder.right, rBorder.bottom); SelectObject(s->dc, prevBrush); SelectObject(s->dc, prevPen); DeleteObject(pen); } nFill = 1; rFill[0] = r; // TODO check error InflateRect(&rFill[0], -1, -1); if (progress != -1) rFill[0].right -= (rFill[0].right - rFill[0].left) * (100 - progress) / 100; else { LONG barWidth; LONG pieceWidth; // TODO explain all this // TODO this should really start the progressbar scrolling into view instead of already on screen when first set rFill[1] = rFill[0]; // save in case we need it barWidth = rFill[0].right - rFill[0].left; pieceWidth = barWidth / indeterminateSegments; rFill[0].left += indeterminatePos % barWidth; if ((rFill[0].left + pieceWidth) >= rFill[0].right) { // make this piece wrap back around nFill++; rFill[1].right = rFill[1].left + (pieceWidth - (rFill[0].right - rFill[0].left)); } else rFill[0].right = rFill[0].left + pieceWidth; } for (i = 0; i < nFill; i++) if (theme != NULL) { hr = DrawThemeBackground(theme, s->dc, PP_FILL, PBFS_NORMAL, &rFill[i], NULL); if (hr != S_OK) { logHRESULT(L"DrawThemeBackground() fill", hr); goto fail; } } else // TODO check errors FillRect(s->dc, &rFill[i], GetSysColorBrush(sysColor)); hr = S_OK; fail: // TODO check errors if (theme != NULL) CloseThemeData(theme); return hr; } static HRESULT drawButtonPart(struct drawState *s) { uiTableData *data; WCHAR *wstr; bool enabled; HTHEME theme; RECT r; TEXTMETRICW tm; HRESULT hr; if (s->p->buttonModelColumn == -1) return S_OK; data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->buttonModelColumn); wstr = toUTF16(uiTableDataString(data)); uiFreeTableData(data); switch (s->p->buttonClickableModelColumn) { case uiTableModelColumnNeverEditable: enabled = 0; break; case uiTableModelColumnAlwaysEditable: enabled = 1; break; default: data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, s->p->checkboxEditableColumn); enabled = uiTableDataInt(data); uiFreeTableData(data); } theme = OpenThemeData(s->t->hwnd, L"button"); if (GetTextMetricsW(s->dc, &tm) == 0) { logLastError(L"GetTextMetricsW()"); hr = E_FAIL; goto fail; } r = s->subitemBounds; if (theme != NULL) { int state; state = PBS_NORMAL; if (!enabled) state = PBS_DISABLED; hr = DrawThemeBackground(theme, s->dc, BP_PUSHBUTTON, state, &r, NULL); if (hr != S_OK) { logHRESULT(L"DrawThemeBackground()", hr); goto fail; } // TODO DT_EDITCONTROL? // TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here // TODO is there a theme property we can get instead of hardcoding these flags? if not, make these flags a macro hr = DrawThemeText(theme, s->dc, BP_PUSHBUTTON, state, wstr, -1, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX, 0, &r); if (hr != S_OK) { logHRESULT(L"DrawThemeText()", hr); goto fail; } } else { UINT state; HBRUSH color, prevColor; int prevBkMode; // TODO check errors // TODO explain why we're not doing this in the themed case (it has to do with extra transparent pixels) InflateRect(&r, -1, -1); state = DFCS_BUTTONPUSH; if (!enabled) state |= DFCS_INACTIVE; if (DrawFrameControl(s->dc, &r, DFC_BUTTON, state) == 0) { logLastError(L"DrawFrameControl()"); hr = E_FAIL; goto fail; } color = GetSysColorBrush(COLOR_BTNTEXT); // TODO check errors for these two prevColor = (HBRUSH) SelectObject(s->dc, color); prevBkMode = SetBkMode(s->dc, TRANSPARENT); // TODO DT_EDITCONTROL? // TODO DT_PATH_ELLIPSIS DT_WORD_ELLIPSIS instead of DT_END_ELLIPSIS? a middle-ellipsis option would be ideal here if (DrawTextW(s->dc, wstr, -1, &r, DT_CENTER | DT_VCENTER | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX) == 0) { logLastError(L"DrawTextW()"); hr = E_FAIL; goto fail; } // TODO check errors for these two SetBkMode(s->dc, prevBkMode); SelectObject(s->dc, prevColor); } hr = S_OK; fail: // TODO check errors if (theme != NULL) CloseThemeData(theme); uiprivFree(wstr); return hr; } static HRESULT freeDrawState(struct drawState *s) { HRESULT hr, hrret; hrret = S_OK; if (s->freeTextBrush) { if (DeleteObject(s->textBrush) == 0) { logLastError(L"DeleteObject()"); hrret = E_FAIL; // continue cleaning up anyway } s->freeTextBrush = FALSE; } if (s->freeBgBrush) { if (DeleteObject(s->bgBrush) == 0) { logLastError(L"DeleteObject()"); hrret = E_FAIL; // continue cleaning up anyway } s->freeBgBrush = FALSE; } return hrret; } static HRESULT itemRect(HRESULT hr, uiTable *t, UINT uMsg, WPARAM wParam, LONG left, LONG top, LRESULT bad, RECT *r) { if (hr != S_OK) return hr; ZeroMemory(r, sizeof (RECT)); r->left = left; r->top = top; if (SendMessageW(t->hwnd, uMsg, wParam, (LPARAM) r) == bad) { logLastError(L"itemRect() message"); return E_FAIL; } return S_OK; } static COLORREF blend(COLORREF base, double r, double g, double b, double a) { double br, bg, bb; br = ((double) GetRValue(base)) / 255.0; bg = ((double) GetGValue(base)) / 255.0; bb = ((double) GetBValue(base)) / 255.0; br = (r * a) + (br * (1.0 - a)); bg = (g * a) + (bg * (1.0 - a)); bb = (b * a) + (bb * (1.0 - a)); return RGB((BYTE) (br * 255), (BYTE) (bg * 255), (BYTE) (bb * 255)); } static HRESULT fillDrawState(struct drawState *s, uiTable *t, NMLVCUSTOMDRAW *nm, uiprivTableColumnParams *p) { LRESULT state; HWND header; HRESULT hr; ZeroMemory(s, sizeof (struct drawState)); s->t = t; s->m = t->model; s->p = p; s->dc = nm->nmcd.hdc; s->iItem = nm->nmcd.dwItemSpec; s->iSubItem = nm->iSubItem; s->hasText = p->textModelColumn != -1; s->hasImage = (p->imageModelColumn != -1) || (p->checkboxModelColumn != -1); // nm->nmcd.uItemState CDIS_SELECTED is unreliable for the // listview configuration we have, so we must do this. state = SendMessageW(t->hwnd, LVM_GETITEMSTATE, nm->nmcd.dwItemSpec, LVIS_SELECTED); s->selected = (state & LVIS_SELECTED) != 0; s->focused = (nm->nmcd.uItemState & CDIS_FOCUS) != 0; // TODO check LRESULT bad parameters here hr = itemRect(S_OK, t, LVM_GETITEMRECT, s->iItem, LVIR_BOUNDS, 0, FALSE, &(s->itemBounds)); hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, LVIR_ICON, 0, FALSE, &(s->itemIcon)); hr = itemRect(hr, t, LVM_GETITEMRECT, s->iItem, LVIR_LABEL, 0, FALSE, &(s->itemLabel)); hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, LVIR_BOUNDS, s->iSubItem, 0, &(s->subitemBounds)); hr = itemRect(hr, t, LVM_GETSUBITEMRECT, s->iItem, LVIR_ICON, s->iSubItem, 0, &(s->subitemIcon)); if (hr != S_OK) goto fail; // LVM_GETSUBITEMRECT treats LVIR_LABEL as the same as // LVIR_BOUNDS, so we can't use that directly. Instead, let's // assume the text is immediately after the icon. The correct // rect will be determined by // computeOtherRectsAndDrawBackgrounds() above. s->subitemLabel = s->subitemBounds; s->subitemLabel.left = s->subitemIcon.right; // And on iSubItem == 0, LVIF_GETSUBITEMRECT still includes // all the subitems, which we don't want. if (s->iSubItem == 0) { s->subitemBounds.right = s->itemLabel.right; s->subitemLabel.right = s->itemLabel.right; } if (s->selected) { s->bgColor = GetSysColor(COLOR_HIGHLIGHT); s->bgBrush = GetSysColorBrush(COLOR_HIGHLIGHT); s->textColor = GetSysColor(COLOR_HIGHLIGHTTEXT); s->textBrush = GetSysColorBrush(COLOR_HIGHLIGHTTEXT); } else { uiTableData *data; double r, g, b, a; s->bgColor = GetSysColor(COLOR_WINDOW); s->bgBrush = GetSysColorBrush(COLOR_WINDOW); if (t->backgroundColumn != -1) { data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, t->backgroundColumn); if (data != NULL) { uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); s->bgColor = blend(s->bgColor, r, g, b, a); s->bgBrush = CreateSolidBrush(s->bgColor); if (s->bgBrush == NULL) { logLastError(L"CreateSolidBrush()"); hr = E_FAIL; goto fail; } s->freeBgBrush = TRUE; } } s->textColor = GetSysColor(COLOR_WINDOWTEXT); s->textBrush = GetSysColorBrush(COLOR_WINDOWTEXT); if (p->textParams.ColorModelColumn != -1) { data = (*(s->m->mh->CellValue))(s->m->mh, s->m, s->iItem, p->textParams.ColorModelColumn); if (data != NULL) { uiTableDataColor(data, &r, &g, &b, &a); uiFreeTableData(data); s->textColor = blend(s->bgColor, r, g, b, a); s->textBrush = CreateSolidBrush(s->textColor); if (s->textBrush == NULL) { logLastError(L"CreateSolidBrush()"); hr = E_FAIL; goto fail; } s->freeTextBrush = TRUE; } } } header = (HWND) SendMessageW(t->hwnd, LVM_GETHEADER, 0, 0); s->bitmapMargin = SendMessageW(header, HDM_GETBITMAPMARGIN, 0, 0); if (ImageList_GetIconSize(t->imagelist, &(s->cxIcon), &(s->cyIcon)) == 0) { logLastError(L"ImageList_GetIconSize()"); hr = E_FAIL; goto fail; } return S_OK; fail: // ignore the error; we need to return the one we got above freeDrawState(s); return hr; } HRESULT uiprivTableHandleNM_CUSTOMDRAW(uiTable *t, NMLVCUSTOMDRAW *nm, LRESULT *lResult) { struct drawState s; uiprivTableColumnParams *p; HRESULT hr; switch (nm->nmcd.dwDrawStage) { case CDDS_PREPAINT: *lResult = CDRF_NOTIFYITEMDRAW; return S_OK; case CDDS_ITEMPREPAINT: *lResult = CDRF_NOTIFYSUBITEMDRAW; return S_OK; case CDDS_SUBITEM | CDDS_ITEMPREPAINT: break; default: *lResult = CDRF_DODEFAULT; return S_OK; } p = (*(t->columns))[nm->iSubItem]; hr = fillDrawState(&s, t, nm, p); if (hr != S_OK) return hr; hr = computeOtherRectsAndDrawBackgrounds(&s); if (hr != S_OK) goto fail; hr = drawImagePart(&s); if (hr != S_OK) goto fail; hr = drawCheckboxPart(&s); if (hr != S_OK) goto fail; hr = drawTextPart(&s); if (hr != S_OK) goto fail; hr = drawProgressBarPart(&s); if (hr != S_OK) goto fail; hr = drawButtonPart(&s); if (hr != S_OK) goto fail; hr = freeDrawState(&s); if (hr != S_OK) // TODO really error out here? return hr; *lResult = CDRF_SKIPDEFAULT; return S_OK; fail: // ignore error here freeDrawState(&s); return hr; } // TODO run again when the DPI or the theme changes // TODO properly clean things up here // TODO properly destroy the old lists here too HRESULT uiprivUpdateImageListSize(uiTable *t) { HDC dc; int cxList, cyList; HTHEME theme; SIZE sizeCheck; HRESULT hr; dc = GetDC(t->hwnd); if (dc == NULL) { logLastError(L"GetDC()"); return E_FAIL; } cxList = GetSystemMetrics(SM_CXSMICON); cyList = GetSystemMetrics(SM_CYSMICON); sizeCheck.cx = cxList; sizeCheck.cy = cyList; theme = OpenThemeData(t->hwnd, L"button"); if (theme != NULL) { hr = GetThemePartSize(theme, dc, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, NULL, TS_DRAW, &sizeCheck); if (hr != S_OK) { logHRESULT(L"GetThemePartSize()", hr); return hr; // TODO fall back? } // make sure these checkmarks fit // unthemed checkmarks will by the code above be smaller than cxList/cyList here if (cxList < sizeCheck.cx) cxList = sizeCheck.cx; if (cyList < sizeCheck.cy) cyList = sizeCheck.cy; hr = CloseThemeData(theme); if (hr != S_OK) { logHRESULT(L"CloseThemeData()", hr); return hr; } } // TODO handle errors t->imagelist = ImageList_Create(cxList, cyList, ILC_COLOR32, 1, 1); if (t->imagelist == NULL) { logLastError(L"ImageList_Create()"); return E_FAIL; } // TODO will this return NULL here because it's an initial state? SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->imagelist)); if (ReleaseDC(t->hwnd, dc) == 0) { logLastError(L"ReleaseDC()"); return E_FAIL; } return S_OK; }