Save state of QTableWidget cell widget checkbox before setting its state - c++

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.

Related

How to catch key presses in editable QTableWidgetItem?

Now I can process all key presses in my QTableWidget in a function eventFilter() (after calling of myTable->viewport()->installEventFilter(this); in the constructor).
The only place where this doesn't work is editable cell while editing (because it grabs all key presses). To fix it I can't call installEventFilter() for each item in the table, because these items are not QObjects (and also I can't use connect for putting of my processing of key presses).
The only solution I have is to put QLineEdits in these cells and to use event filter to catch key presses while editing. But is it possible to solve it using only standard items? (i.e. only QTableWidgetItem with a flag Qt::ItemIsEditable)
Also I can call grabKeyboard() for my QTableWidget. In this case I'll have all key presses (even while editing of cells by user), but it blocks edit box (i.e. user can't input anything). May be it is possible to fix broken edit boxes after calling of grabKeyboard() for the table?
This so quite ease to achieve. Just subclass QStyledItemDelegate override createEditor method like this:
QWidget *AlterEditorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QWidget *result = QStyledItemDelegate::createEditor(parent, option, index);
result->installEventFilter(new YourEventFilter(result));
return result;
}
Than replace delegate for your QTableWidget.
Or even better instead subclassing create proxy class which accepts original QAbstractItemDelegate (more writing but much more universal and can be composed with other modifications).
AlterEditorProxyDelegate::AlterEditorProxyDelegate(QAbstractItemDelegate *original, QObject *parent)
: QAbstractItemDelegate(parent)
, original(original)
{}
QWidget *AlterEditorProxyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
QWidget *result = original->createEditor(parent, option, index);
result->installEventFilter(new YourEventFilter(result));
return result;
}
// other methods which invokes respective methods for `original` style.
Since QTableWidgetItem has no function keyEvent() that you can overload this is not possible.
What you have to do is set a delegate with custom editor factory that produces widgets where keyEvent is overloaded.
But is it possible to solve it using only standard items? (i.e. only QTableWidgetItem with a flag Qt::ItemIsEditable)
Not really. In Qt4 QTableWidget leaks KeyRelease events from the cell editor, but exploiting that would be an ugly hack.
May be it is possible to fix broken edit boxes after calling of grabKeyboard() for the table?
I once tried doing that and then posting the events to QTableWidget but ran into trouble as well.
The proper thing to do is to create your own delegate and install event filter in createEditor function. You can do something like this:
class FilterDelegate : public QStyledItemDelegate
{
public:
FilterDelegate(QObject *filter, QObject *parent = 0) :
QStyledItemDelegate(parent), filter(filter)
{ }
virtual QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
editor->installEventFilter(filter);
return editor;
}
private:
QObject *filter;
};
Then your MainWindow constructor would look something like this:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setupUi(this);
tableWidget->setItemDelegate(new FilterDelegate(this));
tableWidget->installEventFilter(this);
}
And your event filter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
// do something
}
return QMainWindow::eventFilter(obj, event);
}
ANOTHER ALTERNATIVE:
You can install event filter on the QApplication object and capture all events. This is a bit of an overkill if you ask me, but it would work for a small application and requires minimal code.
All you have to do is:
qApp->installEventFilter(this);
And:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
// do something
}
return QMainWindow::eventFilter(obj, event);
}

How set display format and similar properties of QDateTimeEdit in a QTreeView?

How can I set the - say - displayFormat and the calendarPopup property of QDateTimeEdit objects that are used in a QTreeView?
(They are used when editing a QVariant(QDateTime) value there.)
Is it possible to use Qt's property system for that purpose?
Unfortunately, the Style Sheets Reference does not list those properties for QDateTimeEdit. On the other hand, the documentation mentions that:
From 4.3 and above, any designable Q_PROPERTY can be set using the qproperty- syntax.
Thus, I've tried something like this:
QApplication app(argc, argv);
// first try
// app.setStyleSheet(
// " QDateTimeEdit { displayFormat: \"yyyy-MM-dd hh:mm:ss\" ; }");
app.setStyleSheet(
" QDateTimeEdit { qproperty-displayFormat: \"yyyy-MM-dd hh:mm:ss\" ; }");
Both style-sheets are not picked up, though.
What is the correct stylesheet syntax for setting those properties?
Or is there another way to set default values for those properties in an application?
The QDateTime display format used in a TableView, TreeView or ListView can be modified by modifying the ItemDelegate.
In this method, we derive StyledItemDelegate to override createEditor and displayText methods and then apply the new delegate to the desired view.
DateFormatDelegate.hpp :
#include <qstyleditemdelegate.h>
class DateFormatDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit DateFormatDelegate(QObject* parent = Q_NULLPTR)
:QStyledItemDelegate(parent) {}
QString displayText(const QVariant& value, const QLocale& locale) const Q_DECL_OVERRIDE;
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
DateFormatDelegate.cpp :
QString DateFormatDelegate::displayText(const QVariant& value, const QLocale& locale) const
{
switch (value.type()) {
case QVariant::DateTime:
return locale.toString(value.toDateTime(), "yyyy/MM/dd hh:mm:ss");
default:
return QStyledItemDelegate::displayText(value, locale);
}
}
QWidget *DateFormatDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QWidget* widget = QStyledItemDelegate::createEditor(parent, option,index);
if( strcmp(widget->metaObject()->className(),"QDateTimeEdit") == 0)
dynamic_cast<QDateTimeEdit*>(widget)->setDisplayFormat("yyyy/MM/dd hh:mm:ss");
return widget;
}
You can then set the new delegate to your view :
ui->TableView_MyTable->setIteemDelegate(
new DateFormatDelegate(ui->TableView_MyTable)));
it can also be applied directly in the constructor if you have derived the view.
This method allows to modify any editor widget's style without worrying of later modification or your view organization.
There might be more elegant solutions though.

How do I keep my QAbstractTableModel in sync with my data store?

In my app, I have a class for keeping a list of items:
class Database : public QObject
{
Q_OBJECT
public:
Database(QObject *parent, const QString &name);
const Entry& item(int idx) const { Q_ASSERT(idx < itemCount()); return _items.at(idx); }
const QString& name() const { return _name; }
int itemCount() const { return _items.size(); }
bool addItem(const Entry &item);
bool addItems(const Database *source, const QList<int> &idxs);
bool updateItem(int idx, const Entry &updated);
void removeItem(int idx);
void removeItems(const QList<int> &idxs);
private:
QList<Entry> _items;
signals:
void itemsRemoved(int start, int count);
void itemsAdded(int count);
void itemChanged(int index);
void countUpdate();
};
The item manipulation functions (add, update, remove) emit the corresponding signals when done (items added, changed, removed). I have a list of such Database's and a QTableView for displaying their contents. I also have one object of a custom, QAbstractTableModel-derived model class, which can be made to point to (and display) a different Database when needed:
class DatabaseModel : public QAbstractTableModel
{
Q_OBJECT
public:
DatabaseModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const { return _data->itemCount(); }
int columnCount(const QModelIndex &parent = QModelIndex()) const { return 5; };
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;
void setDatabase(const Database *data);
{
beginResetModel();
_data = data;
endResetModel();
}
protected:
const Database *_data;
};
I have a problem with making the model reflect the changes to its current Database. Before, I got it to work by issuing a model reset every time something changed in the Database (triggered by a signal from the Database to the DatabaseModel), but decided that was overkill. Now I have no idea how to connect Database and the model correctly.
Connecting the Database signals to the model and make the model emit dataChanged() doesn't work because the number of items in Database (and hence the model's rows) is changing. There are signals called rowsInserted() and rowsRemoved() in QAbstractTableModel, but the docs say they can't be used in custom classes. There are virtual functions to be reimplemented called removeRows() and insertRows(), but the docs say I must call begin(Remove|Insert)Rows() and end(Remove|Insert)Rows() inside them, which leads to two problems:
begin...Rows() needs a QModelIndex 'parent' parameter, for which I've no idea what to use
EDIT: Actually it doesnt matter, now I'm passing QModelIndex() for this. This is used by QAbstractTreeModel to identify a parent node in a tree and is apparently not necessary for table models.
the docs say these functions need to be called before changing the underlying data store
How do I make the model keep in sync with the database? Thanks!
I think I see your problem.
One the one side you're doing the right thing and trying to keep the data seperate from the model, on the other hand your data isn't aware of the model itself.
There is a reason why you should call begin...Rows() before changing the data and end...Rows() afterwards. Namely the QPersistentModelIndex. Usually you're not supposed to hort QModelIndex objects, but the persistent index is ment to be saved and kept. The model must guarantee its validity. Looking at the code of those begin...Rows() methods it is mainly about those persistent indices.
You have several choices.
a) If you're positivly sure you won't be using persistent indices you could just implement a private slot in your model which listens to a sort of update signal from your data source. This slot would simply call begin...Rows() and end...Rows() with nothing in between. It's not "clean" but it'll work.
b) You could implement more signals in your data source, one that signals the beginning of a data change (removal or adding of a row for example) and one that signals the end of said operation. Of course that would significantly increasy the size of your code.
c) You could make your DataBase class a friend in the model and call the begin... end... methods from within your datasource, but then DataBase would have to be aware of the model.
d) You could rethink the concept. From what I can grasp you're using the DataBase class as a data storage for your model and as an interface for other parts of your code, right?
Wouldn't it be easier to use custom items with methods that operate on the model itself and thus avoid the trouble? I've done my fair share of those so I could give you the code if need be.
Hopefully that helps.
Best regards

How do I detect row selections in QListView <- > QAbstractListModel with Item Delegate?

It seems there is zero built-in selection support with my choice of QListView -> QAbstractListModel. Do I have to write everything from scratch? the catching of a selection event in the UI, the marking of the model item as selected, etc? It seems there is no out-of-the-box support for this.
the weird thing is that there is a QItemSelectionModel that does support this, but you cannot use it with QListView as it’s not derived from QAbstract….
Should my model class use multiple inheritance to inherit both from QItemSelectionModel and QAbstractListModel? Otherwise I don’t see how I can avoid having to re-writing this functionality myself.
My final goal is for the delegate that draws my items to know if the item is selected, both in the paint and the sizeHint function.
QListView is derived from QAbstractItemView, which has a method to get the selection model:
QItemSelectionModel *selectionModel = myView->selectionModel();
This method returns a pointer to the selection model, which is long-lived, i.e., you can save the pointer, connect to its signals, etc.
The answer given by Daniel is correct, but it is better to show it with an example suitable for beginners:
class MyCustomModel : public QAbstractListModel
{
Q_OBJECT
public:
ImageCollectionModel(QObject *parent, MyCustomCollection *data);
: QObject(parent)
, m_myData(data)
{
}
public slots:
void onSelectedItemsChanged(QItemSelection selected, QItemSelection deselected)
{
// Here is where your model receives the notification on what items are currently
// selected and deselected
if (!selected.empty())
{
int index = selected.first().indexes().first().row();
emit mySelectedItemChanged(m_myData->at(index));
}
}
signals:
void mySelectedItemChanged(MyCustomItem item);
private:
MyCustomCollection *m_myData;
// QAbstractItemModel interface
public:
int rowCount(const QModelIndex &) const override;
QVariant data(const QModelIndex &index, int role) const override;
};
When you pass your custom model to the QListView, that's a great opportunity to connect it:
ui->myListView->setModel(m_myModel);
connect(ui->myListView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
m_myModel, SLOT(onSelectedItemsChanged(QItemSelection, QItemSelection)));

QTableView has unwanted checkboxes in every cell

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()?