PDA

View Full Version : How Do I Refresh a View if the Delegate Changes its Display Format?



ChrisW67
17th March 2010, 23:56
I have a series of table models displayed either in a QTableView or QDataWidgetMapper. Each display widget has an attached QStyledItemDelegate derivative. The delegate's paint method is responsible for displaying date and duration columns in a format that is dynamically changeable by the user, e.g. a duration column can be displayed in whole minutes, decimal hours, or HH:MM.

I would like the related views to update when this format changes but haven't found the "correct" way to do this. I can have the delegates emit a signal when the format changes but there is nothing obvious to connect it to that would cause the view to refresh. For the QDataWidgetMapper, which displays a single row output in my application, I can force a refresh using toFirst(). I haven't found the equivalent for QTableView (other than reset() which loses selections etc.). The example code below shows the sort of thing I'm after. The table view only redraws when scrolled or resized.

I've also considered pushing the determination of the display text back into the models. The Qt::DisplayRole could return the variably formatted date/duration and emit layoutChanged() to force refresh. This doesn't work with the mapper though.

Any suggestions?



#include <QtGui>

class TestDelegate: public QStyledItemDelegate {
Q_OBJECT
public:
TestDelegate(QObject * parent = 0 )
: QStyledItemDelegate(parent)
{
m_format = true;
}

void paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
Q_ASSERT(index.isValid());
qDebug() << "Paint called for ("<< index.row() << "," << index.column() << ")";

// Default setup for this index
QStyleOptionViewItemV4 opt = option;
initStyleOption(&opt, index);
const QWidget *widget = opt.widget;
QStyle *style = widget ? widget->style() : QApplication::style();

// Change the display the columns
int mins = index.data(Qt::EditRole).toInt();
if (m_format) {
// H:MM
int hours = mins / 60;
mins = mins % 60;
opt.text = QString("%1:%2").arg(hours).arg(mins, 2, 10, QLatin1Char('0'));
}
else {
// raw minutes
opt.text = opt.locale.toString(mins);
}

// Draw it
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
}

public slots:
void toggleFormat() {
m_format = !m_format;
qDebug() << "Toggled to : " << m_format;
}

private:
bool m_format;

};



class MainWindow: public QWidget {
Q_OBJECT
public:
MainWindow(QWidget *parent=0):
QWidget(parent)
{
m_button = new QPushButton(this);
m_button->setText("Toggle format");

m_view = new QTableView(this);
TestDelegate *del = new TestDelegate(this);
m_view->setItemDelegate(del);
connect(m_button, SIGNAL(clicked()), del, SLOT(toggleFormat()));

QVBoxLayout *layout= new QVBoxLayout(this);
layout->addWidget(m_view);
layout->addWidget(m_button);
}

void setModel(QAbstractItemModel *model) {
m_view->setModel(model);
}

public slots:

private:
QTableView *m_view;
QPushButton *m_button;
};


int main(int argc, char *argv[])
{
QApplication a(argc, argv);

QStandardItemModel *model = new QStandardItemModel();
for (int row=0; row<10; row++) {
for (int col=0; col<5; col++) {
QStandardItem *newItem = new QStandardItem();
newItem->setData(row*10+col, Qt::EditRole);
model->setItem(row ,col, newItem);
}
}


MainWindow main;
main.setModel(model);
main.show();

return a.exec();
delete model;
}

#include "main.moc"

ChrisW67
18th March 2010, 05:22
Thanks to the kind souls who read my plea.

A eureka/d'oh! moment has lead me to a solution for the table view. I have the delegate emit a signal that I connect to the QTableView::viewPort() update() slot.

I'm still working on the equivalent for the widget mapper. Connecting to the update() or repaint()slot of the widget containing all the mapped widgets does not do it.

aamer4yu
18th March 2010, 05:40
You could have also used something in MainWindow ctor like -

connect(m_button, SIGNAL(clicked()), this, SLOT(toggleFormat()));
// and in MainWindow::toggleFormat()
MainWindow::toggleFormat()
{ del->toggleFormat();
m_view->update();
}

This would keep the work of delegate to only render. I dont think it was good that delegate tells view to update. I guess its the controller / creator's job.

ChrisW67
18th March 2010, 05:49
In the real application the signal to change date or duration format comes from outside of the affected view (I just put it there for the example). I could connect that signal to a view slot as you suggest and, yes, it probably is neater. Thanks for the thought.