Click here to Skip to main content
16,016,882 members
Articles / Programming Languages / C++

An Extended MFC CListCtrl to edit individual cells, sort headers, and more

Rate me:
Please Sign up or sign in to vote.
3.65/5 (54 votes)
23 Jul 2008CPOL 243.8K   17.2K   70   84
The MFC CListCtrl does not allow editing labels for all columns. This extended class implements ways to specify column editors, row, cell, and column colors etc.

Introduction

This article explains a very flexible way to specify a row, column, or cell editor, color or sorting features.

untitled1.JPG

Background

I was looking for a list control that allows me to edit each column label separately and specify the editor of my choice like a DateTimePicker, or ComboBox, or TextBox, or even a dialog box. So, I extended and created this list control.

Using the code

Using CListCtrlEx is very similar to using CListCtrl. A few extra methods have been added to ease label editing and sorting. Following are the additional functions added to the list control:

C++
void SetColumnEditor(int nColumn, PFNEDITORCALLBACK pfnInitEditor, 
     PFNEDITORCALLBACK m_pfnEndEditor = NULL,  CWnd* pWnd = NULL);
void SetColumnEditor(int nColumn, CWnd* pWnd);
void SetCellEditor(int nRow, int nColumn, PFNEDITORCALLBACK pfnInitEditor, 
     PFNEDITORCALLBACK m_pfnEndEditor = NULL,  CWnd* pWnd = NULL);
void SetCellEditor(int nRow, int nColumn, CWnd* pWnd);
void SetRowEditor(int nRow, PFNEDITORCALLBACK pfnInitEditor, 
     PFNEDITORCALLBACK m_pfnEndEditor = NULL,  CWnd* pWnd = NULL);
void SetRowEditor(int nRow, CWnd* pWnd);
void SetDefaultEditor(PFNEDITORCALLBACK pfnInitEditor, 
     PFNEDITORCALLBACK m_pfnEndEditor = NULL,  CWnd* pWnd = NULL);
void SetDefaultEditor(CWnd* pWnd); 
         
void SetColumnReadOnly(int nColumn, bool bReadOnly = true);
void SetCellReadOnly(int nRow, int nColumn, bool bReadOnly = true);
void SetRowReadOnly(int nRow, bool bReadOnly = true);

void SetRowColors(int nItem, COLORREF clrBk, COLORREF clrText);
void SetColumnColors(int nColumn, COLORREF clrBack, COLORREF clrText);
void SetCellColors(int nRow, int nColumn, COLORREF clrBack, COLORREF clrText);
 
BOOL DisplayEditor(int nItem, int nSubItem);
void HideEditor(BOOL bUpdate = TRUE);

void DeleteSelectedItems(void);
//Delete selected items when delete key is pressed
void HandleDeleteKey(BOOL bHandle = TRUE);
void SelectItem(int nItem, BOOL bSelect);
BOOL DeleteAllColumns(void);
BOOL Reset(void);

void SetColumnSorting(int nColumn, Sort eSort, Comparer eSortType = String);
void SetColumnSorting(int nColumn, Sort eSort, PFNLVCOMPARE fnCallBack);
BOOL SortOnColumn(int nColumn, BOOL bChangeOrder = FALSE);
 
BOOL IsColumnReadOnly(int nColumn);
BOOL IsRowReadOnly(int nRow);
BOOL IsCellReadOnly(int nRow, int nColumn);

The following code explains how to use the extended list control in your dialog:

C++
BOOL CDemoDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    ...
    //Set image list to increase row height
    m_imgList.Create(1, 20, ILC_COLOR, 0, 1);
    m_lstDemo.SetImageList(&m_imgList, LVSIL_SMALL);
    FillListBox();
    m_lstDemo.SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);
    //set full row select and grid lines

    return TRUE;
    // return TRUE  unless you set the focus to a control
}

void CDemoDlg::AddColumns(void)
{
    m_lstDemo.InsertColumn(0, "Default Editor", LVCFMT_LEFT, 100);
    m_lstDemo.InsertColumn(1, "Date Time Editor", LVCFMT_LEFT, 100);
    m_lstDemo.InsertColumn(2, "Combobox Editor", LVCFMT_LEFT, 100);
    m_lstDemo.InsertColumn(3, "Color Select", LVCFMT_LEFT, 150);
    m_lstDemo.InsertColumn(4, "Read Only Column", LVCFMT_LEFT, 100);
    m_lstDemo.InsertColumn(5, "Just a Column", LVCFMT_LEFT, 100);

    m_lstDemo.SetColumnEditor(1, &CDemoDlg::InitEditor, 
                                 &CDemoDlg::EndEditor, &m_wndDT);
    m_lstDemo.SetColumnEditor(2, &CDemoDlg::InitEditor, 
                                 &CDemoDlg::EndEditor, &m_wndCB);
    m_lstDemo.SetColumnEditor(3, &CDemoDlg::InitEditor, 
                                 &CDemoDlg::EndEditor, &m_dlgColor);
    m_lstDemo.SetColumnReadOnly(4);
    m_lstDemo.SetDefaultEditor(NULL, NULL, &m_wndEdit);
    m_lstDemo.SetColumnColors(4, RGB(200, 200, 200), RGB(128, 128, 128));
    m_lstDemo.SetColumnSorting(0, CListCtrlEx::Auto, CListCtrlEx::StringNoCase);
    m_lstDemo.SetColumnSorting(1, CListCtrlEx::Auto, CListCtrlEx::Date);
    m_lstDemo.SetColumnSorting(2, CListCtrlEx::Auto, CListCtrlEx::String);
    m_lstDemo.SetColumnSorting(3, CListCtrlEx::Auto, CListCtrlEx::StringNoCase);
    m_lstDemo.SetColumnSorting(4, CListCtrlEx::Auto, CListCtrlEx::StringNoCase);
}

void CDemoDlg::FillListBox(void)
{
    m_lstDemo.Reset();
    AddColumns();
    CString strDate = COleDateTime(CTime::GetCurrentTime().GetTime()).Format();
    for(int i = 0; i < 20; i++)
    {
        CString str;
        str.Format("Some %d Text %d", rand(), rand());
        m_lstDemo.InsertItem(i, str);
        m_lstDemo.SetItemText(i, 1, strDate);
        m_lstDemo.SetItemText(i, 2, "text1");
        m_lstDemo.SetItemText(i, 3, "Some Text");
        m_lstDemo.SetItemText(i, 4, "Read Only");
        m_lstDemo.SetItemText(i, 5, "Some Text");
        if(i%9 == 3)
        {
            m_lstDemo.SetRowColors(i, -1, RGB(255, 0, 0));
            m_lstDemo.SetRowEditor(i, NULL, NULL, &m_wndEdit);
        }
        if(i % 7 == 0)
        {
            m_lstDemo.SetCellColors(i, 5, RGB(0, 255, 0), RGB(255, 255, 255));
            m_lstDemo.SetCellEditor(i, 5, &CDemoDlg::InitEditor, 
                                    &CDemoDlg::EndEditor, &m_wndDT);
        }
        if(i % 8 == 0) m_lstDemo.SetCellColors(i, 5, RGB(0, 255, 0), -1);
    }
    
}
//Call back function to initialize the cell editor.
BOOL
CDemoDlg::InitEditor(CWnd** pWnd, int nRow, int nColumn, CString&
strSubItemText, DWORD_PTR dwItemData, void* pThis, BOOL bUpdate)
{
    ASSERT(*pWnd);
    switch(nColumn)
    {
    case 1:
    case 5:
        {
            CDateTimeCtrl *pDTC = dynamic_cast<CDateTimeCtrl *>(*pWnd);
            COleDateTime dt;
            if(dt.ParseDateTime(strSubItemText)) pDTC->SetTime(dt);
        }
        break;
    case 2:
        {
            CComboBox *pCmb = dynamic_cast<CComboBox *>(*pWnd);
            pCmb->SelectString(0, strSubItemText);
        }
        break;
    case 3:
        {
            CDlgColor *pDlg = dynamic_cast<CDlgColor *>(*pWnd);        
            pDlg->m_nColor = strSubItemText.CompareNoCase("green")? 
               (strSubItemText.CompareNoCase("blue")?0:2):1;
            pDlg->Create(CDlgColor::IDD, (CWnd*)pThis);            
            pDlg->UpdateData(FALSE);
        }
        break;
    }
    return TRUE;
}
 
//Call back function to end and destroy the cell editor.
//Spacial feature return -1 to sort list control items 
//based on the current editing item.
BOOL CDemoDlg::EndEditor(CWnd** pWnd, int nRow, int nColumn, 
                         CString& strSubItemText, 
                         DWORD_PTR dwItemData, 
                         void* pThis, BOOL bUpdate)
{
    ASSERT(pWnd);
    switch(nColumn)
    {
    case 1:
    case 5:
        {
            CDateTimeCtrl *pDTC = dynamic_cast<CDateTimeCtrl *>(*pWnd);
            COleDateTime dt;
            pDTC->GetTime(dt);
            strSubItemText = dt.Format();
        }
        break;
    case 2:
        {
            CComboBox *pCmb = dynamic_cast<CComboBox *>(*pWnd);
            int index = pCmb->GetCurSel();
            if(index >= 0) pCmb->GetLBText(index, strSubItemText);
        }
        break;
    case 3:
        {
            CDlgColor *pDlg = dynamic_cast<CDlgColor *>(*pWnd);
            CListCtrlEx *pList = reinterpret_cast<CListCtrlEx *>(pThis);
            pDlg->UpdateData(TRUE);
            switch(pDlg->m_nColor)
            {
            case 1:
                strSubItemText = "Green";
                pList->SetCellColors(nRow, nColumn, RGB(0, 255, 0), -1);
                break;
            case 2:
                strSubItemText = "Blue";
                pList->SetCellColors(nRow, nColumn, RGB(0, 0,255 ), -1);
                break;
            default:
                strSubItemText = "Red";
                pList->SetCellColors(nRow, nColumn, RGB(255, 0, 0), -1);
                break;
            }                
            pDlg->DestroyWindow();
        }
        break;
    }
    return TRUE;
}

I hope the code is self explanatory; if not, just post a question. Also have a look at the demo project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
India India
I have been involved in C, C++ development for over a six year now. I have worked on projects involving games, insurance application, stats and reporting applications. Being busy with work I hardly got time to post my own articles but now I am getting more involved. I don't promise much but hopefully you'll see some more interesting articles from me in near future.

Comments and Discussions

 
GeneralScreen Flicker Pin
mattfaramir3314-Mar-10 8:37
mattfaramir3314-Mar-10 8:37 
QuestionDraw symbol in sub item after combobox hidden? Pin
JezzerP7-Dec-09 23:28
JezzerP7-Dec-09 23:28 
GeneralUser Data Pin
mhalaj29-Oct-09 0:36
mhalaj29-Oct-09 0:36 
GeneralRe: User Data Pin
Sanjay198229-Oct-09 4:51
Sanjay198229-Oct-09 4:51 
QuestionControls visible all the time Pin
JezzerP27-Oct-09 0:52
JezzerP27-Oct-09 0:52 
Question[Message Deleted] Pin
JezzerP28-Oct-09 0:25
JezzerP28-Oct-09 0:25 
AnswerRe: Controls visible all the time Pin
Sanjay198228-Oct-09 4:24
Sanjay198228-Oct-09 4:24 
GeneralRe: Controls visible all the time Pin
JezzerP28-Oct-09 5:51
JezzerP28-Oct-09 5:51 
Hi Sanjay,

Many thanks for replying. Part of the application I am working on is for displaying an X-Y cross plot of data, and it is possible to group the data displaying the different groups using a different coloured symbol. What I have are two custom comboboxes, one that is used to select a colour, the other a symbol. I need to display a list of groups that the user can edit the properties of within the ListCtrl without resorting to a separate dialog. The multiple comboboxes need to be displayed at all times because the user needs to be able to see what the current selection of colour/symbol are.

Anyway, I actually think I have managed to get your ListCtrl to do what I require. At the moment I have multiple comboboxes as part of the dialog resources and call DisplayEditor on them in the FillListBox() function in the dialog class:

m_lstCtrl.SetCellEditor(0, 1, &CTestingDialog::InitEditor, &CTestingDialog::EndEditor, &m_wndSymbolCombo);
m_lstCtrl.SetCellEditor(1, 1, &CTestingDialog::InitEditor, &CTestingDialog::EndEditor, &m_wndSymbolCombo2);
m_lstCtrl.DisplayEditor(0,1);
m_lstCtrl.DisplayEditor(1,1);


However, seeing the number of groups will be variable, I assume I will have to create these on-the-fly when required in the InitEditor function.

In the DisplayEditor function I have moved the call to HideEditor to below the ASSERT on m_pEditor and added a condition as follows:

ASSERT(m_pEditor->m_pWnd->GetParent() == this);

if(!m_pEditor->m_pWnd->IsKindOf(RUNTIME_CLASS(CSymbolCombo)))
    HideEditor();


At the top of the HideEditor function I have modified the code as follows:

if(m_pEditor && m_pEditor->m_pWnd && !m_pEditor->m_pWnd->IsKindOf(RUNTIME_CLASS(CSymbolCombo)))
{
    m_pEditor->m_pWnd->ShowWindow(SW_HIDE);


All other control types behave as normal and are only shown on a double-click. Obviously these changes are only applicable to my own code and classes, and not generic in any way, but it shows that it is possible. If you can think of a more generic way to do this then I'd be happy to have your thoughts on the matter.

Again, many thanks for the control and for replying Smile | :)

Jeremy

___________________________
Jeremy Preston

GeneralRe: Controls visible all the time Pin
Sanjay198228-Oct-09 7:21
Sanjay198228-Oct-09 7:21 
GeneralRe: Controls visible all the time Pin
JezzerP28-Oct-09 23:10
JezzerP28-Oct-09 23:10 
GeneralRe: Controls visible all the time Pin
JezzerP29-Oct-09 0:46
JezzerP29-Oct-09 0:46 
GeneralIf I use ListCtrlEx in SDI project Pin
_Marina_2-Oct-09 2:04
_Marina_2-Oct-09 2:04 
GeneralRe: If I use ListCtrlEx in SDI project Pin
Sanjay19822-Oct-09 3:17
Sanjay19822-Oct-09 3:17 
GeneralRe: If I use ListCtrlEx in SDI project Pin
_Marina_2-Oct-09 4:44
_Marina_2-Oct-09 4:44 
GeneralRe: If I use ListCtrlEx in SDI project Pin
_Marina_2-Oct-09 5:05
_Marina_2-Oct-09 5:05 
GeneralRe: If I use ListCtrlEx in SDI project Pin
Sanjay19822-Oct-09 7:48
Sanjay19822-Oct-09 7:48 
GeneralRe: If I use ListCtrlEx in SDI project Pin
Sanjay19822-Oct-09 16:28
Sanjay19822-Oct-09 16:28 
GeneralRe: If I use ListCtrlEx in SDI project Pin
mesajflaviu19-Mar-10 1:47
mesajflaviu19-Mar-10 1:47 
GeneralRe: If I use ListCtrlEx in SDI project Pin
mesajflaviu4-May-10 0:16
mesajflaviu4-May-10 0:16 
Generalwarning C4541: 'dynamic_cast' used on polymorphic type 'CWnd' with /GR-; unpredictable behavior may result Pin
bingqitan2-Apr-09 21:24
bingqitan2-Apr-09 21:24 
GeneralRe: warning C4541: 'dynamic_cast' used on polymorphic type 'CWnd' with /GR-; unpredictable behavior may result Pin
Sanjay198216-Apr-09 6:20
Sanjay198216-Apr-09 6:20 
GeneralVery nice article Pin
sach!!24-Mar-09 1:20
sach!!24-Mar-09 1:20 
GeneralRelease Pin
ERLN21-Feb-09 7:59
ERLN21-Feb-09 7:59 
GeneralRe: Release Pin
Sanjay198216-Apr-09 6:22
Sanjay198216-Apr-09 6:22 
QuestionRe: Release Pin
ERLN16-Apr-09 7:20
ERLN16-Apr-09 7:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.