In runtime, I'm trying to create a single-column custom CListCtrl
(or CMFCListCtrl
, but not CheckListBox
- I want to be able to add multiple columns in the future) using MFC. Using LVS_EX_CHECKBOXES
style forces all items to have the checkbox. The desired control should look like this (item1 and item3 have checkboxes, item2 doesn't):
From the user's point of view, the desired list control should be created like this:
int main() {
MyCListCtrl list_control;
list_control.AddItem("item1", true) // true indicates checkbox presence
list_control.AddItem("item2", false) // false - item without checkbox
list_control.AddItem("item3", true) // true indicates checkbox presence
}
So far I was able to create a control like this, but adding LVS_OWNERDRAWFIXED
triggers a failed assertion, when calling base class CListCtrl::DrawItem
method:
// MyCListCtrl.h
class MyCListCtrl : public CListCtrl {
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) override {
// if the item should be without a checkbox, here I want to move it a few pixels
// to the left so that the checkbox is hidden
...
CListCtrl::DrawItem(lpDrawItemStruct); // call base's DrawItem - without this
// there's no exception but the listbox appears empty
}
};
BOOL MyCDialogEx::OnInitDialog() {
CDialogEx::OnInitDialog();
...
// list being defined somewhere in the header file
list->Create(WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT | LVS_NOCOLUMNHEADER |
LVS_OWNERDRAWFIXED, // for DrawItem invocation
rect, this, SOME_ID);
list->SetExtendedStyle(list->GetExtendedStyle() | LVS_EX_CHECKBOXES);
// add 1 mandatory column because of LVS_REPORT style
LVCOLUMN lvColumn;
lvColumn.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
lvColumn.fmt = LVCFMT_LEFT;
lvColumn.cx = rect.Width() - 20; // also, how to make this column fit the width exactly?
lvColumn.pszText = nullptr;
list->InsertColumn(0, &lvColumn);
// for now only add 1 testing item and make his checkbox disappear by moving the
// whole item to the left in DrawItem method (called by the system), so that the text
// is aligned to the left list border
list->InsertItem(0, "item1");
...
}
This is how my (not working) solution looks like, if you know how to solve this, maybe even in an easier way, please let me know. Thanks.
EDIT
With @Landstalker 's help, I'm now able to erase the checkbox with the custom drawing, but I still need to move the text to the left (so it takes the place of a non-existing checkbox, like on the picture above). Current solution results in this result:
This is achieved by handling the NM_CUSTOMDRAW message like this:
void MyCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = CDRF_DODEFAULT; // default windows painting
LPNMLVCUSTOMDRAW lpn = (LPNMLVCUSTOMDRAW)pNMHDR;
if (CDDS_PREPAINT == lpn->nmcd.dwDrawStage)
{
*pResult = CDRF_NOTIFYITEMDRAW; // notify on every item
}
else if (CDDS_ITEMPREPAINT == lpn->nmcd.dwDrawStage)
{
int row = lpn->nmcd.dwItemSpec;
if (row == 1) {
lpn->nmcd.rc.left -= 16; // not working
lpn->rcText.left -= 16; // not working
SetItemState(row, INDEXTOSTATEIMAGEMASK(0),
LVIS_STATEIMAGEMASK); // erase checkbox
}
}
}
After long investigations ... I found a solution for you: use SetItemState ()
magic function :
Remarque : Having multiple columns is not a problem
MyCListCtrl.h
class MyCListCtrl : public CListCtrl
{
DECLARE_DYNAMIC(MyCListCtrl)
public:
afx_msg void DrawItem(NMHDR* pNMHDR, LRESULT* pResult);
DECLARE_MESSAGE_MAP()
};
MyCListCtrl.cpp
IMPLEMENT_DYNAMIC(MyCListCtrl, CListCtrl)
BEGIN_MESSAGE_MAP(MyCListCtrl, CListCtrl)
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, DrawItem)
END_MESSAGE_MAP()
void MyCListCtrl::DrawItem(NMHDR* pNMHDR, LRESULT* pResult)
{
*pResult = 0;
LPNMLVCUSTOMDRAW pLPN = (LPNMLVCUSTOMDRAW)pNMHDR;
int iRow = pLPN->nmcd.dwItemSpec;
// Get item flag : true or false (true we show checkbox, false we hide it)
// Here i simulate, i disable rows 1 and 3
SetItemState(1, INDEXTOSTATEIMAGEMASK(0), LVIS_STATEIMAGEMASK);
SetItemState(3, INDEXTOSTATEIMAGEMASK(0), LVIS_STATEIMAGEMASK);
switch(pLPN->nmcd.dwDrawStage)
{
case CDDS_PREPAINT | CDDS_ITEM | CDDS_SUBITEM :
{
*pResult = CDRF_DODEFAULT | CDRF_DOERASE; return;
}
case CDDS_PREPAINT :
{
*pResult = CDRF_NOTIFYITEMDRAW; return;
}
case CDDS_ITEMPREPAINT:
{
pLPN->clrText = RGB(0,0,0);
*pResult = CDRF_NOTIFYSUBITEMDRAW; return;
}
}
}
MainDlg.cpp
CRect rect (30, 30, 180, 180);
list_control.Create(WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_NOCOLUMNHEADER |LBS_OWNERDRAWVARIABLE , rect, this, IDC_LIST2);
list_control.SetExtendedStyle(list_control.GetExtendedStyle() | LVS_EX_FLATSB | LVS_EX_CHECKBOXES | LVS_EX_GRIDLINES);
LVCOLUMN lvColumn;
lvColumn.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
lvColumn.fmt = LVCFMT_LEFT;
lvColumn.cx = 70;
lvColumn.pszText = "Column 1";
list_control.InsertColumn(0, &lvColumn);
lvColumn.pszText = "Column 2";
list_control.InsertColumn(1, &lvColumn);
//// add 1 test item
LVITEM lvItem;
lvItem.mask = LVIF_TEXT;
lvItem.iItem = 0;
lvItem.iSubItem = 0;
lvItem.pszText = "Test";
list_control.InsertItem(&lvItem);
lvItem.pszText = "Stack";
list_control.InsertItem(&lvItem);
lvItem.pszText = "Over";
list_control.InsertItem(&lvItem);
lvItem.pszText = "Flow";
list_control.InsertItem(&lvItem);