For large dataview controls, don't use all items to calculate best column width.
Instead, use just top and bottom N/2 items for some large enough value of N. N is determined dynamically so that column best width calculation doesn't take more than 50ms. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@65951 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -48,6 +48,7 @@
|
|||||||
#include "wx/imaglist.h"
|
#include "wx/imaglist.h"
|
||||||
#include "wx/headerctrl.h"
|
#include "wx/headerctrl.h"
|
||||||
#include "wx/dnd.h"
|
#include "wx/dnd.h"
|
||||||
|
#include "wx/stopwatch.h"
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// classes
|
// classes
|
||||||
@@ -4121,35 +4122,136 @@ unsigned int wxDataViewCtrl::GetBestColumnWidth(int idx) const
|
|||||||
if ( m_colsBestWidths[idx] != 0 )
|
if ( m_colsBestWidths[idx] != 0 )
|
||||||
return m_colsBestWidths[idx];
|
return m_colsBestWidths[idx];
|
||||||
|
|
||||||
const unsigned count = m_clientArea->GetRowCount();
|
const int count = m_clientArea->GetRowCount();
|
||||||
wxDataViewColumn *column = GetColumn(idx);
|
wxDataViewColumn *column = GetColumn(idx);
|
||||||
wxDataViewRenderer *renderer =
|
wxDataViewRenderer *renderer =
|
||||||
const_cast<wxDataViewRenderer*>(column->GetRenderer());
|
const_cast<wxDataViewRenderer*>(column->GetRenderer());
|
||||||
|
|
||||||
int max_width = 0;
|
class MaxWidthCalculator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MaxWidthCalculator(wxDataViewMainWindow *clientArea,
|
||||||
|
wxDataViewRenderer *renderer,
|
||||||
|
const wxDataViewModel *model,
|
||||||
|
unsigned column)
|
||||||
|
: m_width(0),
|
||||||
|
m_clientArea(clientArea),
|
||||||
|
m_renderer(renderer),
|
||||||
|
m_model(model),
|
||||||
|
m_column(column)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateWithWidth(int width)
|
||||||
|
{
|
||||||
|
m_width = wxMax(m_width, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateWithRow(int row)
|
||||||
|
{
|
||||||
|
wxDataViewItem item = m_clientArea->GetItemByRow(row);
|
||||||
|
m_renderer->PrepareForItem(m_model, item, m_column);
|
||||||
|
m_width = wxMax(m_width, m_renderer->GetSize().x);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetMaxWidth() const { return m_width; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_width;
|
||||||
|
wxDataViewMainWindow *m_clientArea;
|
||||||
|
wxDataViewRenderer *m_renderer;
|
||||||
|
const wxDataViewModel *m_model;
|
||||||
|
unsigned m_column;
|
||||||
|
};
|
||||||
|
|
||||||
|
MaxWidthCalculator calculator(m_clientArea, renderer,
|
||||||
|
GetModel(), column->GetModelColumn());
|
||||||
|
|
||||||
if ( m_headerArea )
|
if ( m_headerArea )
|
||||||
{
|
{
|
||||||
max_width = m_headerArea->GetTextExtent(column->GetTitle()).x;
|
int header_width = m_headerArea->GetTextExtent(column->GetTitle()).x;
|
||||||
|
|
||||||
// Labels on native MSW header are indented on both sides
|
// Labels on native MSW header are indented on both sides
|
||||||
max_width += wxRendererNative::Get().GetHeaderButtonMargin(m_headerArea);
|
header_width +=
|
||||||
|
wxRendererNative::Get().GetHeaderButtonMargin(m_headerArea);
|
||||||
|
calculator.UpdateWithWidth(header_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( unsigned row = 0; row < count; row++ )
|
// The code below deserves some explanation. For very large controls, we
|
||||||
|
// simply can't afford to calculate sizes for all items, it takes too
|
||||||
|
// long. So the best we can do is to check the first and the last N/2
|
||||||
|
// items in the control for some sufficiently large N and calculate best
|
||||||
|
// sizes from that. That can result in the calculated best width being too
|
||||||
|
// small for some outliers, but it's better to get slightly imperfect
|
||||||
|
// result than to wait several seconds after every update. To avoid highly
|
||||||
|
// visible miscalculations, we also include all currently visible items
|
||||||
|
// no matter what. Finally, the value of N is determined dynamically by
|
||||||
|
// measuring how much time we spent on the determining item widths so far.
|
||||||
|
|
||||||
|
#if wxUSE_STOPWATCH
|
||||||
|
int top_part_end = count;
|
||||||
|
static const long CALC_TIMEOUT = 20/*ms*/;
|
||||||
|
// don't call wxStopWatch::Time() too often
|
||||||
|
static const unsigned CALC_CHECK_FREQ = 100;
|
||||||
|
wxStopWatch timer;
|
||||||
|
#else
|
||||||
|
// use some hard-coded limit, that's the best we can do without timer
|
||||||
|
int top_part_end = wxMin(500, count);
|
||||||
|
#endif // wxUSE_STOPWATCH/!wxUSE_STOPWATCH
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
|
for ( row = 0; row < top_part_end; row++ )
|
||||||
{
|
{
|
||||||
wxDataViewItem item = m_clientArea->GetItemByRow(row);
|
#if wxUSE_STOPWATCH
|
||||||
|
if ( row % CALC_CHECK_FREQ == CALC_CHECK_FREQ-1 &&
|
||||||
renderer->PrepareForItem(GetModel(), item, column->GetModelColumn());
|
timer.Time() > CALC_TIMEOUT )
|
||||||
|
break;
|
||||||
max_width = (unsigned)wxMax((int)max_width, renderer->GetSize().x);
|
#endif // wxUSE_STOPWATCH
|
||||||
|
calculator.UpdateWithRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// row is the first unmeasured item now; that's out value of N/2
|
||||||
|
|
||||||
|
if ( row < count )
|
||||||
|
{
|
||||||
|
top_part_end = row;
|
||||||
|
|
||||||
|
// add bottom N/2 items now:
|
||||||
|
const int bottom_part_start = wxMax(row, count - row);
|
||||||
|
for ( row = bottom_part_start; row < count; row++ )
|
||||||
|
{
|
||||||
|
calculator.UpdateWithRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally, include currently visible items in the calculation:
|
||||||
|
const wxPoint origin = CalcUnscrolledPosition(wxPoint(0, 0));
|
||||||
|
int first_visible = m_clientArea->GetLineAt(origin.y);
|
||||||
|
int last_visible = m_clientArea->GetLineAt(origin.y + GetClientSize().y);
|
||||||
|
|
||||||
|
first_visible = wxMax(first_visible, top_part_end);
|
||||||
|
last_visible = wxMin(bottom_part_start, last_visible);
|
||||||
|
|
||||||
|
for ( row = first_visible; row < last_visible; row++ )
|
||||||
|
{
|
||||||
|
calculator.UpdateWithRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxLogTrace("dataview",
|
||||||
|
"determined best size from %d top, %d bottom plus %d more visible items out of %d total",
|
||||||
|
top_part_end,
|
||||||
|
count - bottom_part_start,
|
||||||
|
wxMax(0, last_visible - first_visible),
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
int max_width = calculator.GetMaxWidth();
|
||||||
if ( max_width > 0 )
|
if ( max_width > 0 )
|
||||||
max_width += 2 * PADDING_RIGHTLEFT;
|
max_width += 2 * PADDING_RIGHTLEFT;
|
||||||
|
|
||||||
const_cast<wxDataViewCtrl*>(this)->m_colsBestWidths[idx] = max_width;
|
const_cast<wxDataViewCtrl*>(this)->m_colsBestWidths[idx] = max_width;
|
||||||
return max_width;
|
return max_width;
|
||||||
|
|
||||||
|
#undef MEASURE_ITEM
|
||||||
}
|
}
|
||||||
|
|
||||||
void wxDataViewCtrl::ColumnMoved(wxDataViewColumn * WXUNUSED(col),
|
void wxDataViewCtrl::ColumnMoved(wxDataViewColumn * WXUNUSED(col),
|
||||||
|
Reference in New Issue
Block a user