. Home Feedback Contents Search

ListCtrl 

Back Up Next

Adding An Item To The List

Deleting items

m_ctlListCIT.DeleteItem( iRow );

Editing cells

Finding first selected item

Getting the index of a selected item is an exercise in search for the next item that contains a particular status attribute. Good programming practice says that, since it is possible for nothing to be currently selected, that the program take this failure condition into account and react accordingly.

int iRow = m_ctlList.GetNextItem( -1, LVIS_SELECTED );
if ( 0 > iRow )
    break;

Finding the cell upon which a click event occurred

One of the big lacks in this class is an easy way to tell exactly where the last click event transpired. This is particularly perplexing since one of the common programming problems this control has to deal with is handling a user’s click on a particular cell. Here is some sample code that solves this particular problem.

Let’s assume that we have a point in client coordinates that we would like to find the corresponding CListCtrl item. Let’s further assume that the list control is in list mode. Furthermore, we can safely assume that I am one good looking, intelligent, witty and amusing man that no woman in her right mind could possibly refuse anything at all. I’m particularly partial to that last assumption but, in all fairness, that has almost nothing to do with the problem we want to solve. I just wanted to live in the glow of a life that does not always overlap reality as is commonly defined.

So, back to our list control, here’s an example of how to find the row and column that lies underneath this click event.

// Return the column and row in which the specified point resides.
// If the point does not overlap a cell item then return (-1, -1).
// Assume that the CListCtrl is in detail mode.

CPoint ptGetCellFromClick( CPoint pt )
    {
    CPoint ptReturn( -1, -1 );

    Do {
        // The list control may be scrolled. If so, there’s no point in looking
        // at rows that off the screen to the top so we’ll start with the
        // first visible row, iterate through the rows until we find
        // the row that contains the click event.

        bool bFound = false;        // Indicates search success/failure.
        int iMaxY = GetItemCount();

        for ( int iRow = GetTopIndex(); iRow<iMaxY;  iRow++ )
            {

            // Get this item’s rectangular position.
            // Since we are looking at rows only at this time,
            // we can ask for the left most item and get
            // the upper and lower bounds from that.

            CRect    cr;
            GetSubItemRect( iRow, 0, LVIR_BOUNDS, cr );

            // If this isn't the row where the click happened then continue.
            // We check to see if the top of the rect is above or equal to
            // the target point at the same time that the bottom of the rect
            // is below the target point. If either test fails then
            // this isn’t the right row.

            if ( cr.top > pt.y || pt.y >= cr.bottom )
                continue;

            // Found the right row. Stop looking.

            bFound = true;        // Indicate successful search.
            break;            // Exit the loop.
            }

        // Did we find the row? If not, quit.

        if ( !bFound )
            break;

        // Now that we know what row the event transpired in, let’s
        // look for the column. Iterate through the columns until we
        // find a column right that encloses the X of the button up position.
        // This is complicated by the fact that we don’t really know how many
        // columns we have but we can figure it out by starting at the left
        // and working to the right, asking for columns as we go. When we fail
        // to get a column then we know that we’ve gone all of the way to the end.

        bFound = false;    // Haven’t found it yet.
        BOOL b;
        int iCol = 0;    // Start from the left most column.
        int x=0;        // Keeps track of where the start of the column is.
        LVCOLUMN Column;
        Column.mask = LVCF_WIDTH;
        for ( b=GetColumn( iCol, &Column ); b; b=GetColumn( ++iCol, &Column ) )
            {

            // Is this the column wherein the click event transpired?
            // x is the accumulated left coord of the columns that we have
            // inspected so far.
            // Column.cx is the width of the column we’ve just
            // retrieved  information about.

            if ( x <= pt.x && pt.x < x+Column.cx  )
                {
                bFound = true;
                break;
                }

            // Each time we come through here without finding the target
            // column, we will have to bump our x value so that the next
            // time we look at x it will contain the new column’s leftmost
            // coord.

            x += Column.cx;
            }

        // If we didn't find the column then quit.

        if (!bFound)
            break;

        // We now know the column and row in which the event transpired.
        // Load up the return value.

        PtReturn.x = iCol;
        PtReturn.y = iRow;
        } while (false);

    return ptReturn;
}

Finding an item with specified text, or data.

Owner draw

Full row select

This is a new talent for the CListCtrl. It used to require a lot of programming and doing owner draw processing to get a selected CListCtrl row to display for the entire width of the control. Now, all you have to do is set a style.

    m_ctlList.SetExtendedStyle( LVS_EX_FULLROWSELECT );

Programmatically select/deselect

ctl.SetItemState( iRow, LVIS_SELECTED, LVIS_SELECTED );
ctl.SetItemState( iRow, LVIS_SELECTED, 0 );

Select row from click on any column

m_ctlListCIT.SetExtendedStyle( LVS_EX_FULLROWSELECT );

Get column width

    LV_COLUMN col;
    col.mask = LVCF_WIDTH;
    ctl.GetColumn( iZeroRelColFromLeft, &col);

Save and restore column width

A nice touch in a user interface is to have the application restore itself in its last location and size. Part and parcel of that is to have any embedded CListCtrls restore their column widths. This sample code shows you have to do that.

 // Iterate through the specified CListCtrl's columns, get the width of each
// and save to this app's area in the registry.

void CMyView::vSaveColWidths( CListCtrl& ctl, LPCTSTR lpszTitle )
    {
    LV_COLUMN col;

    col.mask = LVCF_WIDTH;

    CString cs;
    for ( int x=0; ctl.GetColumn(x, &col); x++ )
        {
        cs.Format( _T("%s Col%d"), lpszTitle, x+1 );
        AfxGetApp()->WriteProfileInt( _T("Settings"), cs, col.cx );
        }
    }


// Iterate through the specified CListCtrl's columns, get the width of each
// and save to this app's area in the registry.

void CMyView::vSetColWidths( CListCtrl& ctl, LPCTSTR lpszTitle )
    {
    LV_COLUMN col;

    col.mask = LVCF_WIDTH;

    CString cs;
    for ( int x=0; ctl.GetColumn(x, &col); x++ )
        {

        // Format the key where this column width is stored.

        cs.Format( _T("%s Col%d"), lpszTitle, x+1 );

        // Ask the regisitry to cough up the column width. If it
        // can't find it then a 0 will be returned.

        int iWidth = AfxGetApp()->GetProfileInt( _T("Settings"), cs, 0 );

        // If no column width came back then no point in continuing.

        if (!iWidth)
            break;

        // We have a column width. Assert it to the list control.

        ctl.SetColumnWidth( x, iWidth );
        }
    }

Responding to events

Responding to selection change event

In this example of responding to a selection event, we detect if we are gaining or losing the selection and react appropriately.

 void CMyView::OnItemchangedListCitg(NMHDR* pNMHDR, LRESULT* pResult)
    {
    NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

    do    {

        // Did the selection state for an item change?
        // If not then do nothing.

        if ( !((pNMListView->uNewState|pNMListView->uOldState) & LVIS_SELECTED) )
            break;

        // If used to be selected but is now deselected then
        // react to the new condition.

        if ( !(pNMListView->uNewState&LVIS_SELECTED) )
            {
            // Do whatever is appropriate to reflect no longer selected.
            break;
            }

        // If here then used to be deselected but is selected now.
        // React to the new condition.

        // Do whatever is appropriate to reflect newly selected state.

        } while (false);
   
    // Always return 0 so that the base classes will process the event, too.

    *pResult = 0;
    }

Back Up Next