wxListCtrl

From WxWiki
Jump to: navigation, search
Official Classes SmallBlocks.png Archive Containers Controls Data Structures Database Date & Time Debug Device Contexts Dialogs Document & Views Drag & Drop Events Filesystem Frames Graphics Grid Cell Help HTML Logging Miscellaneous Networking Printing Sizers Streams Threading Windows

A list control presents lists in a number of formats: list view, report view, icon view and small icon view.

In any case, elements are numbered from zero. For all these modes, the items are stored in the control and must be added to it using wxListCtrl::InsertItem method.

A special case of report view quite different from the other modes of the list control is a virtual control in which the items data (including text, images and attributes) is managed by the main program and is requested by the control itself only when needed which allows to have controls with millions of items without consuming much memory. To use virtual list control you must use wxListCtrl::SetItemCount first and override at least wxListCtrl::OnGetItemText (and optionally wxListCtrl::OnGetItemImage or wxListCtrl::OnGetItemColumnImage and wxListCtrl::OnGetItemAttr) to return the information about the items when the control requests it.

Virtual list control can be used as a normal one except that no operations which can take time proportional to the number of items in the control happen – this is required to allow having a practically infinite number of items. For example, in a multiple selection virtual list control, the selections won't be sent when many items are selected at once because this could mean iterating over all the items.

Using many of wxListCtrl features is shown in the corresponding sample.

To intercept events from a list control, use the event table macros described in wxListEvent.

Note: Starting with wxWidgets 2.8 (wxMac), wxListCtrl uses a native implementation for report mode, and uses a generic implementation for other modes. You can use the generic implementation for report mode as well by setting the mac.listctrl.always_use_generic system option (see wxSystemOptions) to 1.

If wxListCtrl has more features than you need, have a look at wxListView. For a simpler listing control, look at wxListBox. For a more spreadsheet-like control, please look at wxGrid

Minimal example to get started

// Example uses fictive class "Item" and fictive getters to access them. Adapt for your needs
 
class MyFrame : public wxFrame
{
    wxListCtrl* m_item_list;
 
public:
    MyFrame() : wxFrame(NULL, wxID_ANY,  wxT("Hello wxWidgets"), wxPoint(50,50), wxSize(800,600))
    {
        wxPanel* mainPane = new wxPanel(this);
        wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
 
        m_item_list = new wxListCtrl(mainPane, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT);
 
        // Add first column       
        wxListItem col0;
        col0.SetId(0);
        col0.SetText( _("Foo") );
        col0.SetWidth(50);
        m_item_list->InsertColumn(0, col0);
 
        // Add second column
        wxListItem col1;
        col1.SetId(1);
        col1.SetText( _("Name") );
        m_item_list->InsertColumn(1, col1);
 
        // Add third column     
        wxListItem col2;
        col2.SetId(2);
        col2.SetText( _("Comments") );
        m_item_list->InsertColumn(2, col2);
 
 
        const int item_amount = getItemAmount();
        for (int n=0; n<item_amount; n++)
        {
            Item* curritem = getItem(n);
 
            wxListItem item;
            item.SetId(n);
            item.SetText( curritem->getName() );
 
            m_item_list->InsertItem( item );
 
            // set value in first column
            if (!curritem->isFoo())
            {
                m_item_list->SetItem(n, 0, wxT("Foo"));
            }
            else
            {
                m_item_list->SetItem(n, 0, wxT("Bar"));
            }
 
            // set value in second column
            m_item_list->SetItem(n, 1, curritem->getName());
 
            // set value in third column
            m_item_list->SetItem(n, 2, curritem->getDescription());
 
        }
 
        sizer->Add(m_item_list,1, wxEXPAND | wxALL, 10);
        mainPane->SetSizer(sizer);
    }
};
 
class MyApp: public wxApp
{
    wxFrame* m_frame;
public:
 
    bool OnInit()
    {
        m_frame = new MyFrame();
        m_frame->Show();
        return true;
    } 
 
};
 
IMPLEMENT_APP(MyApp)

Minimal virtual list example to get started

class SearchResultsList: public wxListCtrl{
	public:
		SearchResultsList(wxWindow* parent);
		wxString OnGetItemText(long item, long column) const;
};
 
//Constructor, sets up virtual report list with 3 columns
SearchResultsList::SearchResultsList(wxWindow* parent):
					wxListCtrl(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_VIRTUAL){
	// Add first column        
	wxListItem col0; 
	col0.SetId(0); 
	col0.SetText( _("Track") ); 
	col0.SetWidth(300);
	InsertColumn(0, col0); 
 
	// Add second column 
	wxListItem col1; 
	col1.SetId(1); 
	col1.SetText( _("Score") );
	col1.SetWidth(50);
	InsertColumn(1, col1); 
 
	// Add third column      
	wxListItem col2; 
	col2.SetId(2); 
	col2.SetText( _("ID") ); 
	col2.SetWidth(100);
	InsertColumn(2, col2); 
 
	//This should reflect your data
	SetItemCount(30);
}
 
//Overload virtual method of wxListCtrl to provide text data for virtual list
wxString SearchResultsList::OnGetItemText(long item, long column) const{
	//Use item and column to return the correct data for that particular cell. This example just returns "bawls" no matter what
	return _("bawls");
}

Adding items in a multiple column list

You need to do an InsertItem first, and store the item index. And then SetItem()s for the columns after the first.

long itemIndex = WxListCtrl1->InsertItem(0, "1"); //want this for col. 1
WxListCtrl1->SetItem(itemIndex, 1, "18:00"); //want this for col. 2
WxListCtrl1->SetItem(itemIndex, 2, "17 18 56 48 29 13"); //col. 3

Getting the selected item indexes

There is no simple function to return the currently selected index(es). Instead, you must iterate over all the items and check if they are selected or not. It can be done using GetNextItem with the appropriate flags.

  long itemIndex = -1;
 
  while ((itemIndex = listControl->GetNextItem(itemIndex,
          wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != wxNOT_FOUND) {
    // Got the selected item index
    wxLogDebug(listControl->GetItemText(itemIndex));
  }

Adding Images

m_pImageList = new wxImageList(16,16);
wxIcon icon;
icon.LoadFile(wxT("res/user_offline.ico"), wxBITMAP_TYPE_ICO);
m_pImageList->Add(icon);
SetImageList(m_pImageList, wxIMAGE_LIST_SMALL);

And when you add an item, after that call

SetItemImage(item, 0);

Important: The image list must be created and assigned to the wxListCtrl before adding any items it.

Otherwise, you will not get any error message but the list will not work as expected.

Deleting Selected Rows

Rows (or items) in a listctrl are indexed starting from zero. You often want to delete some items that the user has selected. Get the index numbers of the selected items, and then looping through, delete the selected items. Warning If you delete items starting at the lowest index, the higher index numbers will now point to different items than they did before the deletion. Subsequent deletions will erase the wrong items. Rather loop through starting with the highest index first. For example:

// Make a list of selected row numbers that you want to delete by 
// whatever means your application decides when ones should be deleted.
wxArrayInt row_numbers_to_delete = ......
 
// Delete in reverse order.
for ( long n = ( row_numbers_to_delete.GetCount() -1 ); 0 <= n; n-- )
{
   // Remove it from the listctrl.
   DeleteItem( row_numbers_to_delete[ n ] );
}
 
// Usual clearing of arrays to free memory.
row_numbers_to_delete.Clear();

Get the String Contents of a "cell" in a LC_REPORT wxListCtrl

The functionality to extract a string from a wxListCtrl "cell" in report view is somewhat rough for a newcomer to wxWidgets who is looking through the API. By "cell", we mean the value located at a certain "row" under a certain "column". This code snippet will return the string at that numbered row and column. Each row and column starts at index number zero. Place this custom function in your derived wxListCtrl.

wxString MyListCtrl::GetCellContentsString( long row_number, int column ) 
{
   wxListItem     row_info;  
   wxString       cell_contents_string;
 
   // Set what row it is (m_itemId is a member of the regular wxListCtrl class)
   row_info.m_itemId = row_number;
   // Set what column of that row we want to query for information.
   row_info.m_col = column;
   // Set text mask
   row_info.m_mask = wxLIST_MASK_TEXT;
 
   // Get the info and store it in row_info variable.   
   GetItem( row_info );
 
   // Extract the text out that cell
   cell_contents_string = row_info.m_text; 
 
   return cell_contents_string;
}

An alternative definition:

bool MyListCtrl::GetCellContentsString( long row_number, int column, wxString& cell_contents_string ) const
{
   wxListItem row_info;
 
   // Set what row it is (m_itemId is a member of the regular wxListCtrl class)
   row_info.m_itemId = row_number;
   // Set what column of that row we want to query for information.
   row_info.m_col = column;
   // Set text mask
   row_info.m_mask = wxLIST_MASK_TEXT;
 
   // Get the info and store it in row_info variable.
   if (GetItem( row_info ))
   {
       // Extract the text out of that cell
       cell_contents_string = row_info.m_text;
       return true;
   }
 
   return false;
}

Select or Deselect an Item

More constraints - see help: wxListCtrl::SetItem

// Deselect item (wxLIST_STATE_FOCUSED - dotted border)
wxListCtrl->SetItemState(item, 0, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
 
// Select item
wxListCtrl->SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);

Note that the second parameter, stateMask in wxListCtrl::SetItemState is not the same as the mask used in wxListItem (although the actual flags are the same). The stateMask simply specifies which flags in state are valid. As the above example shows, to select an item, both the state and stateMask must be set to wxLIST_STATE_SELECTED.

Implement wxListCtrl with Checkboxes

This is quite easy using checked and unchecked images. If you want a more complete version of wxCheckedListCtrl with events for checking and unchecking and completely transparent wxWidgets behaviour, see WebUpdate at wxCode. It contains a wxCheckedListCtrl implementation which can be extracted and used separately from WebUpdate. Just move the header and source file, checkedlistctrl.cpp/.h into your project. [seems no longer true: the header also has dependencies on webupdatedef.h and the cpp references checked/unchecked images, so a bit of editing is required.]

#include <bitmaps/checked.xpm>
#include <bitmaps/unchecked.xpm>
 
IMPLEMENT_CLASS(wxCheckedListCtrl, wxListCtrl)
 
BEGIN_EVENT_TABLE(wxCheckedListCtrl, wxListCtrl)
  EVT_LEFT_DOWN(wxCheckedListCtrl::OnMouseEvent)
END_EVENT_TABLE()
 
wxCheckedListCtrl::wxCheckedListCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pt,
   const wxSize& sz, long style):
   wxListCtrl(parent, id, pt, sz, style), m_imageList(16, 16, TRUE)
   {
   SetImageList(&m_imageList, wxIMAGE_LIST_SMALL);
 
   m_imageList.Add(wxICON(unchecked));
   m_imageList.Add(wxICON(checked));
 
   InsertColumn(0, _("Item"), wxLIST_FORMAT_LEFT, 200);
   InsertColumn(1, _("Value"), wxLIST_FORMAT_LEFT, 80);
   }
 
   void wxCheckedListCtrl::OnMouseEvent(wxMouseEvent& event)
   {
      if (event.LeftDown())
      {
         int flags;
         long item = HitTest(event.GetPosition(), flags);
         if (item > -1 && (flags & wxLIST_HITTEST_ONITEMICON))
         {
             SetChecked(item, !IsChecked(item));
         }
         else
            event.Skip();
      }
      else
      {
         event.Skip();
      }
   }
 
bool wxCheckedListCtrl::IsChecked(long item) const
{
   wxListItem info;
   info.m_mask = wxLIST_MASK_IMAGE ;
   info.m_itemId = item;
 
   if (GetItem(info))
   {
      return (info.m_image == 1);
   }
   else
      return FALSE;
}
 
void wxCheckedListCtrl::SetChecked(long item, bool checked)
{
   SetItemImage(item, (checked ? 1 : 0), -1);
}


If you'd like to use native checkboxes replace the bitmap XPMs with this bit of code in wxCheckedListCtrl::Create(...).

bool wxCheckedListCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint& pt,
        const wxSize& sz, long style, const wxValidator& validator, const wxString& name)
{
    if (!wxListCtrl::Create(parent, id, pt, sz, style, validator, name))
        return FALSE;
 
    // Get the native size of the checkbox
    int width = wxRendererNative::Get().GetCheckBoxSize(this).GetWidth();
    int height = wxRendererNative::Get().GetCheckBoxSize(this).GetHeight();
 
    m_imagelist = new wxImageList(width, height, TRUE);
    SetImageList(m_imagelist, wxIMAGE_LIST_SMALL);
 
    wxBitmap unchecked_bmp(width, height),
             checked_bmp(width, height),
             unchecked_disabled_bmp(width, height),
             checked_disabled_bmp(width, height);
 
    wxMemoryDC renderer_dc;
 
    // Unchecked
    renderer_dc.SelectObject(unchecked_bmp);
    renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
    renderer_dc.Clear();
    wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), 0);
 
    // Checked
    renderer_dc.SelectObject(checked_bmp);
    renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
    renderer_dc.Clear();
    wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED);
 
    // Unchecked and Disabled
    renderer_dc.SelectObject(unchecked_disabled_bmp);
    renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
    renderer_dc.Clear();
    wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), 0 | wxCONTROL_DISABLED);
 
    // Checked and Disabled
    renderer_dc.SelectObject(checked_disabled_bmp);
    renderer_dc.SetBackground(*wxTheBrushList->FindOrCreateBrush(GetBackgroundColour(), wxSOLID));
    renderer_dc.Clear();
    wxRendererNative::Get().DrawCheckBox(this, renderer_dc, wxRect(0, 0, width, height), wxCONTROL_CHECKED | wxCONTROL_DISABLED);
 
    // Deselect the renderers Object
    renderer_dc.SelectObject(wxNullBitmap);
 
    // the add order must respect the wxCLC_XXX_IMGIDX defines in the headers !
    m_imageList.Add(unchecked_bmp);
    m_imageList.Add(checked_bmp);
    m_imageList.Add(unchecked_disabled_bmp);
    m_imageList.Add(checked_disabled_bmp);
 
    return TRUE;
}
wxCheckedListCtrl list = new wxCheckedListCtrl(this, 
   wxID_ANY,wxDefaultPosition,wxDefaultSize,wxLC_REPORT|wxLC_SINGLE_SEL);
list->InsertColumn(0, wxT("Item"));     
list->InsertItem(0, "ABC");
list->Check(0, FALSE);
list->InsertItem(1, "DEF");
list->Check(1, TRUE);

Using Specific Colours for Each Item

Trying to set specific item colours for a wxListCtrl doesn't have any effect with wxMSW 2.4.0 standard release. You have to enable it. Indeed, it is not set as default in the downloadable releases so you will have to recompile the library with _WIN32_IE at least defined as 0x300. To do so, just insert the -D_WIN32_IE=0x300 compile flag when making. See the Guides & Tutorials for further details about building wx. Then, just use wxListItem's methods to set the text colour, background colour and font of each item. For example:

MyListCtrl::AppendColouredItem(const wxString& Label) {
   wxListItem NewItem;
 
   NewItem.SetMask(wxLIST_MASK_TEXT);
   NewItem.SetId(GetItemCount());
   NewItem.SetText(sLabel);
   NewItem.SetFont(*wxITALIC_FONT);
   NewItem.SetTextColour(wxColour(*wxRED));
   NewItem.SetBackgroundColour(wxColour(235, 235, 235));
 
   InsertItem(NewItem);
}

This piece of code appends a new line (item) to MyListCtrl with the label of sLabel's value, an italic font, a red text colour and a light grey background.

NOTE: It seems that these methods still don't work with columns so the tip is to set the global text/background colours and font of MyListCtrl and then, when you append an item, set its colours/font to the proper values.

wxListCtrl::SetSingleStyle

The wxListCtrl::SetSingleStyle(long style, const bool add = true) function calls SetWindowStyleFlag(long style) which will delete all items. Use it before filling the control with items.

See Also