And added themed checkboxes.

This commit is contained in:
Pietro Gagliardi 2018-06-10 13:15:21 -04:00
parent 8dd9f08ba4
commit 5a5f9ba9ac
2 changed files with 108 additions and 39 deletions

View File

@ -104,7 +104,10 @@ HRESULT uiprivLVN_GETDISPINFOImagesCheckboxes(uiTable *t, NMLVDISPINFOW *nm, uip
return S_OK; return S_OK;
} }
// see https://blogs.msdn.microsoft.com/oldnewthing/20171129-00/?p=97485 // 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 UINT unthemedStates[] = { static UINT unthemedStates[] = {
0, 0,
DFCS_CHECKED, DFCS_CHECKED,
@ -112,11 +115,16 @@ static UINT unthemedStates[] = {
DFCS_CHECKED | DFCS_INACTIVE, DFCS_CHECKED | DFCS_INACTIVE,
}; };
// TODO call this whenever either theme, system colors (maybe? TODO), or DPI change static int themedStates[] = {
CBS_UNCHECKEDNORMAL,
CBS_CHECKEDNORMAL,
CBS_UNCHECKEDDISABLED,
CBS_CHECKEDDISABLED,
};
// TODO properly clean up on failure // TODO properly clean up on failure
static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy) static HRESULT mkCheckboxes(uiTable *t, HTHEME theme, HDC dc, int cxList, int cyList, int cxCheck, int cyCheck)
{ {
HDC dc;
BITMAPINFO bmi; BITMAPINFO bmi;
HBITMAP b; HBITMAP b;
VOID *bits; VOID *bits;
@ -124,16 +132,12 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy)
HBITMAP prevBitmap; HBITMAP prevBitmap;
RECT r; RECT r;
int i; int i;
HRESULT hr;
dc = GetDC(t->hwnd);
if (dc == NULL) {
logLastError(L"GetDC()");
return E_FAIL;
}
ZeroMemory(&bmi, sizeof (BITMAPINFO)); ZeroMemory(&bmi, sizeof (BITMAPINFO));
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER); bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = cx * nCheckboxImages; bmi.bmiHeader.biWidth = cxList * nCheckboxImages;
bmi.bmiHeader.biHeight = cy; bmi.bmiHeader.biHeight = cyList;
bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biCompression = BI_RGB;
@ -157,29 +161,54 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy)
// the actual list view LVS_EX_CHECKBOXES code does this to ensure the entire image is valid, not just the parts that are drawn after resizing // the actual list view LVS_EX_CHECKBOXES code does this to ensure the entire image is valid, not just the parts that are drawn after resizing
// TODO find a better, alpha-friendly way to do this // TODO find a better, alpha-friendly way to do this
r.left = 0; // note that the actual list view does this only if unthemed, but it can get away with that since its image lists only contain checkmarks
r.top = 0; // ours don't, so we have to compromise until the above TODO is resolved so we don't draw alpha stuff on top of garbage
r.right = cx * nCheckboxImages; if (theme == NULL || cxList != cxCheck || cyList != cyCheck) {
r.bottom = cy; r.left = 0;
FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW)); r.top = 0;
r.right = cxList * nCheckboxImages;
r.bottom = cyList;
FillRect(cdc, &r, GetSysColorBrush(COLOR_WINDOW));
}
r.left = 0; r.left = 0;
r.top = 0; r.top = 0;
r.right = cx; r.right = cxCheck;
r.bottom = cy; r.bottom = cyCheck;
// this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes if (theme != NULL) {
// TODO check errors // because we're not making an image list exactly the correct size, we'll need to manually position the checkbox correctly
InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE)); // let's just center it for now
r.right++; // TODO make sure this is correct...
r.bottom++; r.left = (cxList - cxCheck) / 2;
for (i = 0; i < nCheckboxImages; i++) { r.top = (cyList - cyCheck) / 2;
if (DrawFrameControl(cdc, &r, r.right += r.left;
DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) { r.bottom += r.top;
logLastError(L"DrawFrameControl()"); for (i = 0; i < nCheckboxImages; i++) {
return E_FAIL; hr = DrawThemeBackground(theme, cdc,
BP_CHECKBOX, themedStates[i],
&r, NULL);
if (hr != S_OK) {
logHRESULT(L"DrawThemeBackground()", hr);
return hr;
}
r.left += cxList;
r.right += cxList;
}
} else {
// this is what the actual list view LVS_EX_CHECKBOXES code does to more correctly size the checkboxes
// TODO check errors
InflateRect(&r, -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE));
r.right++;
r.bottom++;
for (i = 0; i < nCheckboxImages; i++) {
if (DrawFrameControl(cdc, &r,
DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | unthemedStates[i]) == 0) {
logLastError(L"DrawFrameControl()");
return E_FAIL;
}
r.left += cxList;
r.right += cxList;
} }
r.left += cx;
r.right += cx;
} }
if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) { if (SelectObject(cdc, prevBitmap) != ((HGDIOBJ) b)) {
@ -190,10 +219,6 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy)
logLastError(L"DeleteDC()"); logLastError(L"DeleteDC()");
return E_FAIL; return E_FAIL;
} }
if (ReleaseDC(t->hwnd, dc) == 0) {
logLastError(L"ReleaseDC()");
return E_FAIL;
}
if (ImageList_Add(t->smallImages, b, NULL) == -1) { if (ImageList_Add(t->smallImages, b, NULL) == -1) {
logLastError(L"ImageList_Add()"); logLastError(L"ImageList_Add()");
@ -208,12 +233,41 @@ static HRESULT mkCheckboxesUnthemed(uiTable *t, int cx, int cy)
// TODO run again when the DPI changes // TODO run again when the DPI changes
HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t) HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t)
{ {
int cx, cy; 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;
}
cx = GetSystemMetrics(SM_CXSMICON);
cy = GetSystemMetrics(SM_CYSMICON);
// TODO handle errors // TODO handle errors
t->smallImages = ImageList_Create(cx, cy, t->smallImages = ImageList_Create(cxList, cyList,
ILC_COLOR32, ILC_COLOR32,
nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip); nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip, nCheckboxImages + uiprivNumLVN_GETDISPINFOSkip);
if (t->smallImages == NULL) { if (t->smallImages == NULL) {
@ -222,5 +276,18 @@ HRESULT uiprivTableSetupImagesCheckboxes(uiTable *t)
} }
// TODO will this return NULL here because it's an initial state? // TODO will this return NULL here because it's an initial state?
SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages)); SendMessageW(t->hwnd, LVM_SETIMAGELIST, LVSIL_SMALL, (LPARAM) (t->smallImages));
return mkCheckboxesUnthemed(t, cx, cy); hr = mkCheckboxes(t, theme, dc, cxList, cyList, sizeCheck.cx, sizeCheck.cy);
if (hr != S_OK)
return hr;
hr = CloseThemeData(theme);
if (hr != S_OK) {
logHRESULT(L"CloseThemeData()", hr);
return hr;
}
if (ReleaseDC(t->hwnd, dc) == 0) {
logLastError(L"ReleaseDC()");
return E_FAIL;
}
return S_OK;
} }

View File

@ -37,6 +37,8 @@
#ifndef RC_INVOKED #ifndef RC_INVOKED
#include <commctrl.h> #include <commctrl.h>
#include <uxtheme.h> #include <uxtheme.h>
#include <vsstyle.h>
#include <vssym32.h>
#include <windowsx.h> #include <windowsx.h>
#include <shobjidl.h> #include <shobjidl.h>
#include <d2d1.h> #include <d2d1.h>