QTreeView: how to abort selection change - c++

I have a QTreeView in my widget. When an item is selected in the view, I have
a signal handler that updates a series of information widgets in a detail window
about the selected item. The user can then edit the item details and commit the
changes back to the model.
If the data in the details view has been edited when this selection change
happens, I present a confirmation dialog to the user before replacing the data
when a new item is selected. If the user cancels, I want to set the selection of
the tree back to what it was before.
I have my slot connected like so:
auto selection_model = treeview->selectionModel();
connect(
selection_model, &QItemSelectionModel::currentChanged,
this, &Editor::on_tree_selection_changed
)
Inside my slot, the code is structured as follows:
void on_tree_selection_changed(QModelIndex const& index, QModelIndex const& previous)
{
if(not confirm_editor_discard())
{
// user does not want to override current edits
log.trace("cancel item selection change");
using SF = QItemSelectionModel::SelectionFlags;
auto sm = treeview->selectionModel();
sm->setCurrentIndex(previous, SF::SelectCurrent | SF::Rows);
}
else
{
// user wants to discard, so update the details view.
log.trace("discard pending edits");
set_details_from_model(index);
}
}
However, the setting of the current index back to the previous does not seem to
affect the TreeView; it still displays the newly selected item as selected, and
the interface becomes non-coherent since the item displayed in the details is
not the one shown as selected in the tree.
The intended behaviour is to re-select the previously selected item, as if no
new selection was made at all.

Apparently the QTreeView ignores any updates from the selection model while the currentChanged slot is being called.
The solution here was to call the slot as a QueuedConnection, so the connect line would look like this:
connect(
selection_model, &QItemSelectionModel::currentChanged,
this, &Editor::on_tree_selection_changed,
Qt::QueuedConnection // <-- connection must be queued.
)
This will ensure that the change in selection of the selection model will not happen directly inside a slot call.

Related

Pyqt get qlistwidget item when widget inside itemwidget is clicked

this is the current application looks like this:
It has a Qlistwidget listWidget_links where each item has own itemwidget set (combobox, checkbox, button, ...) Now I came upon a problem that neither google or I can solve.
If the user presses the create button or any other item's itemwidget how do I let a method know the item the widget was pressed changed inside.
item_widget.comboBox_type.currentIndexChanged.connect(self.itemupdate_linktype)
item_widget.checkBox_hide.stateChanged.connect(self.itemupdate_hidden)
cbox = self.sender() # gives the widget that released the signal
cbox.parent() #I discovered by a lucky try, returns NodeLinkItemWidgetUI.Ui_Form object which is a item's itemwidget
Here is how items get created to in order to understand the program structure better:
def createNewLink(self, nodename, nodeclass):
item = QtWidgets.QListWidgetItem(self.listWidget_links)
item_widget = NodeLinkItemWidgetUI.Ui_Form(nodename, nodeclass)
item.nodename = nodename
item.nodeclass = nodeclass
item.setSizeHint(QtCore.QSize(130, 160))
self.listWidget_links.addItem(item)
self.listWidget_links.setItemWidget(item, item_widget)
Edit: solved setting the variable item_widget.item = item seems to work, but is there a more elegant way?

Remove editable item from QListWidget if empty

I'm using Qt 4.8.6.
I have a QListWidget. When the user click on an Add button, a new item is inserted at the end of the list and edition of the item's text is initiated:
void slot_add_item()
{
auto* item = new QListWidgetItem(QString());
item->setFlags(item->flags() | Qt::ItemIsEditable);
listWidget->addItem(item);
listWidget->setCurrentItem(item);
listWidget->editItem(item);
}
Based on this comment, I'm listening to the commitData signal to catch when the user has finished editing the item and to remove it if the item's text is empty:
connect(
listWidget->itemDelegate(), SIGNAL(commitData(QWidget*)),
SLOT(slot_item_edited(QWidget*)));
...
void slot_item_edited(QWidget* widget)
{
const QString path = reinterpret_cast<QLineEdit*>(widget)->text();
if (path.isEmpty())
delete listWidget->currentItem();
}
However that doesn't catch the case where the user cancels editing with the Escape key: in that case, slot_item_edited() is not called (expectedly) and the (empty) item is not removed.
Any idea on how to remove the item in that case?
You can connect to closeEditor signal of the delegate instead of commitData signal: closeEditor signal is emitted when the editor is closed regardless of the fact whether any new data was entered into the model or not.

QSortFilterProxyModel how to handle QStandardItems correctly

I have QTreeView with some items and search QLineEdit with connected slot on textEdited signal.
With this code:
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(messagesModel);
proxyModel->setFilterFixedString(text);
ui.treeView->setModel(proxyModel);
text filtering is ok, but when I clicked on QTreeView QStandardItems checkboxes (after proxy model assigned to QTreeView), I have the program crashes in slot, that connected to this QTreeView original model (before proxy was assigned).
What is the right way to processing item checkbox clicks? Need I use new connect/slot to processing model changes, or I can use the same code for original model with some changes? I just need to hide filtered items in QTreeView. In QTreeWidget is hide() method, does QTreeView has something like this, or QSortFilterProxyModel - is what I need? Thx!
UPD crashed in slot, connected to treeView:
auto item = messagesModel->itemFromIndex(index); // item is NULL because proxyModel is set for TreeView now
if(item->whatsThis().isEmpty()) return; // error below
#ifndef QT_NO_WHATSTHIS
inline QString whatsThis() const {
return qvariant_cast<QString>(data(Qt::WhatsThisRole));
}
inline void setWhatsThis(const QString &whatsThis);
#endif
because I set proxyModel to treeView, but messagesModel have whatsThis...
I changed my code with that:
QStandardItem* item;
if(ui.leFilter->text().isEmpty())
item = messagesModel->itemFromIndex(index);
else
item = messagesModel->itemFromIndex(proxyModel->mapToSource(index));
if(item->whatsThis().isEmpty()) return;
and it works. Is that correct way? Proxy model is member of my UI class ... not local.
UPD how can I update source model when checkbox checked in proxyModel?
UPD 2 I have load "original" model for QtreeView and show it. When I edit text in QListEdit, I use proxyModel (code from 1st post). When text edited, I have check checkboxes in QtreeView (now proxyModel is active) and at this step all is ok. But when I do some changes in UI, in QTreeView set the original model and it has no changes that was made for proxyModel. How can I notify and update items in source Model with new data from proxyModel?
UPD3 Yes, source model is also modified ... I have just clear it)

Exclusive checkbox in QListView

I'm trying to do exclusive checkboxes as a QListView items. I'm using QStandardItemModel as a model with QStandardItem's.
I'm adding items to the list dynamically and set it checkable:
QStandardItem *item = new QStandardItem(treeView->model()->data(index).toString());
item->setCheckable(true);
m_categoriesModel->appendRow(item);
I tried connect all items to QSignalMapper but QStandardItem doesn't have checked(bool) signal (basically it does not have any).
Is there any way to solve the problem?
You can always make it in the way described below. Firstly connect the clicked signal of ListView to the slot which will handle your items click. Secondly inside of the slot you can get the item from QModelIndex and check the state of the item. Below is pseudo code:
For example, in constructor of ListView:
connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(_handleItemClicked(QModelIndex)));
Slot of ListView:
void ListView::_handleItemClicked(QModelIndex index)
{
QStandardItem* item = _model->itemFromIndex(index);
if( item->checkState() == Qt::Checked) qDebug() << "Checked!";
}
There actually is a class for exactly doing this: QButtonGroup
It's easy to use:
QButtonGroup *group = new QButtonGroup(this);
group->setExclusive(true);//now only one will be checked at a time
//add all buttons
group->addButton(this->ui->myFirstCheckbox);
//...
... at least for manually added buttons. Of course you can use it for the model too, but it would require you to find all the checkbox elements inside the view...

QTreeWidget editItem fails with "edit: editing failed"

I have a QTreeWidgetItem added to a QTreeWidget:
QTreeWidgetItem* item = new QTreeWidgetItem(ui->trwPairs);
item->setFlags(item->flags() | Qt::ItemIsEditable);
If the item is edited, I want to do a few checks on the new value:
Pairs::Pairs(QWidget *parent) :
QWidget(parent),
ui(new Ui::Pairs)
{
ui->setupUi(this);
connect(this->ui->trwPairs, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(Validate(QTreeWidgetItem*,int)));
}
void Pairs::Validate(QTreeWidgetItem* item, int column)
{
if (item->text(column).toInt() < 1)
{
QMessageBox::critical(this, "Error", QString("Node ID ") + item->text(column) + " is invalid.");
ui->trwPairs->editItem(item, column);
}
}
Naturally, if it's less than 1, it catches it, and gives me the message box. However, printed to cerr is edit: editing failed and the item is not in edit mode. What am I missing?
Stepping through it in the debugger reveals the following:
In quabstractitemview.cpp line false is returned on line 3953. Somehow it looks like your item is still in editing state and you are trying to edit it again or something.
bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger,
const QModelIndex &index) const
{
// ..
if (state == QAbstractItemView::EditingState)
return false;
}
IIRC I had a similar problem with tables with multiple lines per cell. Check out the classes QAbstractItemDelegate views have item delegates which allow you to control which editor is used and how it behaves. I believe by default the QLineEdit is used. Editors like QLineEdit can have validators which control how the data is validated, in your case reject it if the numerical value is < 0. But I think you have to use the model / view classes and implement your own model for that. The Qt documentation for QTreeWidget::setItemWidget(..) says:
This function should only be used to display static content in the place of a tree widget item. If you want to display custom dynamic content or implement a custom editor widget, use QTreeView and subclass QItemDelegate instead.
I am not sure however if there is a simpler way to do this using the widget classes.
The problem could be, that you are setting the flags for your items in a very strange way.
Simply enable both item-selection, and edit:
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
I had a similar issue where I was attempting to edit the subsequent column upon receiving the itemChanged signal. Based on Nils' analysis that the item was still in the edit state, I changed the signal connection type to QueuedConnection, which allowed the item to leave the state before re-entering it.
I had a similar problem where I'd get the 'edit: editing failed' error when invoking edit() via a shortcut key. I was passing currentIndex() to edit(), but I wasn't checking that the correct column of the selected row was current. I only had the first column editable, so if I had clicked the row (but in any other column) and then invoked my edit key I'd get the error.
I was able to solve my problem by passing the result of sibling(currentIndex().row(), 0) to edit() instead.