App crashes on AppendCollumn() with a Virtual List Control

239 views Asked by At

I am a hobby programmer still learning C++ and wxWidgets. I use Code::Blocks 20.3, wxWidgets 3.1.4, and MinGW 17.1 on a Windows 10 Pro computer.

I am trying to make a virtual wxListCtrl work in Code::Blocks, I followed the code in the ListCtrl sample and a simple example in the wxWidgets Discussion Forum. The sample (1 file) code on both compiles and works. When I start a new project in Code::Blocks, the GUI APP and MAIN is coded in separate files. Perhaps I need to do more, but cannot find what it is. The project compiles with zero errors and warnings, but it shows below error on startup. I tried moving SetItemCount() above AppendCollumn(), but that did not help. There is no error when I comment both AppendColumn() out, but then of course the list is empty.

Can someone please help me with what is missing in my code?

Thank you, Ruud

Error:

A debugging check in this application has failed
../../src/cpmmon.listctrlcmn.cpp(259):
 assert "Assert Failure" failed in OnGetItemText():
 wxListCtrl::OnGetItemText not supposed to be called

GUI code:

GUIFrame::GUIFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
    this->SetSizeHints( wxDefaultSize, wxDefaultSize );

    wxBoxSizer* bSizer1;
    bSizer1 = new wxBoxSizer( wxVERTICAL );

    m_listCtrl1 = new wxListCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxLC_VIRTUAL );
    bSizer1->Add( m_listCtrl1, 1, wxALL|wxEXPAND, 10 );

    this->SetSizer( bSizer1 );
    this->Layout();

    // Connect Events
    this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( GUIFrame::OnClose ) );
}

GUIFrame::~GUIFrame()
{
    // Disconnect Events
    this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( GUIFrame::OnClose ) );
}

Main Code:

#include "VlistMain.h"

const wxChar *SMALL_VIRTUAL_VIEW_ITEMS[][2] =
{
    { wxT("Cat"), wxT("meow") },
    { wxT("Sheep"), wxT("baaah") }
};

VlistFrame::VlistFrame(wxFrame *frame) : GUIFrame(frame)
{
    m_listCtrl1->AppendColumn("Animal");
    m_listCtrl1->AppendColumn("Sound");
    m_listCtrl1->SetItemCount(WXSIZEOF(SMALL_VIRTUAL_VIEW_ITEMS));
}

wxString VlistFrame::OnGetItemText(long item, long column) const
{
    return SMALL_VIRTUAL_VIEW_ITEMS[item][column];
}

Event table:

wxBEGIN_EVENT_TABLE(MyListCtrl, wxListCtrl)
    EVT_LIST_ITEM_SELECTED(wxAny, MyListCtrl::OnSelected)
wxEND_EVENT_TABLE()
2

There are 2 answers

1
New Pagodi On BEST ANSWER

Here's a second answer that uses wxFormbuilder's subclass feature. The advantage of this approach is that it will let you set up event handlers with wxFormbuilder. First we'll need to have a slightly different declaration for the constructor for the derived class

class MyListCtrl:public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent,
               wxWindowID id,
               const wxPoint& pos = wxDefaultPosition,
               const wxSize& size = wxDefaultSize,
               long style = wxLC_VIRTUAL | wxLC_REPORT);
    virtual wxString OnGetItemText(long item, long column) const wxOVERRIDE;
};

This add's more parameters to the constructor so that it will be compatible with the code generated by wxFormbuilder. As in the other answer, this should be placed in a new header file that is added to the project.

Next in a code file, add the body for the constructor

MyListCtrl::MyListCtrl(wxWindow *parent, wxWindowID id, const wxPoint& pos,
                       const wxSize& size, long style)
           :wxListCtrl(parent, id, pos, size,
                       wxLC_VIRTUAL | wxLC_REPORT, wxDefaultValidator,
                       "MyListCtrl")
{

}

Note that this forces the the subclass to have the style wxLC_VIRTUAL | wxLC_REPORT and will ignore any style entered in wxFormbuilder. So if you want to change the style, you should change it in the code above.

Alternately you could change wxLC_VIRTUAL | wxLC_REPORT to style. Then the style options entered in wxFormbuilder will be used. But then it's up to you to make sure those options always include wxLC_REPORT and wxLC_VIRTUAL and do not contain any other options that are incompatible with those two.

Next, in wxFormbuilder, add a list control to your form and on the object properties tab, set the subclass options to

name: MyListCtrl
header: MyLstCtrl.h

enter image description here

(you can leave the forward_declare option checked or unchecked - it doesn't make any difference).

Now you can add any event handlers for your list control just like with any other widget:

enter image description here

5
New Pagodi On

To use a virtual list control, you need to derive a class from wxListCtrl and override the OnGetItemText method. For example:

class MyListCtrl:public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent, wxWindowID id);
    virtual wxString OnGetItemText (long item, long column) const wxOVERRIDE;
};

MyListCtrl::MyListCtrl(wxWindow *parent, wxWindowID id)
           :wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize,
                       wxLC_VIRTUAL | wxLC_REPORT, wxDefaultValidator,
                       "MyListCtrl")
{

}

wxString MyListCtrl::OnGetItemText(long item, long column) const
{
    return SMALL_VIRTUAL_VIEW_ITEMS[item][column];
}

You can also override OnGetItemColumnImage and OnGetItemAttr to further customize your control.


Since it looks like you're using wxFormbuilder, you can do this using the Custom Control widget. To do this, first you need to add a new header file to your project named MyListCtrl.h (or whatever you want to call it). The contents of the new header should look something like this:

#ifndef MYLISTCTRL_H_INCLUDED
#define MYLISTCTRL_H_INCLUDED

#include <wx/listctrl.h>

class MyListCtrl:public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent, wxWindowID id);
    virtual wxString OnGetItemText (long item, long column) const wxOVERRIDE;
};

#endif // MYLISTCTRL_H_INCLUDED

Once you have the header for your control, you can use the "CustomControl" widget. It's the second to last item on the Additional tab:

enter image description here

On the properties tab, you'll need to set the following items

name:          m_listCtrl1
declaration:   MyListCtrl* m_listCtrl1;
construction:  m_listCtrl1 = new MyListCtrl(m_panel1, wxID_ANY);
include:       #include "MyListCtrl.h"

like so:

enter image description here

You can the provide the code for the MyListCtrl::MyListCtrl constructor and MyListCtrl::OnGetItemText in the same file as your devived frame class. Or if you want maintain one class per file, you can also put it in a new cpp file. Obviously you need to add "#include "MyListCtrl.h" at the top of which ever file you use to put the code for those methods in.


To handle events using event tables, you would need to add declarations for the event handling methods and the wxDECLARE_EVENT_TABLE macro to the class declaration.

class MyListCtrl:public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent, wxWindowID id);
    virtual wxString OnGetItemText (long item, long column) const wxOVERRIDE;

protected:
    void OnItemSelected(wxListEvent& event);
    // Add all other event handler method declarations here
private:
    wxDECLARE_EVENT_TABLE();
};

Then in the file with the code you would have something like:

wxBEGIN_EVENT_TABLE(MyListCtrl, wxListCtrl)
    EVT_LIST_ITEM_SELECTED(wxID_ANY, MyListCtrl::OnItemSelected)
    /* Add all other event macros here */
wxEND_EVENT_TABLE()


void MyListCtrl::OnItemSelected(wxListEvent& event)
{
    // do something here
}

// write all other event handlers here.

On a personal note, I really don't like event tables, so I would use dynamic event binding instead. To do this, the declaration for the class should look something like this:

class MyListCtrl:public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent, wxWindowID id);
    virtual wxString OnGetItemText (long item, long column) const wxOVERRIDE;

protected:
    void OnItemSelected(wxListEvent& event);
    // declare all other event handlers here.
};

Then the code for the constructor should look something like this:

MyListCtrl::MyListCtrl(wxWindow *parent, wxWindowID id)
           :wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize,
                       wxLC_VIRTUAL | wxLC_REPORT, wxDefaultValidator,
                       "MyListCtrl")
{
    Bind(wxEVT_LIST_ITEM_SELECTED, &MyListCtrl::OnItemSelected, this);
    // Add any other needed Bind statements here
}