MFC - 带有可选复选框的 CListCtrl 行
MFC - CListCtrl rows with optional checkboxes
在运行时,我正在尝试创建单列自定义 CListCtrl
(或 CMFCListCtrl
,但不是 CheckListBox
- 我希望能够在未来)使用 MFC。使用 LVS_EX_CHECKBOXES
样式会强制所有项目都具有复选框。所需的控件应如下所示(item1 和 item3 有复选框,item2 没有):
从用户的角度来看,应该像这样创建所需的列表控件:
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
}
到目前为止,我能够创建这样的控件,但是添加 LVS_OWNERDRAWFIXED
会触发失败的断言,当调用基础 class CListCtrl::DrawItem
方法时:
// 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");
...
}
这就是我的(不起作用的)解决方案的样子,如果您知道如何解决这个问题,甚至可能以更简单的方式解决,请告诉我。谢谢。
编辑
在@Landstalker 的帮助下,我现在可以使用自定义绘图擦除复选框,但我仍然需要将文本向左移动(因此它取代了不存在的复选框,如上图)。当前解决方案导致此结果:
这是通过像这样处理 NM_CUSTOMDRAW 消息来实现的:
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
}
}
}
经过长时间的调查...我为您找到了解决方案:使用 SetItemState ()
魔术功能:
Remarque : 有多列不是问题
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);
在运行时,我正在尝试创建单列自定义 CListCtrl
(或 CMFCListCtrl
,但不是 CheckListBox
- 我希望能够在未来)使用 MFC。使用 LVS_EX_CHECKBOXES
样式会强制所有项目都具有复选框。所需的控件应如下所示(item1 和 item3 有复选框,item2 没有):
从用户的角度来看,应该像这样创建所需的列表控件:
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
}
到目前为止,我能够创建这样的控件,但是添加 LVS_OWNERDRAWFIXED
会触发失败的断言,当调用基础 class CListCtrl::DrawItem
方法时:
// 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");
...
}
这就是我的(不起作用的)解决方案的样子,如果您知道如何解决这个问题,甚至可能以更简单的方式解决,请告诉我。谢谢。
编辑
在@Landstalker 的帮助下,我现在可以使用自定义绘图擦除复选框,但我仍然需要将文本向左移动(因此它取代了不存在的复选框,如上图)。当前解决方案导致此结果:
这是通过像这样处理 NM_CUSTOMDRAW 消息来实现的:
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
}
}
}
经过长时间的调查...我为您找到了解决方案:使用 SetItemState ()
魔术功能:
Remarque : 有多列不是问题
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);