Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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.


  • Lifetime Qt Champion

    @hbatalha

    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.

    alt text

    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.

    alt text



  • @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.


  • Lifetime Qt Champion

    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


  • Lifetime Qt Champion

    Hi
    For such effect, you can also consider at Delegate.

    alt text
    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 functions

    Then 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.


  • Lifetime Qt Champion

    @hbatalha

    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.

    alt text

    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.

    alt text



  • @mrjj Working like a charm, thank you very much.


Log in to reply