PDA

View Full Version : QListWidget resize item to reproduce Windows behavior : Magical Number needed



Alundra
16th January 2016, 03:13
Hi,
Here my solution to reproduce the Windows behavior to have always the QListWidget filled in space (override of QStyledItemDelegate) :


virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const override
{
const int RightMargin = 15;
const QSize ItemPadding = QSize( 6, 6 );
QListWidget* ListWidget = static_cast< QListWidget* >( parent() );
const int MaxWidth = ListWidget->viewport()->width() - RightMargin;
const int ItemWidth = option.decorationSize.width() + ItemPadding.width();
if( ItemWidth >= MaxWidth )
return option.decorationSize + QSize( 0, option.fontMetrics.height() ) + ItemPadding;
const int ItemHeight = option.decorationSize.height() + option.fontMetrics.height() + ItemPadding.height();
const int NumItem = std::max( 1, MaxWidth / ItemWidth );
const int NumRow = std::ceil( static_cast< float >( ListWidget->count() ) / static_cast< float >( NumItem ) );
const bool ScrollbarVisible = ( NumRow * ItemHeight ) > ListWidget->viewport()->height();
if( ScrollbarVisible )
{
const int PaddingOffset = ( NumItem >= ListWidget->count() ) ? 0 : ( MaxWidth - ( NumItem * ItemWidth ) ) / NumItem;
return option.decorationSize + QSize( 0, option.fontMetrics.height() ) + ItemPadding + QSize( PaddingOffset, 0 );
}
else
{
// 3 pixels removed to avoid flickering when resize.
const int NewMaxWidth = ListWidget->viewport()->width() - RightMargin - 3;
const int NewNumItem = std::max( 1, NewMaxWidth / ItemWidth );
const int PaddingOffset = ( NewNumItem >= ListWidget->count() ) ? 0 : ( NewMaxWidth - ( NewNumItem * ItemWidth ) ) / NewNumItem;
return option.decorationSize + QSize( 0, option.fontMetrics.height() ) + ItemPadding + QSize( PaddingOffset, 0 );
}
}

This code compute the free space on the right based on the known item size and distribute in each item to always be filled on the width.
All works fine but why this 3px magical number is needed to avoid flickering when the scrollbar is not visible ?
If this magical number is not used one item always swap from one line to another when resize.
Thanks

Alundra
18th January 2016, 05:26
Using RightMargin of 21 solve all issue apparently.

d_stranz
18th January 2016, 19:52
Using RightMargin of 21 solve all issue apparently.

Which you should understand is just another magic number that is likely to change based on your screen resolution, font magnification, and / or default font. Change your screen magnification and I'll bet your code no longer works the same way.

Alundra
18th January 2016, 20:45
Here the actual code which is well reduced but still use the constant 20 :


virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const override
{
// Unused.
DE_UNUSED( index );

// Constants.
const int RightMargin = 20;
const QSize ItemPadding = QSize( 6, 6 );
const int ItemWidth = option.decorationSize.width() + ItemPadding.width();

// Get the list widget using the parent value.
QListWidget* ListWidget = static_cast< QListWidget* >( parent() );

// Compute the max width and the row item count.
const int MaxWidth = ListWidget->viewport()->width() - RightMargin;
const int RowItemCount = std::max( 1, MaxWidth / ItemWidth );

// Cases where the padding offset is not needed.
if( ( ItemWidth >= MaxWidth ) || ( RowItemCount >= ListWidget->count() ) )
return option.decorationSize + QSize( 0, option.fontMetrics.height() ) + ItemPadding;

// Compute the padding offset and add in the item size to compensate of empty space.
const int PaddingOffset = ( MaxWidth - ( RowItemCount * ItemWidth ) ) / RowItemCount;
return option.decorationSize + QSize( PaddingOffset, option.fontMetrics.height() ) + ItemPadding;
}

If a way to have it really better and still working good exists, i'm open to listen how solve that.

ChrisW67
18th January 2016, 20:46
The magic is likely some element(s) of the style's rendering of the widgets that you are not accounting for. You may be able to work the amount of "magic" required using the QStyle::pixelMetric() function and constants
http://doc.qt.io/qt-4.8/qstyle.html#PixelMetric-enum

The flickering is probably because the automatic scrollbar is being added and removed repeatedly because the size of the contained widget is changing around the scroll area's critical size. You need to be careful that the appearance of the scroll bar, which narrows the viewport, does not trigger a shrinking of the layout that causes the viewport to grow... Circular updates.

Alundra
18th January 2016, 21:45
The idea is easy, simply compensate the empty space on each item to use the whole space on the width but to remove this constant, it's not so easy.
Using a little value to only have very little empty space you got bad result (flickering). The solution I give is the only one I found after lot of tests.
Maybe a padding-left needed to solve completely, but then you have big margin on the left and right of the row.
using :

m_ListWidget->setStyleSheet( "QListView{ padding-left: 10px; }" );
I have to use this value to have 0 flickering (if the scrollbar is visible or not) :

const int RightMargin = 28;
But the margin is not the same on the left and right, I have 10px on the left but more space on the right, 28px.
For the case where no padding-left is set, I got also good visual without flickering using 18px for the RightMargin, which gives less empty space than 20px.
That works very well but it's not centered, you have 0 empty space on the left and 18px empty space on the right and no solution found to have it centered...