Formatting data in a QTableView - qt

I'm using a custom delegate to display QDoubleSpinBoxes in a QTableView. Those spinboxes display their contents with two decimals.
My problem is that I would like the QTableView to also display those numbers with two decimals while they are not being edited (at which point they are not in a QDoubleSpinBox). Or, rather, I would like to be able to specifiy a format for the QTableView's contents.
I tried to subclass QStyledItemDelegate to override displayText, but for a strange reason it crashes. It works correctly if I simply subclass QItemDelegate.
I'm using Qt 4.6.3, on Windows.

I'm not really sure what to make of the exception you are getting. Here is a simple QStyledItemDelegate that we are using without problems. Perhaps there is something different?
#include "model_view/color_combo_delegate.h"
#include <QTimer>
#include "map_elements/common/color_combo_box.h"
ColorComboDelegate::ColorComboDelegate(QObject *parent)
: QStyledItemDelegate(parent) {
}
QWidget *ColorComboDelegate::createEditor(
QWidget *parent,
const QStyleOptionViewItem & /*option*/,
const QModelIndex & /*index*/) const {
ColorComboBox *color_combo_box = new ColorComboBox(parent);
connect(color_combo_box, SIGNAL(currentIndexChanged(int)),
this, SLOT(IndexChanged()));
QTimer::singleShot(0, color_combo_box, SLOT(Popup()));
return color_combo_box;
}
QString ColorComboDelegate::displayText(const QVariant &value,
const QLocale &/*locale*/) const {
Map::Color color = static_cast<Map::Color>(value.toInt());
return Map::color_name(color);
}
void ColorComboDelegate::IndexChanged() {
ColorComboBox *color_combo_box = qobject_cast<ColorComboBox *>(sender());
emit commitData(color_combo_box);
emit closeEditor(color_combo_box);
}
void ColorComboDelegate::setEditorData(QWidget * editor,
const QModelIndex & index) const {
ColorComboBox *color_combo_box = qobject_cast<ColorComboBox *>(editor);
Map::Color color = static_cast<Map::Color>(index.data().toInt());
color_combo_box->set_color(color);
}
void ColorComboDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const {
ColorComboBox *color_combo_box = qobject_cast<ColorComboBox *>(editor);
model->setData(index, color_combo_box->color());
}

Well, I don't know what happened, but now it no longer crashes. And it now works.
For the record, this is my displayText method:
QString sqxSpinBoxDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
return locale.toString(value.toDouble(), 'f', Decimals);
}

Related

Proper elide and peridically set background color of QTableView cell and possible for user to edit value

To summarize:
I need a solution for QTableView cells which fulfills
Proper elide
Possible to set background color by emitting dataChanged() from the model in a method called by a timer at ~50ms
Possible for user to edit value as normal.
QTBUG for the elide: https://bugreports.qt.io/browse/QTBUG-87178
Solution which fulfills req 1 but not 2 and 3: How to prevent too aggressive text elide in QTableview?
Sample program which demonstrates the issue:https://udokaelectronics.com/files/untitled.zip
Comment out
ui->tableView->setItemDelegate(elideDelegate);
to see behavior with and without elide delegate.
Anyone with ideas?
This is what I've come up with.
A delegate for keeping track of when an edit is in progress. Then use the state of the delegate in the table models to avoid emitting dataChanged()
#pragma once
#include <QStyledItemDelegate>
class EditInProgressDelegate : public QStyledItemDelegate
{
public:
EditInProgressDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent), editInProgress_(false){};
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void destroyEditor(QWidget* editor, const QModelIndex& index) const override;
bool editInProgress() const { return editInProgress_; }
int editInProgressRow() const { return editInProgressRow_; }
int editInProgressColumn() const { return editInProgressColumn_; }
protected:
mutable bool editInProgress_;
mutable int editInProgressRow_;
mutable int editInProgressColumn_;
};
#include "EditInProgressDelegate.h"
QWidget* EditInProgressDelegate::createEditor(QWidget* parent,
const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
editInProgress_ = true;
editInProgressColumn_ = index.column();
editInProgressRow_ = index.row();
return QStyledItemDelegate::createEditor(parent, option, index);
};
void EditInProgressDelegate::destroyEditor(QWidget* editor, const QModelIndex& index) const
{
editInProgress_ = false;
QStyledItemDelegate::destroyEditor(editor, index);
}
A second delegate with a magic 10 to handle elide
#pragma once
#include "EditInProgressDelegate.h"
// This entire class is a plaster because QTableWidgetItem or
// QStyledItemDelegate malfunctions, see
// https://stackoverflow.com/questions/64198197/how-to-prevent-too-aggressive-text-elide-in-qtableview
// and
// https://bugreports.qt.io/browse/QTBUG-87178
class ElideDelegate : public EditInProgressDelegate
{
public:
ElideDelegate(QObject* parent = nullptr) : EditInProgressDelegate(parent){};
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
#include "ElideDelegate.h"
#include <QApplication>
#include <QPainter>
void ElideDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (!index.isValid())
{
return;
}
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
opt.text = opt.fontMetrics.elidedText(opt.text,
Qt::ElideRight,
opt.rect.width() - 10); // 10 was determined by experimentation
opt.textElideMode = Qt::ElideNone;
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}

Resizing a QTableView column horizontally to the content of an item delegated column which is painted with a new text in Qt

I want to show my database tabel content in a QTableView. I use the following codes to do that:
QSqlDatabase test = QSqlDatabase::addDatabase("QMYSQL");
test.setDatabaseName("dbText");
test.setHostName("localhost");
test.setUserName("***");
test.setPassword("***");
if (!test.open())
qDebug()<<"ERROR ";
QSqlTableModel *model = new QSqlTableModel(this,test);
model->setTable("textTable");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->tableView->setModel(model);
ui->tableView->show();
As it is depicted in the following picture, ui->tableView columns are resized to the content of the database table columns.
Now, I want to clear the display text of Description column and paint it with new text and color. For this propose, I have used the function QTableView::setItemDelegateForColumn as follow:
ui->tableView->setItemDelegateForColumn(2,new PowerColorDelegate(this));
And here are PowerColorDelegate header and source file content:
PowerColorDelegate.h
#include <QStyledItemDelegate>
class PowerColorDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
PowerColorDelegate(QObject *parent = 0);
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QString displayText(const QVariant &value, const QLocale &locale) const;
};
PowerColorDelegate.cpp
#include <QApplication>
#include <QPainter>
#include "powercolordelegate.h"
PowerColorDelegate::PowerColorDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
}
void PowerColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
if(!index.isValid())
{
QStyledItemDelegate::paint(painter, option, index);
return;
}
// Position our pixmap
const int x = option.rect.left();
const int y = option.rect.top();
QString newColor = "#fcaf9e";
QString newText = "This is my new text which I want to paint!";
painter->fillRect(option.rect, newColor);
painter->drawText(QRect(x, y, 80, 20), newText);
QStyledItemDelegate::paint(painter, opt, index);
}
QString PowerColorDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
return "";
}
The result of using PowerColorDelegate on ui->tableView is shown in this picture:
How can I resize the third ui->tableView column (Description) horizontally to the content of the painted column?
Implement sizeHint for your delegate according to required text format

qstyleditemdelegate subclassing paint method not working right

I extended the qstyleditemview class. When I am in the editing mode for the qtreeview item, the paint method seems not to be executing right. When I change state to QStyle::State_Selected it works - it paints the selected row (text) in the qtreeview.
Any idea why it is not working in editing mode?
void myQItemDelegate::paint(QPainter *painter,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
QStyleOptionViewItem s = *qstyleoption_cast<const QStyleOptionViewItem*>(&option);
if(s.state & QStyle::State_Editing)
{
painter->fillRect(s.rect, s.palette.highlight());
s.palette.setColor(QPalette::HighlightedText, QColor(Qt::blue));
}
QStyledItemDelegate::paint(painter, s, index);
}
In the State_Editing state the editor that is a widget created in createEditor() method is opened so that it will not be affected by the QStyleOptionViewItem palette.
Also instead of overwriting the paint method, use the initStyleOption() method:
#include <QtWidgets>
class StyledItemDelegate: public QStyledItemDelegate
{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
QWidget * widget = QStyledItemDelegate::createEditor(parent, option, index);
QPalette pal(widget->palette());
pal.setColor(QPalette::HighlightedText, QColor(Qt::blue));
pal.setBrush(QPalette::Highlight, option.palette.highlight());
widget->setPalette(pal);
return widget;
}
protected:
void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const override{
QStyledItemDelegate::initStyleOption(option, index);
option->palette.setColor(QPalette::HighlightedText, QColor(Qt::blue));
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeView w;
QStandardItemModel model;
w.setModel(&model);
w.setItemDelegate(new StyledItemDelegate);
for(int i=0; i<3; ++i){
auto it = new QStandardItem(QString::number(i));
model.appendRow(it);
for (int j=0; j<4; ++j) {
it->appendRow(new QStandardItem(QString("%1-%2").arg(i).arg(j)));
}
}
w.expandAll();
w.show();
return a.exec();
}
Thanks for helping me out.
I understand now. I added code in QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const metod to style QLineEdit. I am trying to get the same background color for QLineEdit when it is created- when I am editing qtreeview item. The problem is when I select the item in qtreeview the whole row gets colored. That's ok. Now when I edit the item for example to change the text in qtreeview item, only the text part gets selected and colored with the same color as previous row selection color. The rest of the QLineEdit is white. In editing mode I would like to color the whole row which is edited with the same color. I could as obviously from my code color it with RGB but I don't know the exact RGB values. Is there any way to get the exact RGB color from item selection and then use it in
pal.setColor(QPalette::Highlight,QColor(qRgb(0,0,255)));
my code:
QWidget* myQItemDelegate::createEditor(QWidget *parent,const QStyleOptionViewItem &option,const QModelIndex &index) const
{
QLineEdit *lineEdit = new QLineEdit(parent);
connect(lineEdit,SIGNAL(editingFinished()),this,SLOT(commitAndCloseEditor()));
QPalette pal;
pal.setColor(QPalette::HighlightedText, QColor(Qt::white));
pal.setColor(QPalette::Highlight,QColor(qRgb(0,0,255)));
lineEdit->setPalette(pal);
lineEdit->setFrame(false);
return lineEdit;
}
Thanks, Tom

Correctly implementing QStyledItemDelegate

I have a class (EditorTagManager) that contains a QTreeWidget. During runtime, the tree can contain any number of tag items, all of which are checkable. I'm trying to add horizontal lines between the QTreeWidgetItems in order to make it clear that these tags are unrelated and are meant to be separate from one another (each item is a root-level node).
From my research on the subject, I've figured out the only way to control the appearance of QtreeWidgetItems to any meaningful extent is to subclass QStyledItemDelegate and bind the delegate to the QTreeWidget. It's kind of an abstract concept so I don't fully understand it. Since I’ve never had to subclass a Qt object before, I'm not sure if I’m doing it correctly.
Since the Qt Documentation didn't really explain how to do this, I used the settingsdialog.cpp/.h files from the Clementine 1.0.1 source code as my guide/reference because Clementine's preferences window uses similar separators on its QTreeWidget. I'm trying to reverse-engineer my own solution from Clementine's code, the only problem is Clementine's implementation of this does things I don't need (so I have to figure out what's relevant to my code and what's not). That's what got me up to this point; my code is very similar to the Clementine code (I just changed the delegate class name):
Here is my current delegate header declaration in editortreemanager.h:
class TagListDelegate : public QWidget
{
public:
TagListDelegate(QObject* parent);
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const;
};
Here is my current delegate source in editortreemanager.cpp:
TagListDelegate::TagListDelegate(QObject *parent) :
TagListDelegate(parent){
}
void TagListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const{
}
Even though TagListDelegate::paint() doesn't actually do anything yet, I just want to get this code working correctly before I try to change the appearance of the QTreeWidgetItems. My goal is to keep this as simple as possible for now.
Everything compiled fine until I told the QTreeWidget (ui->AvailableTags) to use the delegate:
ui->AvailableTags->setItemDelegate(new TagListDelegate(this));
The compiler error reads:
/home/will/qt_projects/robojournal/ui/editortagmanager.cpp:211: error:
no matching function for call to
'QTreeWidget::setItemDelegate(TagListDelegate*)'
I’m in a bit over my head here so I would definitely appreciate some help in figuring this out.
UPDATE (7/30/13):
My Delegate class now looks like this:
Source:
TagListDelegate::TagListDelegate(QStyledItemDelegate *parent) :
TagListDelegate(parent){
}
void TagListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const{
QStyledItemDelegate::paint(painter, option, index);
}
Header declaration:
class TagListDelegate : public QStyledItemDelegate
{
public:
TagListDelegate(QStyledItemDelegate* parent);
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const;
};
UPDATE (7/31/13)
Here is what my classes look like now:
header:
class TagListDelegate : public QStyledItemDelegate
{
public:
TagListDelegate(QObject* parent);
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const;
};
source:
TagListDelegate::TagListDelegate(QObject *parent)
: TagListDelegate(parent){
}
QSize TagListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize ret = QStyledItemDelegate::sizeHint(option, index);
return ret;
}
void TagListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const{
QStyledItemDelegate::paint(painter, option, index);
}
You're not subclassing QStyledItemDelegate in your code. You're subclassing QWidget.
Change
class TagListDelegate : public QWidget
to:
class TagListDelegate : public QStyledItemDelegate
And don't forget to include the header:
#include <QStyledItemDelegate>

Determine if QTableView has an open editor

Is there any way to determine if a QTableView has an open editor in the current cell? I need to handle the following situation:
A user double-clicks a cell and edits the data, but leaves the cell in the "edit" state.
On another part of the UI, an action is taken that changes the selected row of the underlying model.
Back on my view, I want to determine if the newly selected row is the same as the open row. If not, I need to take an action. (Prompt the user? Commit automatically? Revert?)
I see how to get the current item, and can get the delegate on that item, but I don't see any isEditMode() property I was hoping to find.
Can someone point me in the right direction?
Just check whether the return value of
State QAbstractItemView::state () const
is
QTableView::EditingState
Connect to underlying model dataChanged signal
void QAbstractItemModel::dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight )
You can check if the cell where data has changed is the same than the currentIndex
QModelIndex QAbstractItemView::currentIndex () const
You cannot know if the current cell had an open editor straight, but can check if the view is in QAbstractItemView::EditingState
State QAbstractItemView::state () const
It should be enough to do what you want.
You can subclass QTableView in order to be able to access the state() function, which is unfortunately protected. However, I did not try that.
If you already have an QStyledItemDelegate subclass, you can use it to track whether an editor is currently open. However, you can't just use setEditorData/setModelData, because setModelData won't be called, when the user cancels editing. Instead, you can track the creation and destruction of the editor itself.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate( QObject* parent = nullptr );
~MyItemDelegate();
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void setEditorData( QWidget* editor, const QModelIndex& index ) const;
void setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const;
bool isEditorOpen() const { return *m_editorCount > 0; }
protected:
int* m_editorCount;
protected slots:
void onEditorDestroyed( QObject* obj );
};
Implementation:
MyItemDelegate::MyItemDelegate( QObject* parent ) :
QStyledItemDelegate( parent )
{
m_editorCount = new int;
*m_editorCount = 0;
}
MyItemDelegate::~MyItemDelegate()
{
delete m_editorCount;
}
QWidget* MyItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// create an editor, can be changed as needed
QWidget* editor = QStyledItemDelegate::createEditor( parent, option, index );
connect( editor, SIGNAL(destroyed(QObject*)), SLOT(onEditorDestroyed(QObject*)));
printf( "editor %p created\n", (void*) editor );
(*m_editorCount)++;
return editor;
}
void MyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
...
}
void MyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
...
}
void MyItemDelegate::onEditorDestroyed( QObject* obj )
{
printf( "editor %p destroyed\n", (void*) obj );
(*m_editorCount)--;
}
On some occasions, e.g. when moving to the next item in the tree using the cursor keys, Qt will create the new editor first and then destroy the old one. Hence, m_editorCount must be an integer instead of a bool.
Unfortunately, createEditor() is a const function. Therefore, you cannot create an int-member. Instead, create a pointer to an int and use that.
Subclass your delegate so that it includes an accessor that tells you when it's editing:
void MyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const {
// _isEditing will have to be mutable because this method is const
_isEditing = true;
QStyledItemDelegate::setEditorData(editor, index);
}
void MyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const {
QStyledItemDelegate::setModelData(editor, model, index);
_isEditing = false;
}
bool MyDelegate::isEditing() const { return _isEditing; }
Then you can just check the delegate to see what's going on. Alternatively and/or if you don't like the mutable, you can emit signals so you know what state the delegate is in.
If you know the index of the item being edited, you can call indexWidget() and attempt to cast it. If it's valid, you not only know you're editing, but you also have your editor widget handy.
EditWidget *editWidget = qobject_cast<EditWidget*>(tableView->indexWidget(tableView->currentIndex()));
if(editWidget)
{
//yep, ur editing bro
}
Here is an idea, its even helpful to get the edit/combo widget before the edit begins...
just emit a signal and consume it in the mainwindow... this is what I used one to get combo box in QTableWidget before editing...
first create a signal in ComoBoxItemDelegate...
signals:
void OnComboEdit(QComboBox* pCombo) const;
then emit the signal in the createEditor method...
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// Create the combobox and populate it
QComboBox* cb = new QComboBox(parent);
emit OnComboEdit(cb);
return cb;
}
and in the MainWindow declare a function to receive the singal...
void MainWindow::OnComboEidt(QComboBox *pCB) const
{
qDebug() << "Combo Eidt Singal Received";
}
Then finally in the constructor of MainWindow connect it...
ComboBoxItemDelegate* cbid = new ComboBoxItemDelegate(ui->tableWidget);
connect(cbid, &ComboBoxItemDelegate::OnComboEdit, this, &MainWindow::OnComboEidt);
ui->tableWidget->setItemDelegateForColumn(0, cbid);

Resources