I'm trying to implement QAbstractItemModel for QTreeView. I have problem with inserting rows.
I noticed that if I insert at the beginning of my application all works fine. But If I insert rows later - after some other operations (like selections etc.) new items stay invisible. Moreover QTreeView seems to doesn't work at all! Do I have to emit some signals to notify QTreeView about rows insertion?
This is my insertion method:
bool LayersModel::insertRows(int position, int count, const QModelIndex & parent)
{
LayersModelItem * parentItem = getItem(parent);
if (position > parentItem->childCount())
return false;
beginInsertRows(parent,position,position+count-1);
bool result = true;
for (;count;--count)
result &= parentItem->insertChildren(position, new LayersModelItem());
endInsertRows();
return result;
}
LayersModelItem is class with QList with its children and data.
Full code of my project (KDE libs needed) is here:
https://github.com/coder89/PhotoFramesEditor/tree/v0.0.8
To see the problem select one of blue item on main window and then right-click on it and select "Delete item". (this method is in Canvas::removeItems()) and it is completly commented - I'm desperate and I've tried to find reason of this problem... (in fact it wasn't delete anything - it adds new item).
Thanks for any help & advice!
Just a quick guess, the QT Doc for QAbstractItemModel says...
The model emits signals to indicate
changes. For example, dataChanged() is
emitted whenever items of data made
available by the model are changed.
Changes to the headers supplied by the
model cause headerDataChanged() to be
emitted. If the structure of the
underlying data changes, the model can
emit *layoutChanged() to indicate to
any attached views that they should
redisplay any items shown, taking the
new structure into account*.
So i guess, you need to emit layoutChanged() signal from your model (whenever you change the data in model) in order to update connected views.
Also read the QT docs for model view architecture, how it is implemented in QT
see if that helps, if it doesn't i will try to download your code and debug it and see, what's wrong.
Good Luck
Related
I have an application which contains two views of the same model: a QTreeView and QTableView. For some specific nested items say PeakItem, derived from QStandardItem, I would like them to be hidden in the QTreeView but showed in the QTableView. To do so, I did the following:
void PeakItem::setData(const QVariant& value, int role)
{
emitDataChanged();
}
and when my model is created I add the following signal/slot connection
connect(ui->treeView->model(),SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),ui->treeView,SLOT(addNewItem(QModelIndex)));
Debugging my code, the PeakItem::SetData is actually called but the signal does not seem to be fired or at least is not caught by the tree view as I would expect. Would you have any idea about what I did wrong ?
I think I'm having a problem similar to the one in Qt crash when redrawing Widget, and switching to Qt::QueuedConnection fixed the problem. However, in my case, both the signal emitter and received are always in the same thread (the main thread).
I have a QAbstractItemModel with entry rows, and a QSortFilterProxyModel for filtering. Since the model can be very large, I wanted to make a progress bar when filtering. Updating the filter basically does this in a slot that is connected to a QAction::toggled signal:
m_ProgressBar = new QProgressBar(); // Put into status bar etc.
auto connection = connect(m_filteredModel, SIGNAL(filterProgressChanged(int)), m_ProgressBar, SLOT(setValue(int)), Qt::QueuedConnection);
m_filteredModel->UpdateFilter();
delete m_ProgressBar;
disconnect(connection);
UpdateFilter basically does some housekeeping and then calls invalidate, making the filter model requery filterAcceptsRow for every row.
The filter model then emits the filterProgressChanged(int) signal within filterAcceptsRow (works by incrementing a counter and dividing by the source model's row count, and is only emitted when the actual int progress value changes).
UpdateFilter returns when the filtering is complete. The progress bar is not deleted until then (verified), so it should work in my opinion. Not deleting the progress bar leads to getting a new one every call, but the crash is still the same.
Everything is done in the main thread: Creating the progress bar, calling UpdateFilter, emitting the filterProgressChanged signal. However, when the connection is created as Qt::AutoConnection, i.e. direct, it crashes (only when disabling the filter, for some reason) within repainting the progress bar. The same happens when I call setValue directly in my own event handler, which is what I did prior to switching to the current code.
Now I have a solution that works, but I don't understand why the original code does not work. I thought that a DirectConnection would only make an actual difference when sender and receiver of the signal are in different threads, but they're not. You can easily see in the stack trace that everything happens within the same thread, and that is even true with the queued connection.
So, what's going wrong in the original code? Is there something I just missed? Is there any way to get more information out of the actual crash?
I only found out that in void QRasterPaintEngine::clip(const QRect &rect, Qt::ClipOperation op), state() returns 0, and the code assumes it never returns 0, which is the immediate crash reason, but likely not the reason. And the stack trace points to painting as the problem area, that's all I saw when debugging this.
I'm on Windows with Qt 5.4.2 (also tried 5.7), and with MSVC 2013, if any of that matters.
Edit: As requested by code_fodder, I added the UpdateFilter and emit code (actualFilterFunction performs the actual filtering, but has nothing to do with signals or GUI or anything).
void MyModel::UpdateFilter() {
m_filterCounter = 0;
m_lastReportedProgress = -1;
invalidate();
}
bool MyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
m_filterCounter++;
int progress = (100 * m_filterCounter) / m_sourceModel->rowCount();
if (progress != m_lastReportedProgress) {
emit filterProgressChanged(m_lastReportedProgress = progress);
}
return actualFilterFunction();
}
Sorry to have to go this way, but this question is already available and I could ask there, but no no, need a reputation of 50 first. This give me 1.
From one of the answers:
QPushButton* viewButton = new QPushButton("View");
tableView->setIndexWidget(model->index(counter,2), viewButton);
I've tried that but the button doesn't display at all in, the code works but nothing shows in the cell. Have looked at the spinbox sample and tried a pushbutton delegate -no success
I'm using a QStandardItemModel to hold the data, add the model to a QSortFilterProxyModel (for filtering purpose) that is set to tableView->setModel. Display data is no problem though not the button.
The index argument in setIndexWidget(QModelIndex const& index, QWidget*) should belong to the same model, which is set in the view. What is your variable "model" refers to? To the data holder model(which is not the model set as view's model!!!) or to the proxy-model?
A safe approach would be to call:
tableView->setIndexWidget(tableView->model()->index(counter,2), viewButton);
In my app I have a QTableView and a QTreeView. I need to drag from the table view and drop in the tree view. I had this working, but I reworked the app so that the GUI elements were created in C++ and this this has stopped working.
I can pick up an item from the table and drop it on the tree - all looks fine. However, the table model's mimeData() method is not called, so the dropped data is incomplete.
How do I get the drag drop operation to call the mimeData() method?
The tables' model is based on a QStandardItemModel.
Had a similar issue with my code. Make sure you override the mimeData properly. For example, some examples show the prototype as:
QMimeData *mimeData(const QList<QTreeWidgetItem *> &items) const;
while the correct prototype is:
QMimeData * mimeData(const QList<QTreeWidgetItem *> items) const;
(note the missing reference in front of items). And if you implement it incorrectly, you're not overriding the mimeData() but implementing another mimeData() function.
An easy check to make sure you're doing it right is to declare a function you want to override, but change its return type from QMimeData* to bool, such as:
bool mimeData(const QList<QTreeWidgetItem *> items) const;
If you are overriding a correct function, your code will not compile due to conflicting return type (you're not allowed to override only the return type). If your code compiles fine, you are not overriding anything but instead declaring a new function. Check the function signature.
This question is an upgrade on the following two questions:
Qt Model View pattern, design choices for connecting Model with Data
Qt Model-View update view?
Here is the situation:
MODEL has a pointer to the SERVER (SERVER represents Data) through which it gets the required data and formats them into QStrings, so that the VIEW can understand them. The model keeps no internal copies of the QList, it accesses it directly and converts the requ QTcpSocket * to QStrings in the QVariant QAbstractItemModel::data method.
However the list of sockets can change without the Model or View knowing about it if a new connection to the SERVER is established. In that case another QTcpSOcket * is appended to the SERVERs QList.
How to notify the View on the Model/Data change?
Call QAbstractItemModel::reset() from the SERVER on each new connection. I consider this bad for it requires to modify the SERVER for the needs of the MODEL in which case i could of just had the MODEL and the SERVER as a single entity.
connect(&server, QTcpServer::newConnection, &model, &StationListModel::reset) Try to connect the SERVER and MODEL via Signals and Slots. However, &StationListModel::reset ISN'T a slot, so i believe this isn't the right way.
I would like to hear which of the mentioned approaches (if any) is considered appropriate in the given situation. And is the insisting on MODEL-SERVER loose coupling a bad design choice?
Here is how it should be done:
Create signals in SERVER that notify about data change (or use existing QTcpServer::newConnection signal if it is sufficient).
Create a slot (or slots) in your model class and connect SERVER's signal to this slot.
In the slot's implementation emit signals or call internal methods (e.g. beginInsertRows, endInsertRows) or just reset the model to notify the view about new changes.
Since you need to incrementally append new items to your view, I would do this in the following way:
In your model class
// A slot
void MyModel::onNewConnection()
{
// Append new socket to the list QList<QTcpSocket *>
m_socketList.puch_back(new QTcpSocket);
// Update the model.
insertRow(0);
}
// Virtual function
bool MyModel::insertRows(int row, int count, const QModelIndex &parent)
{
if (!parent.isValid()) {
// We need to append a row to the bottom of the view.
int rows = rowCount() - 1;
beginInsertRows(parent, rows, rows);
// Do nothing, but just inform view(s) that the internal data has changed
// and these rows should be added.
endInsertRows();
return true;
}
return QAbstractItemModel::insertRows(row, count, parent);
}
Somewhere in your code
[..]
connect(&server, QTcpServer::newConnection, &model, &StationListModel::onNewConnection)
I know this is an old question, but I would like to share what I do when I was working on the exactly same issue.
If you are placing the server pointer into the model implementation and you get all the model information from the QList< QTcpSocket *> use this connection:
connect(server, SIGNAL(newConnection()), this, SIGNAL(modelReset()));