wxListCtrl GetTopItem() returns negative number Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

wxListCtrl GetTopItem() returns negative number

Post by pkl »

I have a wxListCtrl (virtual, in report mode) where the underlying data supports batch deletion (using a predicate to decide what to remove). When this deletion gets called, I update the listctrl using the following member functions:

SetItemCount(items);
RefreshItems(0,10000000);
Refresh();

(where items is the new number of data items).

As a result, the listctrl gets empty (completely or at the top items), and GetTopItem returns a negative value.
Env: Windows 10, wxWidgets 3.0.

What am I missing. What is the proper procedure to update a wxListCtrl in such a circumstance?

/Peter
tuk1
Earned some good credits
Earned some good credits
Posts: 114
Joined: Sun Oct 08, 2017 9:36 am

Re: wxListCtrl GetTopItem() returns negative number

Post by tuk1 »

If you set a breakpoint on:

Code: Select all

SetItemCount(items); 
What is the value of items?
& does the "underlying data" still exist?
wxWidgets(v3.2.2.1) - Vs2022(v143) - Win10(x64) - DialogBlocks(v5.16.5_Unicode)
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by PB »

Using the example code below, all seems to work as expected. However, I am using wxWidgets 3.1.

The one suspect thing in your code is using an arbitrary number (is it smaller than the actual number of items?) in RefreshItems().

Code: Select all

#include <wx/wx.h>
#include <wx/listctrl.h>
#include <wx/numdlg.h> 

class MyListCtrl : public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent)
        : wxListCtrl(parent, wxID_ANY, wxDefaultPosition, wxSize(-1,-1), wxLC_REPORT | wxLC_VIRTUAL )
    {       
       InsertColumn(0, ("Item"));
       UpdateData(10);
    }
    
    void UpdateData(size_t newItemCount)
    {
        static size_t updateCount = 0;

        m_items.clear();
        m_items.reserve(newItemCount);

        updateCount++;
        for ( size_t i = 0; i < newItemCount; ++i )
            m_items.push_back(wxString::Format("Update %zu: Item no %zu", updateCount, i));

        SetItemCount(m_items.size());
        SetColumnWidth(0, wxLIST_AUTOSIZE);
        RefreshItems(0, GetItemCount() - 1);
    }
protected:                
    wxArrayString m_items;
    
    virtual wxString OnGetItemText(long item, long) const
    {                
        return m_items[item];
    }
};

class MyFrame : public wxFrame
{
public:
    MyFrame() : wxFrame(NULL, wxID_ANY, _("Test"))
    {
        wxBoxSizer* bSizer = new wxBoxSizer(wxVERTICAL);

        m_listCtrl = new MyListCtrl(this);
        bSizer->Add(m_listCtrl, 1, wxEXPAND | wxALL, 5);

        wxButton* button = new wxButton(this, wxID_ANY, "Update items");
        bSizer->Add(button, 0, wxEXPAND | wxALL, 5);
        button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame::OnButtonClicked, this);

        SetSizer(bSizer);
    }	
private:    
    MyListCtrl* m_listCtrl;
    
    void OnButtonClicked(wxCommandEvent&)
    {       
        long newItemCount = 10;

        newItemCount = wxGetNumberFromUser("Input number of items", "Number", "New number of items in the list",
            newItemCount, 0, 25000, this);
		
        if ( newItemCount >= 0 )
        {
            m_listCtrl->UpdateData(newItemCount);        
            wxLogMessage("m_listCtrl->GetTopItem(): %ld", m_listCtrl->GetTopItem());
        }
    }
};

class MyApp : public wxApp
{
public:
    virtual bool OnInit()
    {     
        (new MyFrame())->Show();               
        return true;
    }
}; wxIMPLEMENT_APP(MyApp);
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxListCtrl GetTopItem() returns negative number

Post by doublemax »

I believe the problem occurs when the list is scrolled down and after deleting items above the visible area, the scroll position becomes invalid. But i can't test this right now.
Use the source, Luke!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by PB »

doublemax wrote:I believe the problem occurs when the list is scrolled down and after deleting items above the visible area, the scroll position becomes invalid. But i can't test this right now.
I could not reproduce this with code from above:
Step 1: Change the item count to 1000
Step 2: Scroll towards the bottom of the list, so the scroll position is large
Step 3: Change the item count to a much smaller number with the vertical scroll bar still shown

But I may be missing something and I am also using a newer wxWidgets...
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by pkl »

Thank you for your comments!
tuk1: It is a small positive value = vec.size(), where vec is a std::vector holding the data. So that is not the problem.
PB: You are using a "reduced" version of the ListCtrl demo, I guess? You are not replicating my behaviour as you are not deleting any entries.
The arbitrary number is a huge codesmell, yes. I tried that number instead of my original "items" to see if it changed anything. It did not.
doublemax: My guess is that you are correct! So this is a bug in wxListCtrl, I guess?

/Peter
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxListCtrl GetTopItem() returns negative number

Post by doublemax »

doublemax: My guess is that you are correct! So this is a bug in wxListCtrl, I guess?
Possible, although unlikely.

I couldn't reproduce the problem either, so there must be something else you're doing that triggers it.

Please try to create a minimal, compilable sample that shows the problem.
Use the source, Luke!
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by PB »

pkl wrote:PB: You are not replicating my behaviour as you are not deleting any entries.
The way I understand it, when using virtual mode you are not deleting any entries as far as wxListCtrl is concerned. You just tell wxListCtrl to change the item count and redraw - wxListCtrl in virtual mode does not own/manage the items. Am I wrong?
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by pkl »

So far, I have been able to reproduce the problem with the sample code. Thus it is probably a problem with my code. I will investigate further and return here to provide an explanation or close the subject. Thank you for your support. As always it has been very good.
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by pkl »

Hi all,

I have now had time to look at this wxWidgets problem again, and I can confirm that I still believe this to be a bug in wxWidgets.
What happens in my code is that when the size of a ListCtrls client-size changes I perform a column resize (the width of my columns may be dynamic depending on the data they represent).
If I disable my resizing code, the control behaves as expected.
I believe my bug is related to #16894.

I can consistently reproduce my bug by maximizing the window.
Output from my debug log: with first four trapped events after my column resize:

Event 10027 Top = 4/4
Event 10027 Top = 0/0
Event 10101 Top = 4/0
Event 10121 Top = -4/-4

I hook ProcessEvent and write the EventType(), and GetTopItem() before and after the real processing of the event.
As you can see, none of the events trapped cause the top item to become negative. I find it a little suspicious though that TopLevelItem sometimes becomes 0 - also if nothing is changed by the user. Another excerpt where the window is not touched:

Event 10137 Top = 4/4
Event 10002 Top = 4/4
Event 10083 Top = 4/4
Event 10060 Top = 4/4
Event 10002 Top = 0/0
Event 10137 Top = 4/4
Event 10002 Top = 4/4
Event 10083 Top = 4/4
Event 10060 Top = 4/4
Event 10002 Top = 0/0

I would like some help from you: do you know how I translate from EventType to the define? That would help me work around the problem and perhaps also file a bug report. The original bug is three years old so I am not sure that is worth the trouble. ;-(

/Peter

NB: I forgot a NOT in my previous post. I could not reproduce the problem.
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxListCtrl GetTopItem() returns negative number

Post by doublemax »

In any case, some sample to reproduce the issue will be needed. Either here or when opening a bug report. Without this, nobody will be able to fix it.
If I disable my resizing code, the control behaves as expected.
Wild guess: I assume you catch wxEVT_SIZE. Do you call event.Skip() in the event handler? (You have to).
Use the source, Luke!
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by pkl »

Yes.

I bind wxEVT_SIZE in my constructor for my controlling class (derived from wxListCtrl) and I do call event.Skip() in my handler.
But why do I have to do that? I could imagine a handler that performs some action and then still wants the default action to take place (Imagine you would like the colour of the text to depend on the size of the wxListCtrl).

/Peter
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by pkl »

Here is code to reproduce my problem. I modified the code from PB. Thank you for the code, PB. ;-)

* Press update items and insert e.g. 20 items.
* Scroll down so you are near the end of the report.
* Maximize window.

Several empty lines and missing columns.

And by the way: refreshing is awfully slow! Slower than my code which has more complicated elements.

Can anyone reproduce?

Code: Select all

#include <wx/wx.h>
#include <wx/listctrl.h>
#include <wx/numdlg.h>
#include <algorithm> 
#include <vector> 

const int my_cols = 7;
const int min_csize = 100;
const int max_csize = 500;
class MyListCtrl : public wxListCtrl
{
public:
    MyListCtrl(wxWindow *parent)
        : wxListCtrl(parent, wxID_ANY, wxDefaultPosition, wxSize(-1,-1), wxLC_REPORT | wxLC_VIRTUAL | wxLC_SINGLE_SEL)
    {       
        Bind(wxEVT_SIZE,[this](wxEvent& e) 
        { 
            e.Skip();
            this->on_size(); 
        });

        Bind(wxEVT_CONTEXT_MENU,[this](wxEvent&) 
        { 
            on_right_click(); 
        });

        Bind(wxEVT_KEY_DOWN,[this](wxKeyEvent& kd_info)
        {
            this->on_key_down(kd_info);
        });

        for (int i{0}; i < my_cols; ++i)
        {
            wxString s;
            s.Printf("Col %d",i);
            InsertColumn(i,s);
            SetColumnWidth(i, 200);
        }
        Updated();
    }
    
    void on_size()
    {
        int wdw_width = GetClientSize().x;
        int col_size = std::max(100,wdw_width/my_cols);

        for (auto  i = 0; i< my_cols; ++i)
            SetColumnWidth(i,col_size);
    }

    void on_key_down(wxKeyEvent& kd_info)
    {
        if (kd_info.GetModifiers() == wxMOD_ALT && kd_info.GetKeyCode() == 'D')
        {
            RemoveData(m_items.size()/2);
            kd_info.Skip(false);
        }
    }

    void on_right_click()
    {
        wxMenu menu;
        menu.Append(9856,"Erase","");
        if (9856 == GetPopupMenuSelectionFromUser(menu))
        {
            auto pos = GetNextItem(-1,wxLIST_NEXT_ALL,wxLIST_STATE_SELECTED);
            if (pos >= 0 && pos < m_items.size())
            {
                RemoveData(pos);
            }
        }
    }
    void Updated()
    {
        SetItemCount(m_items.size());
        RefreshItems(0, GetItemCount() - 1);
        wxLogMessage("GetTopItem(): %ld", GetTopItem());
    }
    void AddData(size_t count)
    {
        m_items.clear();
        m_items.reserve(count);
        for (size_t i{0}; i < count; ++i)
            m_items.push_back(i);
        Updated();
    }

    void RemoveData(size_t keep)
    {
        if (keep < m_items.size())
        {
            m_items.erase(
                m_items.begin(),
                m_items.begin() + keep
            );
        }
        Updated();
    }

protected:                
    std::vector<size_t> m_items;
    
    virtual wxString OnGetItemText(long item, long col) const
    {                
        OutputDebugStringA("Hello\n");
        wxString res;
        res.Printf("Item %u (%d)",m_items[item],col);
        return res;
    }
};

class MyTextCtrl : public wxTextCtrl 
{
public:
    MyTextCtrl(wxWindow *parent)
        : wxTextCtrl(parent, wxID_ANY, "",wxDefaultPosition,wxSize(-1,-1))
    {       
        Bind(wxEVT_CONTEXT_MENU,[this](wxEvent&) 
        { 
            on_right_click(); 
        });

        Bind(wxEVT_KEY_DOWN,[this](wxKeyEvent& kd_info)
        {
            this->on_key_down(kd_info);
        });
    }
    
    void on_key_down(wxKeyEvent& kd_info)
    {
        if (kd_info.GetModifiers() == wxMOD_ALT && kd_info.GetKeyCode() == 'D')
        {
            kd_info.Skip(false);
            this->SetValue("HELLO");
        }
    }

    void on_right_click()
    {
        wxMenu menu;
        menu.Append(9856,"Erase","");
        if (9856 == GetPopupMenuSelectionFromUser(menu))
        {
            SetValue("");
        }
    }

    
};

class MyFrame2 : public wxFrame
{
public:
    MyFrame2() : wxFrame(NULL, wxID_ANY, _("Test"))
    {
        wxBoxSizer* bSizer = new wxBoxSizer(wxVERTICAL);

        //m_child = new MyTextCtrl(this);
        //bSizer->Add(m_child, 1, wxEXPAND | wxALL, 5);
        m_listCtrl = new MyListCtrl(this);
        bSizer->Add(m_listCtrl, 1, wxEXPAND | wxALL, 5);

        wxButton* button = new wxButton(this, wxID_ANY, "Update items");
        bSizer->Add(button, 0, wxEXPAND | wxALL, 5);
        button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame2::OnButtonClicked, this);

        wxButton* button2 = new wxButton(this, wxID_ANY, "Erase items");
        bSizer->Add(button2, 0, wxEXPAND | wxALL, 5);
        button2->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &MyFrame2::OnButtonClicked2, this);

        SetSizer(bSizer);
    }   
private:    
    MyTextCtrl* m_child = nullptr;
    MyListCtrl* m_listCtrl = nullptr;
    
    void OnButtonClicked(wxCommandEvent&)
    {       
        long newItemCount = 10;

        newItemCount = wxGetNumberFromUser("Input number of items", "Number", "New number of items in the list",
            newItemCount, 0, 25000, this);
      
        if (m_listCtrl && newItemCount >= 0 )
        {
            m_listCtrl->AddData(newItemCount);
        }
    }

    void OnButtonClicked2(wxCommandEvent&)
    {       
        long newItemCount = 10;

        newItemCount = wxGetNumberFromUser("Number of items to erase", "Number", "",
            newItemCount, 0, 25000, this);
        if (m_listCtrl) m_listCtrl->RemoveData(newItemCount);
    }
};

class MyApp : public wxApp
{
public:
    virtual bool OnInit()
    {     
        (new MyFrame2())->Show();               
        return true;
    }
}; 
wxIMPLEMENT_APP(MyApp);
User avatar
doublemax
Moderator
Moderator
Posts: 19116
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxListCtrl GetTopItem() returns negative number

Post by doublemax »

I see the problem, apparently the control doesn't like the SetColumnWidth() call inside the size event handler.

But as this column width is directly sent to the underlying native control, i don't see what wxWidgets can do differently here.

In your code the solution is to call on_size() after then size event has been handled:

Code: Select all

Bind(wxEVT_SIZE,[this](wxEvent& e)
{
    e.Skip();
    CallAfter( &MyListCtrl::on_size );
    //this->on_size();
});
I don't think it's worth opening a bug report for this, but it's up to you.
Use the source, Luke!
pkl
Knows some wx things
Knows some wx things
Posts: 36
Joined: Mon Jan 30, 2017 11:46 pm

Re: wxListCtrl GetTopItem() returns negative number

Post by pkl »

Hi doublemax,

Thank you for your assistance. Indeed your solution worked, also in my real project.
I do not know how to solve this problem in wxWidgets since it appears to be a Windows bug.
I guess You could work your way around it, but perhaps some documentation would be in order?
Just weird I have been unable to find anything about that bug except for on the wxWidgets tracker.
The performance problem also sort of went away, even if the code is not the fastest on this planet. ;-)

/Peter
Post Reply