Skip to content

Instantly share code, notes, and snippets.

@ssrlive
Last active November 30, 2020 03:07
Show Gist options
  • Save ssrlive/cffaf5e27eb3f500684d2f6e51628932 to your computer and use it in GitHub Desktop.
Save ssrlive/cffaf5e27eb3f500684d2f6e51628932 to your computer and use it in GitHub Desktop.
A GroupBox control with a CheckBox: CheckableGroupBox class for Windows
#include <Windows.h>
#include <WindowsX.h>
#include <commctrl.h>
#include <assert.h>
#include "checkablegroupbox.h"
#pragma comment(lib, "Comctl32.lib")
#define ID_CHECKBOX 0xFFFE
#define LEFT_OFFSET 12
#define CHECKBOX_SIZE 16
typedef struct CheckableGroupBoxData {
HWND hCheckBox;
} CheckableGroupBoxData;
static void CheckableGroupBox_OnClicked(HWND hWnd);
static int CheckableGroupBox_GetCheck(HWND hWnd);
static int CheckableGroupBox_SetCheck(HWND hWnd, int nCheck);
static void CheckableGroupBox_EnableControls(HWND hWnd, BOOL bActivate);
static int CheckableGroupBox_MoveTitle(HWND hWnd);
static void CheckableGroupBox_OnEnable(HWND hWnd, BOOL bEnable);
LRESULT CALLBACK CheckableGroupBoxProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
LRESULT rs = 0;
CheckableGroupBoxData *data = (CheckableGroupBoxData *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
switch (uMsg)
{
case WM_COMMAND:
{
HWND hChkBox = GET_WM_COMMAND_HWND(wParam, lParam);
int chkBoxID = (int)GET_WM_COMMAND_ID(wParam, lParam);
if (chkBoxID == ID_CHECKBOX) {
assert(data->hCheckBox == hChkBox);
CheckableGroupBox_OnClicked(hWnd);
}
}
break;
case BM_GETCHECK:
return CheckableGroupBox_GetCheck(hWnd);
break;
case BM_SETCHECK:
CheckableGroupBox_SetCheck(hWnd, (int)wParam);
return 0;
break;
case WM_ENABLE:
CheckableGroupBox_OnEnable(hWnd, (BOOL)wParam);
break;
case WM_SETFOCUS:
rs = DefSubclassProc(hWnd, uMsg, wParam, lParam);
SetFocus(data->hCheckBox);
return 0;
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, CheckableGroupBoxProc, 0);
free(data);
break;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
static int CWnd_GetCheck(HWND hWnd) {
assert(IsWindow(hWnd));
return (int)SendMessage(hWnd, BM_GETCHECK, 0, 0);
}
static void CWnd_SetCheck(HWND hWnd, int nCheck) {
assert(IsWindow(hWnd));
SendMessage(hWnd, BM_SETCHECK, (WPARAM)nCheck, 0);
}
static int CheckableGroupBox_GetCheck(HWND hWnd) {
CheckableGroupBoxData *data = (CheckableGroupBoxData *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
assert(IsWindow(hWnd));
return (int)CWnd_GetCheck(data->hCheckBox);
}
static HFONT CWnd_GetFont(HWND hWnd) {
assert(IsWindow(hWnd));
return (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
}
static void CWnd_SetFont(HWND hWnd, HFONT pFont, BOOL bRedraw) {
assert(IsWindow(hWnd));
SendMessage(hWnd, WM_SETFONT, (WPARAM)pFont, bRedraw);
}
static BOOL _Afx_ModifyStyle(HWND hWnd, int nStyleOffset, DWORD dwRemove, DWORD dwAdd, UINT nFlags)
{
DWORD dwStyle, dwNewStyle;
assert(hWnd != NULL);
dwStyle = GetWindowLong(hWnd, nStyleOffset);
dwNewStyle = (dwStyle & ~dwRemove) | dwAdd;
if (dwStyle == dwNewStyle) {
return FALSE;
}
SetWindowLong(hWnd, nStyleOffset, dwNewStyle);
if (nFlags != 0) {
SetWindowPos(hWnd, NULL, 0, 0, 0, 0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | nFlags);
}
return TRUE;
}
static void CheckableGroupBox_OnClicked(HWND hWnd) {
CheckableGroupBox_EnableControls(hWnd, CheckableGroupBox_GetCheck(hWnd));
SendMessage(GetParent(hWnd), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hWnd), BN_CLICKED), (LPARAM)hWnd);
}
static void CheckableGroupBox_EnableControls(HWND hWnd, BOOL bActivate)
{
// Enable/Disable all controls that fall whole inside of me.
// Brute force: check every control in the dialog.
RECT rCtrl, rTest = { 0 };
RECT rGroup;
HWND pParent = GetParent(hWnd);
HWND pCtrl = GetWindow(pParent, GW_CHILD); // Gets first child
GetWindowRect(hWnd, &rGroup);
while (IsWindow(pCtrl)) {
// Don't do myself !!
if (pCtrl == hWnd) {
pCtrl = GetWindow(pCtrl, GW_HWNDNEXT);
continue;
}
/*
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
if ((short)GetDlgCtrlID(pCtrl) == IDC_STATIC) {
pCtrl = GetWindow(pCtrl, GW_HWNDNEXT);
continue;
}
*/
GetWindowRect(pCtrl, &rCtrl);
if (IntersectRect(&rTest, &rGroup, &rCtrl)) {
EnableWindow(pCtrl, bActivate);
}
pCtrl = GetWindow(pCtrl, GW_HWNDNEXT);
}
}
void CheckableGroupBox_SubclassWindow(HWND hWnd) {
// Make sure that this control has the BS_ICON style set.
// If not, it behaves very strangely:
// It erases itself if the user TABs to controls in the dialog,
// unless the user first clicks it. Very strange!!
wchar_t strTitle[MAX_PATH] = { 0 };
int nWidth;
RECT r = { 0 };
HWND hCheckBox = NULL;
CheckableGroupBoxData *data = NULL;
_Afx_ModifyStyle(hWnd, GWL_STYLE, 0, BS_ICON | WS_TABSTOP | WS_GROUP, 0);
GetWindowTextW(hWnd, strTitle, ARRAYSIZE(strTitle));
nWidth = CheckableGroupBox_MoveTitle(hWnd);
GetWindowRect(hWnd, &r);
//ScreenToClient(hWnd, &r);
ScreenToClient(hWnd, (LPPOINT)&r);
ScreenToClient(hWnd, ((LPPOINT)&r) + 1);
OffsetRect(&r, LEFT_OFFSET, 0);
r.bottom = r.top + CHECKBOX_SIZE;
r.right = r.left + CHECKBOX_SIZE + nWidth + 5;
hCheckBox = CreateWindowW(L"BUTTON", strTitle,
WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX,
r.left, r.top, r.right - r.left, r.bottom - r.top,
hWnd, (HMENU)ID_CHECKBOX,
NULL, //((LPCREATESTRUCT)lParam)->hInstance,
NULL);
CWnd_SetFont(hCheckBox, CWnd_GetFont(hWnd), TRUE);
ShowWindow(hCheckBox, SW_SHOW);
data = (CheckableGroupBoxData *)calloc(1, sizeof(*data));
data->hCheckBox = hCheckBox;
SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LONG)data);
SetWindowSubclass(hWnd, CheckableGroupBoxProc, 0, 0);
}
static int CheckableGroupBox_MoveTitle(HWND hWnd)
{
// The group box title needs to be erased, but I need to keep the border away from the check box text.
// I create a string of spaces (' ') that is the same length as the title was plus the size of the check box.
// Plus a little more.
HDC dc;
HFONT pOldFont;
SIZE czText = { 0 };
int nRet, nTarget, count;
TCHAR strOldTitle[256] = { 0 }, strNewTitle[256] = { 0 };
GetWindowText(hWnd, strOldTitle, ARRAYSIZE(strOldTitle));
dc = GetDC(hWnd);
pOldFont = (HFONT)SelectObject(dc, CWnd_GetFont(hWnd));
GetTextExtentPoint32(dc, strOldTitle, (int)lstrlen(strOldTitle), &czText);
nRet = czText.cx;
nTarget = czText.cx + CHECKBOX_SIZE + 10;
count = 0;
while (czText.cx < nTarget) {
strNewTitle[count++] = ' ';
assert(count < ARRAYSIZE(strNewTitle));
ZeroMemory(&czText, sizeof(czText));
GetTextExtentPoint32(dc, strNewTitle, (int)lstrlen(strNewTitle), &czText);
}
SelectObject(dc, pOldFont);
SetWindowText(hWnd, strNewTitle);
ReleaseDC(hWnd, dc);
return nRet;
}
static int CheckableGroupBox_SetCheck(HWND hWnd, int nCheck)
{
CheckableGroupBoxData *data = (CheckableGroupBoxData *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
CWnd_SetCheck(data->hCheckBox, nCheck);
CheckableGroupBox_EnableControls(hWnd, nCheck == 0 ? FALSE : TRUE);
return 0;
}
static void CheckableGroupBox_OnEnable(HWND hWnd, BOOL bEnable)
{
CheckableGroupBoxData *data = (CheckableGroupBoxData *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
EnableWindow(data->hCheckBox, bEnable);
CheckableGroupBox_EnableControls(hWnd, bEnable && CheckableGroupBox_GetCheck(hWnd));
}
//=============================================================================
//
// EnableGroupboxControls()
//
// Purpose: This function enables/disables all the controls that are
// completely contained with a groupbox.
//
// Parameters: hWnd - HWND of groupbox control
// bEnable - TRUE = enable controls within groupbox
//
// Returns: int - number of controls enabled/disabled. If zero is
// returned, it means that no controls lie within the
// rect of the groupbox.
//
int EnableGroupboxControls(HWND hWnd, BOOL bEnable)
{
int rc = 0;
if (IsWindow(hWnd))
{
LONG lStyle;
// get class name
wchar_t szClassName[MAX_PATH] = { 0 };
GetClassNameW(hWnd, szClassName, ARRAYSIZE(szClassName) - 2);
// get window style
lStyle = GetWindowLong(hWnd, GWL_STYLE);
if ((_wcsicmp(szClassName, L"Button") == 0) &&
((lStyle & BS_GROUPBOX) == BS_GROUPBOX))
{
// this is a groupbox
HWND hWndChild = 0, hWndParent;
RECT rectGroupbox;
GetWindowRect(hWnd, &rectGroupbox);
// get first child control
hWndParent = GetParent(hWnd);
if (IsWindow(hWndParent)) {
hWndChild = GetWindow(hWndParent, GW_CHILD);
}
while (hWndChild)
{
RECT rectChild;
GetWindowRect(hWndChild, &rectChild);
// check if child rect is entirely contained within groupbox
if ((rectChild.left >= rectGroupbox.left) &&
(rectChild.right <= rectGroupbox.right) &&
(rectChild.top >= rectGroupbox.top) &&
(rectChild.bottom <= rectGroupbox.bottom))
{
//TRACE(_T("found child window 0x%X\n"), hWndChild);
EnableWindow(hWndChild, bEnable);
rc++;
}
// get next child control
hWndChild = GetWindow(hWndChild, GW_HWNDNEXT);
}
// if any controls were affected, invalidate the parent rect
if (rc && IsWindow(hWndParent))
{
InvalidateRect(hWndParent, NULL, FALSE);
}
}
}
return rc;
}
#pragma once
#include <Windows.h>
#include <WindowsX.h>
#ifdef __cplusplus
extern "C" {
#endif
//
// make a group box control with a check box.
// usage:
//
// HWND hBox = ::GetDlgItem(this->GetSafeHwnd(), IDC_GROUPBOX3);
// CheckableGroupBox_SubclassWindow(hBox);
// Button_SetCheck(hBox, FALSE);
// BOOL chk = Button_GetCheck(hBox);
//
void CheckableGroupBox_SubclassWindow(HWND hWnd);
#ifdef __cplusplus
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment