[solved] Changing row background of tableview afterwards



  • Hi,

    Following scenario:
    I get data from a video stream and write them into the table (record mode). If I switch to the view mode rows are related to the actual frame should changed the background to e.g. red ...

    I implemented the following in a read-only model/view:

    The funktion to initiate the change:
    @void ObjectListModel::setRowBackground(int row)
    {
    m_change_bg = true;
    for (int column = 0; column < columnCount(); column++)
    {
    QModelIndex _index = index(row, column, QModelIndex());
    setData(_index, color, Qt::BackgroundRole);
    }
    m_change_bg = false;
    }@

    The setData() function:
    @bool ObjectListModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
    // validate the index and that row and column are inside the given borders...
    if(!index.isValid() || (m_controller->size() <= index.row()) || (columnCount() <= index.column()))
    return false;

    if(Qt::BackgroundRole == role)
    {
        index.data(role);
        emit dataChanged(index, index);
        return true;
    }
    else
        return false;
    

    }@

    The changed data() function:
    @QVariant ObjectListModel::data(const QModelIndex &index, int role) const
    {
    if(!index.isValid())
    return QVariant();

    switch (role)
    {
    case Qt::DisplayRole:
        {
            QList<QVariant> entries = m_controller->at(index.row());
            return entries[index.column()].toString();
        }
    case SortRole:
        {
            QList<QVariant> entries = m_controller->at(index.row());
            return entries[index.column()].toInt();
        }
    case Qt::BackgroundRole:
        {
        if (m_change_bg == true)
            return QVariant(QBrush(Qt::red));
        }
    }
    return QVariant();
    

    }@

    As I debug it, the correct role will be executed, but nothing is changed in the view. If I delete the "if (m_change_bg == true)" in the data function all rows get red. What do I forget or wrong?

    thanks in advance,
    Thomas



  • Hi,

    I'm not sure what is connected to emit dataChanged(index, index), but anyway, you're setting global flag to false at the end of void ObjectListModel::setRowBackground(int row):
    m_change_bg = false;

    I'm also not sure why are you not setting your color in the bool ObjectListModel::setData(const QModelIndex &index, const QVariant &value, int role)

    In that case you w'll be able to take real data at given index without any flag.



  • [quote author="gmaro" date="1333619612"]I'm not sure what is connected to emit dataChanged(index, index)[/quote]Nothing because I found no code which this signal is connected to ...
    In this program "VirtualCdRack":http://www.gitorious.org/qtdevnet-wiki-mvc/qtdevnet-wiki-mvc/blobs/raw/20175536aac6c1d39132ce5e7057699b629cb962/VirtualCdRackPart3.zip from a german "Model/View tutorial":http://qt-project.org/groups/qt_german/wiki/Model_View_Tutorial also no connect() with dataChanged() is used.

    [quote author="gmaro" date="1333619612"]but anyway, you're setting global flag to false at the end of void ObjectListModel::setRowBackground(int row):
    m_change_bg = false;[/quote]Because if I don't do this all rows will be changed to red background.

    [quote author="gmaro" date="1333619612"]I'm also not sure why are you not setting your color in the bool ObjectListModel::setData(const QModelIndex &index, const QVariant &value, int role)

    In that case you w'll be able to take real data at given index without any flag.[/quote]Ok, but I don't know how ...



  • Thanks for clarification.

    Maybe you don't need to use this at all? If you have some QTableView, maybe just a QStandardItemModel is enough.

    If I understand you correctly.
    You have the table with some data, and you know on which frame which rows need to be red. So the function may look like that:
    @
    void ObjectListModel::setRowBackground(int row, const QColor &color )
    {
    for (int column = 0; column < columnCount(); column++)
    {
    QStandardItem *i = model->item(row, column);
    if( i != 0 )
    {
    i->setData( color, Qt::BackgroundRole );
    }
    }
    }
    @

    now, after you selected the new rows with Qt:red, you may decolorize the previous ones with Qt::white, so the rest of model reimplementation is not necessary.

    Other solution might be by using the selection in QTableView instead of modyfing model data (in case that you don't need the QTableView selection behaviour for other purposes.

    Sorry if I'm not getting your point exactly but hope that helps. I can prepare some example of that if I'm not clear.



  • [quote author="gmaro" date="1333625935"]Maybe you don't need to use this at all? If you have some QTableView, maybe just a QStandardItemModel is enough.[/quote]Unfortunately I have created an own model class based on QAbstractItemModel. Thus no model->item(..) exist.

    [quote author="gmaro" date="1333625935"]Other solution might be by using the selection in QTableView instead of modyfing model data (in case that you don’t need the QTableView selection behaviour for other purposes.[/quote]Yes, that's my intend, that I can use the selection for delete some rows if needed.



  • Btw what I forgot:
    for sorting I use a QSortFilterProxyModel ([Source model] -> [Sort/filter proxy] -> [View]). Perhaps it's relevant ...



  • -Ok got it. I'll prepare the example solution of that when i get a free minute. I hope that's not extremely urgent.-

    I think so, If you processing the data throuth the proxy model your flag is set to false so the data() function (from base model) returns QVariant() instead of Qt::Red color.



  • Just to clarify: the dataChanged() signal is connected to by the view when you set the model. So, even if there is no slot connected to that signal in your own code, Qt under the covers uses it.



  • [quote author="Andre" date="1333970623"]Just to clarify: the dataChanged() signal is connected to by the view when you set the model. So, even if there is no slot connected to that signal in your own code, Qt under the covers uses it. [/quote]But what function should I call? I thought it's enough if the data() function is called?

    At the moment I have implemented it as followed: [Source model] -> [highlight proxy] -> [View]
    I click a checkbox to view. Here I call
    @void VdBmtcLearn::highlightActualLabels()
    {
    QList<int> indexes_of_labels = m_controller->getIndexesOfFrameLabels(m_actual_frame.m_viewed_labels);
    for (int i=0; i<indexes_of_labels.length(); i++)
    {
    m_high_proxy->setRowBackground(indexes_of_labels[i]);
    //m_model->setRowBackground(indexes_of_labels[i]);
    }
    }
    @

    The setRowBackground() calls setData from highlight proxy:
    @void ObjectListHighlightProxy::setRowBackground(int row)
    {
    m_controller->m_change_bg = true;
    emit layoutAboutToBeChanged();
    for (int column = 0; column < columnCount(); column++)
    {
    QModelIndex _index = index(row, column, QModelIndex());
    setData(_index, Qt::BackgroundRole);
    //emit dataChanged(_index, _index);
    }
    emit layoutChanged();
    m_controller->m_change_bg = false;
    }
    @

    It jumps into the data() function from the highlight proxy:
    @QVariant ObjectListHighlightProxy::data(const QModelIndex &index, int role) const
    {
    if(!index.isValid())
    return QVariant();

    if (Qt::BackgroundRole == role && m_controller->m_change_bg == true)
        return QVariant(Qt::red);
    else
        return QSortFilterProxyModel::data( index, role );
    

    }
    @

    The role is set to Qt::BackgroundRole (int 8) and returns QVariant(Qt::red). But the background won't be changed. Must I repaint the parent? Does that not ObjectListHighlightProxy::data() do?
    It's the same behavior as without a proxy if I call the base model directly.

    Regards,
    Thomas



  • You are not actually changing the layout of the model, so why do you emit that signal? Use the dataChanged signal instead. Also, I don't really get the way you use your proxy with your source model. You are quickly setting some controller value to true, and then immediately back to false again. It doesn't work that way. Redraws go through the event loop, so by the time your data() method is called, the controller's m_change_bg value will be false again.

    What I would do, is first make a choice. What is the responsibility of the source model, and what of the proxy model? You don't want them to share responsibilities (in this case: for handing some highlighting). If you decide that this belongs in the proxy, then the source model should stay out of the picture and is no longer involved. Instead, reimplement the data() method in your proxy model, and return the right value for the highlighted rows.

    The proxy model will have to keep track of which rows to highlight though. For that, it could simply keep a QSet<int> with row numbers for a list of a table, or a QSet<QPersistentModelIndex> in case of a tree structure. Your setRowBackground() method then manipulates this list, and notifies connected views of the changes via the dataChanged() signal. The actual new background value is supplied by the proxy model's data() implementation, that gives the modified color in case the row is in the set of rows to color, and the data from the base model for all other cases.



  • First, thanks for quick reply :-)
    [quote author="Andre" date="1334047654"]You are not actually changing the layout of the model, so why do you emit that signal? [/quote]It's a hangover from an earlier test. ok, will be removed.

    [quote author="Andre" date="1334047654"]Use the dataChanged signal instead.[/quote]I use it in the setData() function of the highlight proxy (sorry forgot to post :~ ):
    @bool ObjectListHighlightProxy::setData(const QModelIndex &index, int role)
    {
    if(!index.isValid())
    return false;

    if(Qt::BackgroundRole)
    {
        index.data(role);
        emit dataChanged(index, index);
        return true;
    }
    else
        return false;
    

    }
    @

    [quote author="Andre" date="1334047654"]Also, I don't really get the way you use your proxy with your source model.[/quote]In the parent I initialize it so:
    @
    void VdBmtcLearn::Create(QWidget* parent, QDockWidget* dock, RemoteCtrlInterface* ctrlobj)
    {
    :
    :
    m_model = new ObjectListModel(m_controller, this);

        // use proxy model to change background of viewed rows
        m_high_proxy = new ObjectListHighlightProxy(this);
        m_high_proxy->setSourceModel(m_model);
    
        m_view = new ObjectListTableView(m_dialog->centralWidget());
        m_view->m_table->setModel(m_high_proxy);
        :
        :
    

    }
    @
    The complete background change will be done in the proxy. Here's the class declaration:
    @class ObjectListHighlightProxy : public QSortFilterProxyModel
    {
    Q_OBJECT
    public:
    explicit ObjectListHighlightProxy(QObject *parent = 0);
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    bool setData(const QModelIndex& index, int role);

    void setRowBackground(int row);
    

    };@

    [quote author="Andre" date="1334047654"]You are quickly setting some controller value to true, and then immediately back to false again. It doesn't work that way. Redraws go through the event loop, so by the time your data() method is called, the controller's m_change_bg value will be false again.[/quote] No, that's not true. If I debug it and set breakpoints in

    1. VdBmtcLearn::highlightActualLabels() which calls m_high_proxy->setRowBackground(indexes_of_labels[i]);
    2. ObjectListHighlightProxy::setRowBackground(int row) which calls setData(_index, Qt::BackgroundRole);
    3. ObjectListHighlightProxy::setData(const QModelIndex &index, int role) which do the dataChanged()
    4. ObjectListHighlightProxy::data(const QModelIndex &index, int role) which returns QVariant(Qt::red)
      I saw in ObjectListHighlightProxy::data that m_change_bg is set (true) because it jumps into the block
      @ if (Qt::BackgroundRole == role && m_controller->m_change_bg == true)
      return QVariant(Qt::red);
      @

    and return QVariant(Qt::red)

    [quote author="Andre" date="1334047654"]What I would do, is first make a choice. What is the responsibility of the source model, and what of the proxy model? You don't want them to share responsibilities (in this case: for handing some highlighting). If you decide that this belongs in the proxy, then the source model should stay out of the picture and is no longer involved. Instead, reimplement the data() method in your proxy model, and return the right value for the highlighted rows.[/quote]The source model fill the table, remove rows and all that stuff with the data (the controller handles the data management).
    the proxy do only the highlighting. So all is implemented as you suggested ... but as no background is changed i do something wrong ...

    [quote author="Andre" date="1334047654"]The proxy model will have to keep track of which rows to highlight though. For that, it could simply keep a QSet<int> with row numbers for a list of a table.[/quote] ok, I will read the documentation about QSet ...

    [quote author="Andre" date="1334047654"]Your setRowBackground() method then manipulates this list, and notifies connected views of the changes via the dataChanged() signal. The actual new background value is supplied by the proxy model's data() implementation, that gives the modified color in case the row is in the set of rows to color, and the data from the base model for all other cases. [/quote] Does that not happens actually with my implementation?



  • Hi,

    after many unsuccessful tests with Qt::BackgroundRole and setData() I've switched to the delegate option. And, if I had knew how easy it is to use this possibility I had saved 3 days of frustration ...

    For any newbies who want to know how this works, here's my implementation:

    First, I delete all stuff with setData ...

    Second, I've overwritten the paint() function of QStyledItemDelegate:
    @void BackgroundColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
    if(index.data().isValid())
    {
    QStyleOptionViewItem viewOption(option);
    painter->save();
    painter->fillRect(option.rect, QColor(238, 233, 233, 255));
    painter->restore();
    viewOption.palette.setColor(QPalette::Text, QColor(Qt::black));
    QStyledItemDelegate::paint(painter, viewOption,index);
    }
    else
    {
    QStyledItemDelegate::paint(painter, option, index);
    }

    }
    @
    (found it here: "QTableView and QTreeView item background":http://permalink.gmane.org/gmane.comp.lib.qt.general/29846 and here: "Change appearence of selected row in QTableView":http://www.qtcentre.org/threads/21482-Change-appearence-of-selected-row-in-QTableView )

    Third, I create the delegate object:
    @BackgroundColorDelegate* m_background_delegate;@
    @m_background_delegate = new BackgroundColorDelegate;@

    Fourth, I activate the custom delegate every time I need it (normally in the view mode) else the default delegate will use:
    @void VdBmtcLearn::changeBgOfSelectedLabelsInView(bool bg_change)
    {
    QList<int> indexes_of_labels = m_controller->getIndexesOfFrameLabels(m_actual_frame.viewed_labels_list);
    for (int i=0; i<indexes_of_labels.length(); i++)
    {
    QModelIndex sourceIndex = m_model->index(indexes_of_labels[i], 0);
    QModelIndex viewIndex = getViewIndex(sourceIndex);
    if (bg_change)
    m_view->m_table->setItemDelegateForRow(viewIndex.row(), m_view->m_background_delegate);
    else
    m_view->m_table->setItemDelegateForRow(viewIndex.row(), new QStyledItemDelegate);
    }
    }
    @

    (found how to call the custom delegate here: "Show other data in QTableView with QItemDelegate":http://stackoverflow.com/questions/2013052/show-other-data-in-qtableview-with-qitemdelegate )

    Thanks all for helping anyway ;-)
    Cheers,
    Thomas


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.