QListWidget - Setting default row background color
-
I have a
QListWidget
that I use to store file paths for a media player. Below I have its configuration:this->setAcceptDrops(true); this->setDragEnabled(true); this->setSelectionMode( QAbstractItemView::ExtendedSelection ); this->setContextMenuPolicy( Qt::CustomContextMenu ); this->setDragDropMode( QAbstractItemView::InternalMove ); this->setSelectionBehavior( QAbstractItemView::SelectRows ); this->setAlternatingRowColors( true ); this->setDropIndicatorShown(true);
The problem I am having is that when I select a row to play a media, I want to change that row so it is visible to user which row in the playlist is currently playing.
I can do that by setting background to a different color. But when a media is changed I need it restore the background it had before.
Right now I use this:
this->item(previousSelectedRow)->setBackground(QColor());
But the problem with this approach is that it will destroy the
setAlternatingRowColors
behavior that is meant to be true .I am on Windows using Qt 6.2.0 with MingW.
I hope the question is clear. -
@mrjj Thanks that's the perfect solution for this problem.
However for some reason it takes too long to update the list when I change the media, most of the time I have to give the list focus by clicking it or clicking in any item so the new playing row is highlighted.
Hi
It's my fault.
Using an int for the row has the side effect that the model and the view know
nothing about we change the Play row so it doesn't update before you focus it.
Normally one can have model issues an update but cant do that here so I didn't find a good
way to let it know the row was changed so the delegate would be asked to repaint.So we could try use the model and a UserRole
class PlayingDelegate: public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); if (! index.isValid() ) return; const QAbstractItemModel *model = index.model(); if (!model) return; bool isPlay=model->data(index,Qt::UserRole).toBool(); if ( isPlay ) { painter->setBrush(QColor(0,255,0,100)); painter->drawRect( option.rect.adjusted(0, 0, -1, -1) ); } } };
and then we set the playRow like
int row= ui->listWidget->currentRow(); auto model = ui->listWidget->model(); model->setData(model->index(row,0),true, Qt::UserRole); // true to set it, false to clear it
This will update instant.
This has one side effect though. You need to keep track of last playRow so you can clear it again.
Or if the playlist is not that super long, you could just have a clear function that reset all.
void ResetPlayRow(QAbstractItemModel *model) { for (int var = 0; var < model->rowCount(); ++var) { model->setData(model->index(var, 0), false, Qt::UserRole); } }
and then clear all before you set new. /just like you must stop the old song playing)
int row = ui->listWidget->currentRow(); auto model = ui->listWidget->model(); ResetPlayRow(model); // reset old model->setData(model->index(row, 0), true, Qt::UserRole);
Then it works quite well but if you have many hundreds of rows, its not an optimal solution.
-
I have a
QListWidget
that I use to store file paths for a media player. Below I have its configuration:this->setAcceptDrops(true); this->setDragEnabled(true); this->setSelectionMode( QAbstractItemView::ExtendedSelection ); this->setContextMenuPolicy( Qt::CustomContextMenu ); this->setDragDropMode( QAbstractItemView::InternalMove ); this->setSelectionBehavior( QAbstractItemView::SelectRows ); this->setAlternatingRowColors( true ); this->setDropIndicatorShown(true);
The problem I am having is that when I select a row to play a media, I want to change that row so it is visible to user which row in the playlist is currently playing.
I can do that by setting background to a different color. But when a media is changed I need it restore the background it had before.
Right now I use this:
this->item(previousSelectedRow)->setBackground(QColor());
But the problem with this approach is that it will destroy the
setAlternatingRowColors
behavior that is meant to be true .I am on Windows using Qt 6.2.0 with MingW.
I hope the question is clear. -
@hbatalha
I would expect you to achieve this by setting the selection color, which should be independent of any coloring and apply only to the row while it is selected. You can also do that from stylesheet instead of code.@JonB I already have selection color set, I use it to select rows that are meant to be moved, deleted or played. I want a behavior like the one on VLC Player, I am going through its source code but haven't found how they do that yet.
which should be independent of any coloring and apply only to the row while it is selected
please elaborate more.
-
Return the appropriate color in QAbstractItemView::data() or use a stylesheet for QListView. Since you don't use a custom model you must go with the stylesheet.
-
Return the appropriate color in QAbstractItemView::data() or use a stylesheet for QListView. Since you don't use a custom model you must go with the stylesheet.
@Christian-Ehrlicher I don't understand how to do that to solve my problem
-
Hi
For such effect, you can also consider at Delegate.
However, since it uses a private ListModel subclass we cant access in any easy way,
its not optimal as we just use row index to color it and if you added or remove items over it,
It stays at the same row index so you have to handle that.class PlayingDelegate: public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); if (! index.isValid() ) return; const QAbstractItemModel *model = index.model(); if (!model) return; if ( playRow == index.row() ) { painter->setBrush(QColor(0,255,0,100)); painter->drawRect( option.rect.adjusted(0, 0, -1, -1) ); } } void setPlayRow(int newPlayRow) { playRow = newPlayRow; } private: int playRow = -1; };
have a
PlayingDelegate * delegate;
as a member of main window so we can address it from other functionsThen in constructor, init it
delegate = new PlayingDelegate;
ui->listWidget->setItemDelegate(delegate );Then to set a playline. (like play button did )
int row= ui->listWidget->currentRow();
delegate->setPlayRow(row);to clear it , call it with ( like stop did)
delegate->setPlayRow(-1);This said, it would be better with a listView and std.model and then we can
set it via the model and a UserRole and it would move correctly around with delete and insert.
If you consider this, do know its not much different using listVIEW + a model as
you then just create model items (versus QListWidget items) , insert to the model and then set the model to the view. -
Hi
For such effect, you can also consider at Delegate.
However, since it uses a private ListModel subclass we cant access in any easy way,
its not optimal as we just use row index to color it and if you added or remove items over it,
It stays at the same row index so you have to handle that.class PlayingDelegate: public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); if (! index.isValid() ) return; const QAbstractItemModel *model = index.model(); if (!model) return; if ( playRow == index.row() ) { painter->setBrush(QColor(0,255,0,100)); painter->drawRect( option.rect.adjusted(0, 0, -1, -1) ); } } void setPlayRow(int newPlayRow) { playRow = newPlayRow; } private: int playRow = -1; };
have a
PlayingDelegate * delegate;
as a member of main window so we can address it from other functionsThen in constructor, init it
delegate = new PlayingDelegate;
ui->listWidget->setItemDelegate(delegate );Then to set a playline. (like play button did )
int row= ui->listWidget->currentRow();
delegate->setPlayRow(row);to clear it , call it with ( like stop did)
delegate->setPlayRow(-1);This said, it would be better with a listView and std.model and then we can
set it via the model and a UserRole and it would move correctly around with delete and insert.
If you consider this, do know its not much different using listVIEW + a model as
you then just create model items (versus QListWidget items) , insert to the model and then set the model to the view.@mrjj Thanks that's the perfect solution for this problem.
However for some reason it takes too long to update the list when I change the media, most of the time I have to give the list focus by clicking it or clicking in any item so the new playing row is highlighted.
-
@mrjj Thanks that's the perfect solution for this problem.
However for some reason it takes too long to update the list when I change the media, most of the time I have to give the list focus by clicking it or clicking in any item so the new playing row is highlighted.
Hi
It's my fault.
Using an int for the row has the side effect that the model and the view know
nothing about we change the Play row so it doesn't update before you focus it.
Normally one can have model issues an update but cant do that here so I didn't find a good
way to let it know the row was changed so the delegate would be asked to repaint.So we could try use the model and a UserRole
class PlayingDelegate: public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); if (! index.isValid() ) return; const QAbstractItemModel *model = index.model(); if (!model) return; bool isPlay=model->data(index,Qt::UserRole).toBool(); if ( isPlay ) { painter->setBrush(QColor(0,255,0,100)); painter->drawRect( option.rect.adjusted(0, 0, -1, -1) ); } } };
and then we set the playRow like
int row= ui->listWidget->currentRow(); auto model = ui->listWidget->model(); model->setData(model->index(row,0),true, Qt::UserRole); // true to set it, false to clear it
This will update instant.
This has one side effect though. You need to keep track of last playRow so you can clear it again.
Or if the playlist is not that super long, you could just have a clear function that reset all.
void ResetPlayRow(QAbstractItemModel *model) { for (int var = 0; var < model->rowCount(); ++var) { model->setData(model->index(var, 0), false, Qt::UserRole); } }
and then clear all before you set new. /just like you must stop the old song playing)
int row = ui->listWidget->currentRow(); auto model = ui->listWidget->model(); ResetPlayRow(model); // reset old model->setData(model->index(row, 0), true, Qt::UserRole);
Then it works quite well but if you have many hundreds of rows, its not an optimal solution.
-
Hi
It's my fault.
Using an int for the row has the side effect that the model and the view know
nothing about we change the Play row so it doesn't update before you focus it.
Normally one can have model issues an update but cant do that here so I didn't find a good
way to let it know the row was changed so the delegate would be asked to repaint.So we could try use the model and a UserRole
class PlayingDelegate: public QStyledItemDelegate { Q_OBJECT public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); if (! index.isValid() ) return; const QAbstractItemModel *model = index.model(); if (!model) return; bool isPlay=model->data(index,Qt::UserRole).toBool(); if ( isPlay ) { painter->setBrush(QColor(0,255,0,100)); painter->drawRect( option.rect.adjusted(0, 0, -1, -1) ); } } };
and then we set the playRow like
int row= ui->listWidget->currentRow(); auto model = ui->listWidget->model(); model->setData(model->index(row,0),true, Qt::UserRole); // true to set it, false to clear it
This will update instant.
This has one side effect though. You need to keep track of last playRow so you can clear it again.
Or if the playlist is not that super long, you could just have a clear function that reset all.
void ResetPlayRow(QAbstractItemModel *model) { for (int var = 0; var < model->rowCount(); ++var) { model->setData(model->index(var, 0), false, Qt::UserRole); } }
and then clear all before you set new. /just like you must stop the old song playing)
int row = ui->listWidget->currentRow(); auto model = ui->listWidget->model(); ResetPlayRow(model); // reset old model->setData(model->index(row, 0), true, Qt::UserRole);
Then it works quite well but if you have many hundreds of rows, its not an optimal solution.