wxListCtrl GetTopItem() returns negative number Topic is solved
wxListCtrl GetTopItem() returns negative number
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
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
Re: wxListCtrl GetTopItem() returns negative number
If you set a breakpoint on:
What is the value of items?
& does the "underlying data" still exist?
Code: Select all
SetItemCount(items);
& does the "underlying data" still exist?
wxWidgets(v3.2.2.1) - Vs2022(v143) - Win10(x64) - DialogBlocks(v5.16.5_Unicode)
Re: wxListCtrl GetTopItem() returns negative number
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().
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);
Re: wxListCtrl GetTopItem() returns negative number
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!
Re: wxListCtrl GetTopItem() returns negative number
I could not reproduce this with code from above: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.
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...
Re: wxListCtrl GetTopItem() returns negative number
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
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
Re: wxListCtrl GetTopItem() returns negative number
Possible, although unlikely.doublemax: My guess is that you are correct! So this is a bug in wxListCtrl, I guess?
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!
Re: wxListCtrl GetTopItem() returns negative number
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 wrote:PB: You are not replicating my behaviour as you are not deleting any entries.
Re: wxListCtrl GetTopItem() returns negative number
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.
Re: wxListCtrl GetTopItem() returns negative number
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.
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.
Re: wxListCtrl GetTopItem() returns negative number
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.
Wild guess: I assume you catch wxEVT_SIZE. Do you call event.Skip() in the event handler? (You have to).If I disable my resizing code, the control behaves as expected.
Use the source, Luke!
Re: wxListCtrl GetTopItem() returns negative number
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
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
Re: wxListCtrl GetTopItem() returns negative number
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?
* 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);
Re: wxListCtrl GetTopItem() returns negative number
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:
I don't think it's worth opening a bug report for this, but it's up to you.
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();
});
Use the source, Luke!
Re: wxListCtrl GetTopItem() returns negative number
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
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