QTableView has unwanted checkboxes in every cell - c++

I'm just getting started with Qt programming, and I'm trying to make a simple tabular data layout using a QTableView control with a model class of my own creation inheriting from QAbstractTableModel. For some reason, my table view ends up looking like this:
(source: nerdland.net)
What in the heck are those things that look like checkboxes (but don't do anything when I click them) in every cell, and how do I make them go away? I haven't changed any of the QTableView properties except for the object's name.
If it matters, my model code is dead simple:
MyTableModel::MyTableModel(QObject* parent)
: QAbstractTableModel(parent)
{
}
MyTableModel::~MyTableModel()
{
}
int MyTableModel::rowCount(const QModelIndex& parent) const
{
return 1000;
}
int MyTableModel::columnCount(const QModelIndex& parent) const
{
return 5;
}
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
return "Foo";
}
The dialog UI is built in Qt Designer, and inside the class for the dialog I attach the model to the view like this:
MyTableModel testModel = new MyTableModel(this);
ui.testTable->setModel(testModel);
Other than that I perform no operations on ui.testTable.
Using Qt 4.6.

Try changing MyTableModel::data() to the following:
QVariant MyTableModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DisplayRole)
return "foo";
else
return QVariant();
}
Probably the returned QVariant for role Qt::CheckStateRole was misunderstood by the QTableView.

Do you by any chance happen to set the Qt::ItemIsUserCheckable flag in flags()?

Related

Save state of QTableWidget cell widget checkbox before setting its state

I have a QTableWidget, where one column is filled with custom checkboxes. Also, I have implemented an undo mechanic so that every change to the table can be undone. For the other item columns, where only text is stored, I basically achieve it in the following way:
Every time an item in the table is pressed (calling the itemPressed signal), I store the table data before the item editing starts using a function called saveOldState. After editing (and triggering an itemChanged signal), I push the actual widget content together with the old content onto a QUndoStack instance using a function called pushOnUndoStack.
Now I want to achieve a similar thing for the cell widgets. However, changing the checkbox state does not trigger itemChanged. Thus, I have to connect to the checkbox's stateChanged signal to save the new state:
QObject::connect(checkBox, &QCheckBox::stateChanged, this, [checkBox] {
pushOnUndoStack();
});
So, getting the newest table data is not that hard. However, I am struggling to find the right moment to save the data before the checkbox is set, because there is no similar variant for an itemPressed signal in case of a cell widget.
My question is: Is there a good alternative way to store the checkbox state immediately before the state is actually set? Currently, my only idea is to implement a custom mouse move event filter for the cell widget, which calls saveOldState the moment a user moves the mouse inside the cell widget's boundaries. But is there maybe a better way?
Chapter 1: the solution you cannot use
What I think is the correct way to address your question is a proxy model in charge of maintaining the undo stack. It does so by saving the model's data right before changing it.
Header:
class MyUndoModel : public QIdentityProxyModel
{
public:
MyUndoModel(QObject* parent = nullptr);
bool setData(const QModelIndex& index, const QVariant& data, int role) override;
bool restoreData(const QModelIndex& index, const QVariant& data, int role);
private:
void pushOnUndoStack(const QPersistentModelIndex& index, int role, const QVariant& value) const;
//QUndoStack undoStack;
};
Source:
bool MyUndoModel::setData(const QModelIndex& index, const QVariant& data, int role)
{
QVariant currentData = index.data(role);
bool result = QIdentityProxyModel::setData(index, data, role);
if (result) {
//If the source model accepted the change, push currentData to the undo stack.
pushOnUndoStack(QPersistentModelIndex(index), role, currentData );
}
return result;
}
bool MyUndoModel::restoreData(const QModelIndex& index, const QVariant& data, int role)
{
return QIdentityProxyModel::setData(index, data, role);
}
Note that we use QPersistentModelIndexes in a modified version of pushOnUndoStack (that I let you implement yourself). Also, I did not write how the stacked undo/redo commands should be processed, apart from calling restoreData. As long as you get the idea...
Chapter 2: where it fails for you
The above solution works regarless of the actual class of the source model ... except if working with QTableWidget and QTreeWidget.
What blocks this solution in the case of e.g. QTableWidget is its internal model (QTableModel).
You cannot substitute model of your QTableWidget to use MyUndoModel instead.
If you try, you will very quickly see your application crash.
You could in theory subclass QTableModel to perform the above substitution but I advise against it.Sample: myTableWidget->QTableView::setModel(new MyQTableModel);QTableModel is a private class in Qt and should not be used directly. I wish I knew why it was done this way.
Chapter 3:The alternative solution
Alternatively, subclassing QStyledItemDelegate could work for you. The design is not as clean, there are more ways to make a mistake when using it in your window but it essentially follows the same logic as the above proxy model.
class UndoItemDelegate : protected QStyledItemDelegate
{
public:
UndoItemDelegate(QUndoStack* undoStack, QObject* parent = nullptr);
//Importnt: we set setModelData as final.
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override final;
protected:
virtual QVariant valueFromEditor(QWidget *editor) const noexcept = 0;
virtual int roleFromEditor(QWidget *editor) const noexcept = 0;
private:
void pushOnUndoStack(const QPersistentModelIndex& index, int role, const QVariant& value) const;
//undoStack as a pointer makes it possible to share it across several delegates of the same view (or of multiple view)
mutable QUndoStack* undoStack;
};
The magic is in setModelData.
void UndoItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
auto role = roleFromEditor(editor);
QVariant currentData = index.data(role);
bool dataChanged = model->setData(index, valueFromEditor(editor), role);
if (dataChanged && undoStack) {
pushOnUndoStack(QPersistentModelIndex(index), role, currentData);
}
}
I kept the version with index (my habit) but you could use the pointers to QTableItem of course.
To be used (most likely in the constructor of your window):
ui->setupUi(this);
auto myDelegate = new MyUndoItemDelegateSubclass(&windowUndoStack, ui->myTableWidget);
ui->myTableWidget->setItemDelegate(myDelegate);
You will have to implement:
pushOnUndoStack (once).
roleFromEditor and valueFromEditor (for every subclass).
the processing of undo/redo commands.
Edit to address your comment.
I am going to assume you know how QAbstractIdemModel and subclasses work in a generic manner. To manipulate a checkState in the model of a QTableWidget, I recommend you create a UndoCheckboxDelegate subclass to implement/override the additional methods this way:
Header:
class UndoCheckboxDelegate : public UndoItemDelegate
{
public:
UndoCheckboxDelegate(QUndoStack* undoStack, QObject* parent = nullptr);
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
protected:
virtual QVariant valueFromEditor(QWidget *editor) const noexcept override;
virtual int roleFromEditor(QWidget *editor) const noexcept override;
};
Source:
UndoCheckboxDelegate::UndoCheckboxDelegate(QUndoStack* undoStack, QObject* parent)
: UndoItemDelegate(undoStack, parent)
{}
QWidget* UndoCheckboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.isValid()) {
QCheckBox* control = new QCheckBox(parent);
control->setText(index.data(Qt::DisplayRole).toString());
control->setCheckState(index.data(Qt::CheckStateRole).value<Qt::CheckState>());
return control;
}
else
return nullptr;
}
QVariant UndoCheckboxDelegate::valueFromEditor(QWidget *editor) const noexcept
{
if (editor)
return static_cast<QCheckBox*>(editor)->checkState();
else
return QVariant();
}
int UndoCheckboxDelegate::roleFromEditor(QWidget * /* unused */) const noexcept
{
return Qt::CheckStateRole;
}
It may be only a starting point for you. Make sure it correctly fills the undo stack first; after that, you can tweak the behavior a bit.

QTreeView Edit UserRole Instead of DisplayRole Upon Double Click

In my project, I have a QTreeView displaying items from a QStandardItemModel. Each item has data stored in several UserRoles.
QStandardItem* item = new QStandardItem();
item->setIcon(iconByte);
item->setData(3, Qt::UserRole+1);
item->setData(name, Qt::UserRole+2);
item->setData(data, Qt::UserRole+3);
... and so on
When the user double clicks on an item, a dialog with two line edits displays allowing the user to edit parts of the UserRole data. When editing ceases, the edits run through some logic and a display name is generated based on the new UserRole data.
However, this gets very tedious very quickly. With dialogs constantly popping up and whatnot, it's a slow and ugly solution.
I now would like to remove the dialog completely and show the line edit widgets WITHIN the item itself. By default, double clicking an item to edit it only shows one line edit widget to change the DISPLAY role. However I want two line edits to change the two USER roles. And then the normal logic continues.
How would I go about modifying the edit item portion of a QTreeView?
Thanks for your time!
I would use a custom subclass of QStyledItemDelegate to solve this. Somewhere near your QTreeView you could have a QComboBox switching between the user roles; your custom delegate would somehow be informed which user role is currently selected and would intercept the method updating the data in the model to set the proper role.
An example implementation (not tested, may contain typos and errors):
class RoleSwitchingDelegate: public QStyledItemDelegate
{
public:
explicit RoleSwitchingDelegate(QComboBox * roleSwitcher, QObject * parent = 0);
virtual void setEditorData(QWidget * editor, const QModelIndex & index) const Q_DECL_OVERRIDE;
virtual void setModelData(QWidget * editor, QAbstractItemModel * model,
const QModelIndex & index) const Q_DECL_OVERRIDE;
private:
QComboBox * m_roleSwitcher;
};
RoleSwitchingDelegate::RoleSwitchingDelegate(QComboBox * roleSwitcher, QObject * parent) :
QItemDelegate(parent),
m_roleSwitcher(roleSwitcher)
{}
void RoleSwitchingDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
{
// Assuming the model stores strings for both roles so that the editor is QLineEdit
QLineEdit * lineEdit = qobject_cast<QLineEdit*>(editor);
if (!lineEdit) {
// Whoops, looks like the assumption is wrong, fallback to the default implementation
QStyledItemDelegate::setEditorData(editor, index);
return;
}
int role = m_roleSwitcher->currentIndex();
QString data = index.model()->data(index, role).toString();
lineEdit->setText(data);
}
void RoleSwitchingDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const
{
// Again, assuming the model stores strings for both roles so that the editor is QLineEdit
QLineEdit * lineEdit = qobject_cast<QLineEdit*>(editor);
if (!lineEdit) {
// Whoops, looks like the assumption is wrong, fallback to the default implementation
QStyledItemDelegate::setModelData(editor, model, index);
return;
}
int role = m_roleSwitcher->currentIndex();
QString data = lineEdit->text();
model->setData(index, data, role);
}
Once you have the delegate, you just need to set it to the view:
view->setItemDelegate(new RoleSwitchingDelegate(roleSwitchingComboBox, view));

QT - QTreeView with different colors for subgroups of the QTreeView items

Does anybody know how to implement/realize a QTreeView with different colors for subgroups of the QTreeView items?
Something like:
Does anybody has done something like that and could give me a link to an tutorial or how to, or sample code would also be good. Currently I have absolutely no idea how to build this.
I'm working with Qt 5.1.1 and using the QTreeView with the QFileSystemModel and the QItemSelectionModel.
I also thought of :
m_TreeView->setStyleSheet(...)
but this only sets the style for the whole treeView or only for the selected ones.
Any suggestions? Thanks a lot for your help!
There's Qt::BackgroundRole which can be used to return a QColor to be used by the view to paint an index' background.
As you use an existing item model class (QFileSystemModel), it would be easiest to put a proxy model on top of the file system model just doing the colorization.
Using QIdentityProxyModel:
class ColorizeProxyModel : public QIdentityProxyModel {
Q_OBJECT
public:
explicit ColorizeProxyModel(QObject *parent = 0) : QIdentityProxyModel(parent) {}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (role != Qt::BackgroundRole)
return QIdentityProxyModel::data(index, role);
... find out color for index
return color;
}
};
To use it:
QFileSystemModel *fsModel = new QFileSystemModel(this);
ColorizeProxyModel *colorProxy = new ColorizeProxyModel(this);
colorProxy->setSourceModel(fsModel);
treeView->setModel(colorProxy);
If you need anything more fancy, (like special shapes etc.), you'd need your own item delegate with custom painting (see QStyledItemDelegate).

How to set a delegate for a single cell in Qt item view?

Rather perplexed by this omission--but in Qt's QAbstractItemView class, it's possible to set a QAbstractItemDelegate (i.e., QItemDelegate or QStyledItemDelegate) to the entire view, a single row, or a single column, using the setItemDelegate* methods. In addition the item delegate for an individual cell can be queried, with QAbstractItemView::itemDelegate(const QModelIndex&), along with the delegate for rows, columns. and the entire view. But there appears to be no way to set an item delegate to an individual cell. Am I missing something? Any reason this should be?
No you can't set item delegate only for one cell or one column but you can easly set item delegate for whole widget and choose in which cell, column or row you want to use your custom painting or something.
For e.g.
void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == 1)
{
// ohh it's my column
// better do something creative
}
else // it's just a common column. Live it in default way
QItemDelegate::paint(painter, option, index);
}
You can find some more information here
I'd recommend reimplementing createEditor function instead:
QWidget * WidgetDelegate::createEditor(
QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex &index) const
{
QWidget *widget = 0;
if (index.isValid() && index.column() < factories.size())
{
widget = factories[index.column()]->createEditor(index.data(Qt::EditRole).userType(), parent);
if (widget)
widget->setFocusPolicy(Qt::WheelFocus);
}
return widget;
}
Sinc Qt 4.2, QAbstractItemView provides setItemDelegateForColumn() and setItemDelegateForRow().

Automatically refreshing a QTableView when data changed

I have written a custom data model to be displayed with several QTableViews.
Technically, everything works fine: my views show the changes made from my model. My data model is editable, and the setData() method does emit the dataChanged() signal and returns true on successful edition.
However, my problem is that I have to move my mouse over a QTableView for it to show the actual change, while I would like all views to show the changes when they were made, without needing to interact with the views in order for them to be updated.
Any idea? Thanks,
It may be relevant to mention that I do not use the default Qt::EditRole role to edit data, but rather a custom enum value (named ActiveRole).
Here is what I am seeking: my data model contains properties on how to display data, used to generate style sheets that are fed to the viewS.
Thus, when altering the model, for each view, all its items are impacted, which is why the dataChanged() signal is sent with indices covering all cells.
I also tried to emit layoutChanged(), but it does not seem to change the behavior in my case.
Here is an excerpt of the setData() method:
bool DataModel::setData(QModelIndex const& idx, QVariant const& value, int role)
{
if (ActiveRole == role)
{
// Update data...
QModelIndex topLeft = index(0, 0);
QModelIndex bottomRight = index(rowCount() - 1, columnCount() - 1);
emit dataChanged(topLeft, bottomRight);
emit layoutChanged();
return true;
}
return false;
}
Here is a sample of the data() method:
QVariant DataModel::data(QModelIndex const& idx, int role) const
{
if (ActiveRole == role)
{
boost::uuids::uuid id;
return qVariantFromValue(id);
}
return QVariant();
}
And flags() does indicate an editable model:
Qt::ItemFlags DataModel::flags(QModelIndex const& idx) const
{
if (false == idx.isValid())
{
return Qt::ItemIsEditable;
}
return QAbstractTableModel::flags(idx) | Qt::ItemIsEditable;
}
I have a custom delegate, which relies heavily on this SO thread for overriding the paint and the sizeHint methods in order to draw a QTextDocument. Also, it provides the content of the ActiveRole to the editor in setEditorData, and calls DataMode::setData in setModelData:
void DataModelDelegate::setEditorData(QWidget* editor, QModelIndex const& idx) const
{
auto active = qVariantValue<boost::uuids::uuid>(idx.data(ActiveRole));
static_cast<DataModelEditor*>(editor)->setActive(active);
}
void DataModelDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, QModelIndex const& idx) const
{
auto active = static_cast<DataModelEditor*>(editor)->getActive();
model->setData(idx, qVariantFromValue(active), ActiveRole);
}
In the createEditor(), I plug a signal from the editor to a slot of my delegate for committing data:
QWidget* DataModelDelegate::createEditor(QWidget* parent, QStyleOptionViewItem const& option, QModelIndex const& idx) const
{
auto editor = new DataModelEditor(parent);
connect(editor, SIGNAL(activeItem()), this, SLOT(commitEditorData()));
return editor;
}
When clicking on an item, the editor triggers the activeItem signal; the connected slot commitEditorData in turn raises the commitData signal with the editor in argument.
So all my views use these custom delegate, editor, and data model. The view that I am interacting with does show the change immediately, but the other views need to have the mouse hovering over them to show changes as well.
I actually found the problem, which was that my other view was not properly notified of the data changes: my views each showed different portions of my data, so the other views needed to be notified of the dataChanged(), but for their own, proper, indices.
On a side note, I also had the problem of updating my views while my Qt application was not the active window in my window manager. The solution was to call repaint() on the main window.
I have met the same problem, and let me add a detailed explanation to the piwi's answers. If you change the data,and what to update the single or several columns(or rows,depending on your requirement), you should emit a set of index for topleft to bottomright.For example,if you have a table like below:
and, now you have changed some data, and want to update the cell row 1, column 1-2, then you should emit signal dataChange
emit datachange(index(1,1),index(1,2));
Are you calling the setData()? Is the dataChanged() signal really emitted? Connect some debug logging slot to it. I dare to speculate that this is very similar problem to yours:
http://www.qtcentre.org/threads/18388-Refreshing-a-QTableView-when-QAbstractTableModel-changes?s=fd88b7c4e59f4487a5457db551f3df2c