MFC - 带有可选复选框的 CListCtrl 行

Posted

技术标签:

【中文标题】MFC - 带有可选复选框的 CListCtrl 行【英文标题】:MFC - CListCtrl rows with optional checkboxes 【发布时间】:2020-04-04 18:49:50 【问题描述】:

在运行时,我正在尝试使用 MFC 创建单列自定义 CListCtrl(或 CMFCListCtrl,但不是 CheckListBox - 我希望将来能够添加多个列)。使用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

到目前为止,我能够创建这样的控件,但是在调用基类CListCtrl::DrawItem 方法时添加LVS_OWNERDRAWFIXED 会触发失败的断言:

    // 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
            
        
    

【问题讨论】:

这也不例外。这是一个失败的调试断言。该对话框会告诉您如何继续。 我在这里犯了一个错误,对不起。现在我明白了,由于某种原因,该函数中有 ASSERT(FALSE)。我需要找到另一种方法,继续寻找答案。 【参考方案1】:

经过长时间的调查......我为你找到了一个解决方案:使用SetItemState ()魔法函数:

备注:多列不是问题

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);

【讨论】:

AFAIK LBS_OWNERDRAWVARIABLE 强制系统调用 MeasureItem 函数,但我可以将项目移到左边吗?该函数向我发送MEASUREITEMSTRUCT 结构,但没有我可以操作的“左边框”字段。另外,我需要动态创建列表框,而不是通过工具箱,但我下次会先尝试,谢谢。 @mrdecompilator 我知道您是动态创建控件的,只是为了在未来帮助您:) 发布您的DrawItem () 函数的完整代码 现在它是空的,但是DrawItem 发送DRAWITEMSTRUCT,它有rcItem 字段与项目的矩形,所以我可以做类似rcItem.left -= 20 的事情。我已经使用 CCheckListBox 进行了这项工作,但后来我意识到,用户将来可能希望添加多个列。 @mrdecompilator 在编辑后的答案代码中,我用网格来调整这两列 感谢您精疲力竭的回答!出于某种原因,使用此代码列表包含网格,但似乎没有项目。然后我发现,它们在那里,但都是白色的(可以选择但看不到)。当我删除整个switch 时,这些项目正常显示。现在的问题是,没有复选框的行有一个空白区域而不是复选框。我需要这样一行中的文本来代替不存在的复选框(如我图片中的 item2) - 不幸的是,这是必须的,pLPN->rcText.left += 20 没有做任何事情。

以上是关于MFC - 带有可选复选框的 CListCtrl 行的主要内容,如果未能解决你的问题,请参考以下文章

MFC中CListCtrl添加复选框,实现单选

MFC中CListCtrl添加复选框,实现单选

MFC-CListCtrl-SetCheck设置复选框的状态

有没有办法覆盖当用户单击 CListCtrl 中的复选框时调用的处理程序? (MFC)

MFC - 如何禁用列表项?

MFC总结之CListCtrl用法及技巧