I created custom proxy model inherited by QSortFilterProxyModel. My source model for the above mentioned proxy model is also a custom model inherited by QAbstractTableModel. Then I set my custom proxy model to a QTableView.
In this custom proxy model, I reimplemented the mimeData(..) function. It is as follows.
QMimeData* CustomProxyModel::mimeData( const QModelIndexList & rListIndexes ) const
{
QMimeData *pMimeData = new QMimeData();
//some code here
connect(pMimeData, SIGNAL( destroyed(QObject*) ), this, SLOT( OnDestroyDraggedItem() ) );
return pMimeData;
}
In Qt4.7, soon after the user dropped an item of the QTableView into somewhere, OnDestroyDraggedItem() slot was called. In other words, QMimeData object is deleted soon after the drag and drop operation.
But in Qt 5.1, OnDestroyDraggedItem() slot is never called. In other words, QMimeData object is never deleted after a drag and drop operation.
Am I doing something wrong?
Or Has Qt 5.1 a memory leak after a drag and drop operation ?
Is there another way to find a end of a drag and drop operation ?
Maybe it's a bit late - but can't you just inherit QMimeData and do something in the destructor?
Should be small and safe code of course - throwing exceptions in destructors can cause strange behavior :)
Related
I have two separate threads.
First thread for GUI, and second for application data.
Initially, I wanted to use QUndoStack and QUndoView.
But there was a problem - this view works directly with the stack:
https://code.woboq.org/qt5/qtbase/src/widgets/util/qundoview.cpp.html#_ZN10QUndoModel20setStackCurrentIndexERK11QModelIndex
In this case I got race condition.
To solve this problem I wrote custom myUndoView using QListView and QAbstractListModel.
Now all my slots using queued connections and I store a lightweight copy of the "real" undo stack in the custom view model.
This is same size and same order of the "real" undo stack elements.
A lightweight element contains only type of the undo command and text.
Now I have another problem. I'm not blame for this ))
I have a QLineEdit that emits signal on value changed when I click Enter key or lost focus.
This value in turn is sent to object (app model) with "real" undo stack. It works.
But this does not work when I interact with undo view too.
Repeat, I'm not blame for this. QUndoView has the same behavior.
Step by step:
QLineEdit in focus.
Changing value, still in focus.
Click the mouse in the undo view.
Oops.. currentIndexChanged() signal from undo view can be sent first,
or signal from QLineEdit can be sent first.
It always differs ..
If signal from QLineEdit was sent first - it works correctly.
The history of changes not lost.
I want to make enter/blur and other changes (not in history view) always invoked first. Probably I can use QTimer::singleShot() for delay of emit undo view signals . But not curentIndexChanged() because this signal emit with user interactions and when undo stack updated programmatically. We can not determine who make changes - user or application.
What I tried?
Intercept mouse clicks:
myUndoView::mousePressEvent(QMouseEvent *event)
{
event->ignore();
qDebug() << "catched!";
}
But sometimes it loses the clicks.
At the bottom of the list item (under the letters) is an area that pass a click to the item.
This may be a Qt bug, found in my environment: Debian, Mate, GTK+ Qt-style.
I think, I can place another transparent widget over list, and get coordinates of the click and use it:
http://doc.qt.io/qt-5/qabstractitemview.html#indexAt
to get the selected index.
Or I make all wrong?
Maybe there is an easier way?
How to make it right?
I would try blocking the list model signals while the line edit is focused.
Let's have an event filter like this:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(QObject * model) : _model(model){}
bool eventFilter(QObject *watched, QEvent *event);
private:
QObject * _model;
};
which keeps a private reference to the list model as a pointer to QObject, passed in constructor argument.
The filter implementation:
bool EventFilter::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::FocusIn)
{
_model->blockSignals(true);
}
return false;
}
Keep a reference to an instance of the filter in the window class (Form, in my example), along with the list model instance reference:
private:
EventFilter * filter;
QAbstractListModel * model;
The filter has to be instantiated and installed in line edit, in Form constructor (don't forget to delete it in the destructor):
filter = new EventFilter(model); //the model is passed to the filter in construction
ui->lineEdit->installEventFilter(filter);
At this point, model events will be blocked when the line edit gets focus. To unlock them, use the line edit editingFinished slot:
void Form::on_lineEdit_editingFinished()
{
model->blockSignals(false);
}
I have subclassed QAbstractItemModel and trying to retrieve a widget in slot of dataChanged signal.
connect(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(slotDataChanged(const QModelIndex&, const QModelIndex&)));
void MyEditor::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
QComboBox* widget = dynamic_cast<QComboBox*>(sender());
if (widget)
{
// do something
}
}
Here I am getting a null widget everytime, same result with qobject_cast.
I am setting combobox widget in my tableview a delegate class which derives QStyledItemDelegate.
MyDelegate* myDelegate;
myDelegate = new MyDelegate();
tableView->setItemDelegate(myDelegate);
tableView->setModel(model);
QWidget* MyDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QComboBox* cb = new QComboBox(parent);
cb->addItem(QString("All"));
cb->setCurrentIndex(0);
return cb;
}
How can I obtain sender object in this case? Thanks.
Not sure of what are your intentions. Acquiring the editor widget when the data is already updated in the model is unnecessary in general.
Seems to me that a brief introduction to Model-View-Delegate concept is needed to solve your problem.
In short, the view, which in your case is the QTableView, has no data by itself. View acquires data from the attached model by calling data method. When user tries to edit some data, delegate createEditor and setEditorData methods are called. The latter has model pointer as one of the arguments so it can access actual data which needs to be represented.
When user finishes editing setModelData is called which has the editor widget available to acquire the updated value. It also has the model available to change the proper data entry normally done via setData method. At this point the dataChanged signal is emitted which notifies the view that corresponding data was updated so it can refresh the displayed value(s).
Hence, try rethinking your design. Maybe what you want to achieve can be implemented differently or your implementation can be slightly modified to conform the described flow.
You can also check the Qt site for Star Delegate Example to see some sample implementations or Model View Tutorial for a broader description of the Model-View topic.
My Model/View design was fine. I just needed to obtain a widget when user double clicks on a cell in my editor.
QComboBox* widget = dynamic_cast<QComboBox*>(tableView->indexWidget(topLeft));
if (widget)
{
// Do something
}
Here in slotDataChanged I obtained the required widget using QModelIndex.
Thanks for helping me out.
The source code relating to this question is available on my public Git repository on BitBucket.
I'm trying to dynamically add some items to a QTreeView model using the following code in mainwindow.cpp:
if(dlg->exec() == QDialog::Accepted) {
QList<QVariant> qList;
qList << item.name << "1111 0000" << "0x00";
HidDescriptorTreeItem *item1 = new HidDescriptorTreeItem(qList, hidDescriptorTreeModel->root());
hidDescriptorTreeModel->root()->appendChild(item1);
}
This works when run from within my MainWindow constructor, just after ui->setupUi(this), but I need this to run from within an event filter, but the same code doesn't get the QTreeView updating. When I set a breakpoint at mainwindow.cpp:70 and step through the next few lines, I can see the data is being added to the Model, but I need the QTreeView to refresh.
I understand this is done by emitting dataChanged(), but not really sure how to do this. The signal signature for the dataChanged signal looks as follows:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>());
so I need to come up with topLeft and bottomRight QModelIndex instances. How do I build/obtain these from item1 in the above snippet?
Also, where does beginInsertRows() and endInsertRows() come into view with this, should I be calling these functions?
From QAbstractItemModel documentation:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent, int first, int last ) [protected]
Begins a row insertion operation.
When reimplementing insertRows() in a subclass, you must call this function before inserting data into the model's underlying data store.
The parent index corresponds to the parent into which the new rows are inserted; first and last are the row numbers that the new rows will have after they have been inserted.
The other protected functions say similar things.
And insertRows() says:
If you implement your own model, you can reimplement this function if
you want to support insertions. Alternatively, you can provide your
own API for altering the data. In either case, you will need to call
beginInsertRows() and endInsertRows() to notify other components that
the model has changed.
Take a look to QAbstractItemModel protected functions and signals
Views connect to those signals to know when model data changes and rearrange data inside. The functions emit the signals internally to make it easy for you to warn the view when it has happenned. But signals can only be emitted by abstract class.
Components connected to this signal use it to adapt to changes in the
model's dimensions. It can only be emitted by the QAbstractItemModel
implementation, and cannot be explicitly emitted in subclass code.
So you will have to stick to the methods.
Edit in answer to your comment:
Indeed, Items should have a reference to model and tell it about changes, check theses lines from QStandardItem:
void QStandardItem::emitDataChanged()
void QStandardItem::removeRows(int row, int count)
( Note, how, in second, it calls model's rowsAboutToBeRemoved() and rowsRemoved() )
Maybe you should try to use QStandardItem and QStandardItemModel.
Either direct or subclassing. It will hide a lot of ugly stuff.
There's also a less proper but much easier way to achieve this - emit layoutChanged() instead of dataChanged(). More info - https://stackoverflow.com/a/41536459/635693
I have QTableView using a QSqlQueryModel (it fetches data from SQLite).
There is a QStyledItemDelegate subclass called MiniItemDelegate that I use as a delegate for the items. I set up a sizeHint() method like this:
QSize MiniItemDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
// just for testing...breakpoint shows this line never gets called
return QSize(256,256);
}
I'm not sure why this method isn't called when I run the following code:
m_pMiniItemDelegate = new MiniItemDelegate(this);
ui->PList_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->PList_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->PList_tableView->setItemDelegate(m_pMiniItemDelegate);
ui->PList_tableView->setAlternatingRowColors(true);
ui->PList_tableView->setModel(ListMiniSqlModel::instance());
This also doesn't work:
ui->PList_tableView->resizeColumnsToContents();
ui->PList_tableView->resizeRowsToContents();
Nor does this:
QHeaderView* headerView = ui->PList_tableView->horizontalHeader();
headerView->setResizeMode(QHeaderView::ResizeToContents);
QStyledItemDelegate::sizeHint is useful only when QTableView::resizeRowsToContents, QTableView::resizeRowToContents, QTableView::resizeColumnsToContents and QTableView::resizeColumnToContents are called. or use
QHeaderView* headerView = tableView->horizontalHeader();
headerView->setResizeMode(QHeaderView::ResizeToContents);
Have you tried: setColumnWidth or setRowHeight and horizontalHeader()->setResizeMode(QHeaderView::Fixed) ?
(Credit where credit is due.)
In #HostileFork's comment about a Qt Forum discussion, there's a comment thread. Within that thread, a user mikhailt offers a good solution.
The verticalHeader has a DefaultSectionSize property that can be adjusted. It doesn't matter whether the vertical header (labels on the left side of the table) is actually being displayed or not, the size will still be used.
ui->PList_tableView->verticalHeader()->setDefaultSectionSize(34);
This just solved my problem with Qt 5.6, and saved me from adjusting each data row's height separately, or causing a resize on a table.
Based on the age of the comment thread where I found it, this was already working in Qt 4, too.
Is it possible to add QPushButtons for every item in a QTreeView? For instance, when you click on a TreeItem (that is a button), it's children get displayed as buttons as well? I just have a standard QTreeView.
_layout = new QVBoxLayout(this);
treeView = new QTreeView(this);
QStandardItemModel* standardModel = new QStandardItemModel();
QStandardItem* rootMenu = standardModel->invisibleRootItem();
//populate TreeView
treeView->setModel(standardModel);
treeView->setWordWrap(true);
treeView->setHeaderHidden(true);
//treeView->expandAll();
_layout->addWidget(treeView);
this->setLayout(_layout);
I have not personally done this (yet), but you could try using QAbstractItemView::setIndexWidget(). The widgets won't aren't connected in any way to the data model, so it is up to your code to update them if necessary. Also, you need to call it for each QModelIndex separately.
Here is the answer. You must create your own delegate and applay it for your QTreeView.
To create delegate you must subclass QStyledItemDelegate and re-implement its QStyledItemDelegate::paint(...) method in that way what you want, also, don't forget about re-implementing QStyledItemDelegate::sizeHint(...) method if needed, of course.
Also, you may need to re-implement QStyledItemDelegate::createEditor(...) method.
To apply created delegate to your view (QTreeView) you must create delegate and call QTreeView's method setItemDelegate (or setItemDelegateForColumn, or setItemDelegateForRow).
Good luck!