Qt attaching itemChanged signal to QStandardItem doesn't work - c++

I'm using qtreeview trying to find out when ever the check box state changes,
but the SLOT method never fires.
Here is my code:
// in the init
connect(ui.treeView_mainwindow, SIGNAL(itemChanged( const QModelIndex &)), this,
SLOT(tree_itemChanged( const QModelIndex &)));
// this method never trigered
void GroupMainWindowContainer::tree_itemChanged(const QModelIndex & index)
{
QStandardItem* standardItem = m_model->itemFromIndex(index);
Qt::CheckState checkState = standardItem->checkState();
if(checkState == Qt::Checked)
{
WRITELOG("Qt::Checked")
}
else if(checkState == Qt::Unchecked)
{
WRITELOG("Qt::Unchecked")
}
}
// this is how i build the items :
QList<QStandardItem *> items;
items.insert(0,new QStandardItem());
items.at(0)->setCheckable(true);
items.at(0)->setCheckState(Qt::Unchecked);
m_model->insertRow(0,items);

QTreeView doesn't have an itemChanged signal, so your QObject::connect call will fail.
This is a good example of why you should always check the return value from QObject::connect. Also, the failed connection would have appeared in your debug output, which you should also be monitoring.
Possibly you're looking for QTreeWidget, which inherits from QTreeView and does have an itemChanged signal, albeit one that has an QTreeWidgetItem* as a parameter, not a const QModelIndex&.

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.

Disable QAction if the QTreeView item has no children

I have QTreeView populated from database.
I have contextmenu configured as:
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
I have the method to look for a right click to open contextMenu on the item.
void MainWindow::on_treeView_customContextMenuRequested(const QPoint &pos)
{
QModelIndex idx = ui->treeView->indexAt(pos);
if (!idx.isValid())
{return;}
else{
QPoint globalPos = ui->treeView->mapToGlobal(pos);
QAction* selectedItem = contextMenu->exec(globalPos);
if (selectedItem){
qDebug () << selectedItem;
}
}
h.file
QMenu *contextMenu;
How do I check if the selected item from QTreeView is not a parent of any item & it has a parent.
Should I include QTreeView and QStandardItem code here to see or that's irrelevant?
The Qt doc. has a dedicated chapter for this topic:
Model/View Programming
which I recommend to get an overview.
Concerning the actual question of OP:
How do I check if the selected item from QTreeView is not a parent of any item & it has a parent.
The QTreeView inherits QAbstractItemView::model() which provides a pointer to the QAbstractItemModel which in turn provides the underlying model data for the rendered tree view items.
Any provided QModelIndex in a view should refer to this model.
The QAbstractItemModel provides a variety of methods to retrieve data concerning visualization and relations of model items. The QTreeView uses this but it should be used as well for any added function.
So, selected item is not parent of any item is turned around into "selected item has no children" for which QAbstractItemModel::hasChildren() is good for:
bool QAbstractItemModel::hasChildren(const QModelIndex &parent = QModelIndex()) const
Returns true if parent has any children; otherwise returns false.
Use rowCount() on the parent to find out the number of children.
Note that it is undefined behavior to report that a particular index hasChildren with this method if the same index has the flag Qt::ItemNeverHasChildren set.
Note: This function can be invoked via the meta-object system and from QML. See Q_INVOKABLE.
See also parent() and index().
and it has a parent can be retrieved using QAbstractItemModel::parent():
QModelIndex QAbstractItemModel::parent(const QModelIndex &index) const
Returns the parent of the model item with the given index. If the item has no parent, an invalid QModelIndex is returned.
A common convention used in models that expose tree data structures is that only items in the first column have children. For that case, when reimplementing this function in a subclass the column of the returned QModelIndex would be 0.
When reimplementing this function in a subclass, be careful to avoid calling QModelIndex member functions, such as QModelIndex::parent(), since indexes belonging to your model will simply call your implementation, leading to infinite recursion.
Note: This function can be invoked via the meta-object system and from QML. See Q_INVOKABLE.
See also createIndex().
Putting this together, OPs function should look like this:
void MainWindow::on_treeView_customContextMenuRequested(const QPoint &pos)
{
QModelIndex idx = ui->treeView->indexAt(pos);
if (!idx.isValid()
|| !ui->treeView->model()->hasChildren(idx)
&& !ui->treeView->model()->parent(idx).isValid()) {
return;
// bail out -> no context menu for leaf nodes or toplevel nodes
} else {
QPoint globalPos = ui->treeView->mapToGlobal(pos);
QAction* selectedItem = contextMenu->exec(globalPos);
if (selectedItem) {
qDebug () << selectedItem;
}
}
}
I'm not quite sure whether this matches exactly OPs required behavior. It might be necessary to fix the condition but this should be not that hard.

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 to get selection changed notification in QTreeView

I am trying to figure this out and it seems like I have to use QItemSelectionModel but I can't find an example how to wire things up.
I have defined in .h file.
QItemSelectionModel* selectionModel;
Now in constructor of the view, I set:
selectionModel = ui->treeView->selectionModel();
// the following line is not compiling!
connect(ui->treeView->selectionModel(), SIGNAL( ui->treeView->selectionModel(const QModelIndex&, const QModelIndex &) ),
this, this->selectionChanged ( QItemSelection & sel, QItemSelection & desel) );
I thought there would be predefined slot but I can't find one so I added this one (the syntax of which I found here)
void MyDialog::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
qDebug() << "Item selection changed";
}
I also tried replacing QItemSelection with QModelIndex but still doesn't work.
What do I need to do in order to simply get notified when selection has changed and than obviously grab the newly selected item?
the QObject::connect method should be used as follow :
QObject::connect(sender, SIGNAL(signal_method), receiver, SLOT(slot_method));
so in your case it should be something like
connect(selectionModel, SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(mySelectionChanged(const QItemSelection&,const QItemSelection&)));

QComboBox Get The Varient When "currentIndexChanged(int)" Emitted

I am having difficulty finding documentation on this or an example.
Could someone concretely show me how to access the QVariant of the currently selected index in a QComboBox
QComboBox * combo = new QComboBox();
combo->addItem("Bla1", QVariant(1));
combo->addItem("Bla2", QVariant(2));
combo->addItem("Bla3", QVariant(3));
combo->addItem("Bla4", QVariant(4));
connect(combo, SIGNAL(currentIndexChanged(int)), this, slot(HANDLEITMAN(int))
And of course else where in the source
void TheCooler::HANDLEITMAN(int index)
{
//What do I do with index?
//sender()?
}
First, make combo a member of TheCooler, or otherwise put HANDLEITMAN in a class which has combo as a member. Unless it's available to TheCooler::HANDLEITMAN somehow you can't get the data, and this is the logical way to do it. Then it's just
void TheCooler::HANDLEITMAN(int index)
{
QVariant data = combo->itemData(index);
}
If you don't want to make combo a member of the class TheCooler, you can use the sender() function that returns a pointer to the QObject that sent the triggering signal (in this case, currentIndexChanged(int)).
void TheCooler::HANDLEITMAN(int index)
{
QComboBox * combo = qobject_cast< QComboBox * >(sender());
if (combo == 0)
return; // something wrong happened
QVariant data = combo->itemData(index);
}
If combo is null, then you probably tried to call the slot by yourself, or you have connected it with a signal emitted by a class that is not a QComboBox.