I have a snippet of code that is used to populate a table. It opens a file on disk and throws the data into a table.
if (file.open(QIODevice::ReadOnly))
{
QDataStream stream(&file);
qint32 numRows, numColumns;
stream >> numRows >> numColumns;
QStandardItemModel* tempModel = new QStandardItemModel;
tempModel->setRowCount(numRows);
tempModel->setColumnCount(numColumns);
for (int i = 0; i < numRows ; ++i) {
for (int j = 0; j < numColumns; j++) {
QStandardItem* tempItem = new QStandardItem; // stored in heap
tempItem->read(stream);
tempModel->setItem(i, j, tempItem);
}
}
file.close();
tableView->setModel(tempModel);
...
}
This code works. But the issue I'm having is that, the more files I open, the more memory is being used and it never goes down. If I add a second file, for example, I don't need to store the previous file's model anymore. I want to delete it.
I'm guessing the memory is not being freed up because it never gets deleted since I'm using the new keyword and a pointer.
If we take the tempItem for loop as an example, I imagine I'd have to do something similar to this to fix it:
for (int i = 0; i < numRows ; ++i) {
for (int j = 0; j < numColumns; j++) {
//QStandardItem* tempItem = new QStandardItem;
QStandardItem tempItem; // store on stack and delete at end of scope
//tempItem->read(stream);
tempItem.read(stream);
tempModel->setItem(i, j, tempItem);
}
But even then, it throws an error because QStandardItemModel's setItem (seen here) takes a QStandardItem pointer.
I'd like to fix this for both tempModel and tempItem if possible. What am I doing wrong here?
The memory leak is not due to QStandardItem ownership. The setItem() method takes ownership of the QStandardItem objects, which will be freed automatically when the QStandardItemModel object is freed.
Your memory leak is due to the tableView->setModel(tempModel);statement, because that method does not take ownership. When you change the model, or free the view, you are responsible for freeing the model.
See this document for details.
For example:
QItemSelectionModel *m = tableView->selectionModel();
tableView->setModel(tempModel);
delete m;
I wound up setting a single QStandardItemModel in my header file and my main window's initialization list.
myapp.h
...
QStandardItemModel* mainModel;
...
myapp.cpp
...
MyApp::MyApp(QWidget* parent)
: QMainWindow(parent),
mainModel(new QStandardItemModel(tableView)),
...
I changed the way the application works so that I only ever need one model for my table view. Whenever I need to add a new data set to populate the table with, I clear the current table entirely, create a new, temporary model, then set the temp model to the main model.
mainTableModel->clear();
QStandardItemModel* tempModel = new QStandardItemModel;
tempModel->setRowCount(numRows);
tempModel->setColumnCount(numColumns);
for (int i = 0; i < numRows ; ++i) {
for (int j = 0; j < numColumns; j++) {
QStandardItem* tempItem = new QStandardItem();
tempItem->read(stream);
tempModel->setItem(i, j, tempItem);
}
}
file.close();
mainModel = tempModel;
tableView->setModel(mainModel);
Clearing the model and reusing it lessens, but does not fix, the memory leak.
Related
I have a wxScrolledWindow object filled with elements (pictures) (every element add with the class ThumbNail which uses dc.paint). I would like to dynamically change the elements with new once (not the same number) (change of folder by the user).
How can I empty all the items in wxScrolledWindow object and put new once back in? And then reset the scrollbars.
_ScrolThumbs = new wxScrolledWindow(_pMainPanel);
wxGridSizer *m_swSizer = new wxGridSizer(1,1,0);
_ScrolThumbs->SetSizer(m_swSizer); // Sets the window to have the given layout sizer.
std::vector<ThumbNail*> _Thumbs;
for(int i=0;i < FilePr::Instance()->GetNumThumbs() ;i++)
{
_Thumbs.push_back(new ThumbNail(_ScrolThumbs, PicName[i]));
_ScrolThumbs ->GetSizer()->Add (_Thumbs[i], 1, wxALL|wxALIGN_CENTER_VERTICAL, 5);
}
Then I tried to do this (when a button is hit):
wxWindowList& lst = _ScrolThumbs->GetChildren();
//if (!lst.empty())
std::cout << lst.size() << '\n';
while(!lst.empty()) //for(int i = 0; i < lst.size(); i++) //lst.size()
{
wxWindow *wnd = lst.back();
wnd->Destroy();
}
But putting new elements back in, like I did above does not work...
Any idea how to do this or were to find help on the web? Thanks!
_ScrolThumbs->GetSizer()->Clear(true);
// or
_ScrolThumbs->DestroyChildren();
I don't know if I do something wrong in my qt code. I just need that itemChanged signal is emitted every time when item data changed.
I use following code to make the model:
QStandardItemModel* model = new QStandardItemModel;
QStandardItem *parentItem = model->invisibleRootItem();
QList<QStandardItem*> itemList1;
QList<QStandardItem*> itemList2;
QList<QStandardItem*> itemList3;
QStandardItem* item1;
QStandardItem* item2;
QStandardItem* item3;
for (int i = 0; i < 3; ++i)
{
item1 = new QStandardItem;
item1->setText("item1-" + QString::number(i));
for (int i = 0; i < 3; ++i)
{
item2 = new QStandardItem;
item2->setText("item2-" + QString::number(i));
for (int i = 0; i < 3; ++i)
{
item3 = new QStandardItem;
item3->setText("item3-" + QString::number(i));
itemList3 << item3;
}
item2->appendRows(itemList3);
itemList3.clear();
itemList2 << item2;
}
item1->appendRows(itemList2);
itemList2.clear();
itemList1 << item1;
}
parentItem->appendRows(itemList1);
itemList1.clear();
ui.treeView->setModel(model);
QObject::connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(onChanged(QStandardItem*)));
and I want, that onChanged will be called every time when item changed - for example, item text edited or checkbox clicked.
But in each case I have triggered itemChanged signal only on "item1-..." level items (first level items), not on item2/3 level items.
Why? And how can I make it correct?
PS: same code with QTreeWidget works perfectly, but I use multithreading in my app and I need to divide model and view. QTreeWidget items can't be created in non-gui thread and qtreewidget can't use self-created model. That's the reason why I must to use QTreeView with QStandardItem.
I just debugged the situation, the reason why you don't get the signal is as follows: When the item's data is changed, you have this in the Qt source:
void QStandardItem::setData(...)
{
/* ... */
if (d->model)
d->model->d_func()->itemChanged(this);
}
On the other hand, when adding a child item to a parent, you have
bool QStandardItemPrivate::insertRows(int row, const QList<QStandardItem*> &items)
{
/* ... */
for (int i = 0; i < items.count(); ++i) {
/* ... */
item->d_func()->model = model;
}
}
So this means, items need a pointer to the model to notify the model about the change, and the model of a child item is set to the parent's item at the time of insertion. Now you add the children to the parent first, and then the parents to their parents, with the invisible root as the parent of level-1 items. As the invisible root has a model, the level-1 items send the signal, but the others do not.
A simple change fixes this: Just add the item to its parent first, and then append the children, like this:
for (int i = 0; i < 3; ++i)
{
item1 = new QStandardItem;
item1->setText("item1-" + QString::number(i));
parentItem->appendRow(item1);
for (int i = 0; i < 3; ++i)
{
item2 = new QStandardItem;
item2->setText("item2-" + QString::number(i));
item1->appendRow(item2);
for (int i = 0; i < 3; ++i)
{
item3 = new QStandardItem;
item3->setText("item3-" + QString::number(i));
item2->appendRow(item3);
}
}
}
I am not sure why the default is for child item mods not being causing the parent item to be "changed". I suppose as a workaround you could connect each childs DataChanged() signal to its parents DataChanged() signal, so that the signal propagates up the hierarchy, something like:
for (int i = 0; i < 3; ++i)
{
item1 = new QStandardItem;
item1->setText("item1-" + QString::number(i));
for (int i = 0; i < 3; ++i)
{
item2 = new QStandardItem;
item2->setText("item2-" + QString::number(i));
for (int i = 0; i < 3; ++i)
{
item3 = new QStandardItem;
item3->setText("item3-" + QString::number(i));
itemList3 << item3;
connect(item3, SIGNAL(DataChanged()), item2, SIGNAL(DataChanged()));
}
item2->appendRows(itemList3);
itemList3.clear();
itemList2 << item2;
connect(item2, SIGNAL(DataChanged()), item1, SIGNAL(DataChanged()));
}
item1->appendRows(itemList2);
itemList2.clear();
itemList1 << item1;
}
What should happen is that if you change an item3 level record the signal should be forwarded all the way up to item1 (which you suggest is working) and then continue as normal:
item3 ---DataChanged()---> item2
item2 ---DataChanged()---> item1
item1 ---DataChanged()---> model
model ---itemChanged()---> onChanged()
I have not tested this, but assuming the item1 dataChanged() signal is working for you (which your comments suggest) then this should work.
Edit
Ah, I think this may not actually work as you want it. I think you will always get an item1 pointer sent to your onChanged() slot. If you want the item3 pointer sent then you may have to sub class QStandardItem (make a class that inherits QStandardItem) and then do the following:
Add a signal that emits: itemChanged(QStandardItem*)
add a slot: void itemHasChanged() {emit itemChanged(this);}
connect dataChanged() to itemHasChanged() in the constructor.
Then in your loop you can item1 = new myQStandardItem;
And then for each new item you add directly connect them to your onChanged() slot:
QObject::connect(itemX, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(onChanged(QStandardItem*)));
It's a bit of an extra effort (but not too much), if you can't get the model to do it for you (i.e. a plan-B)...
I have a QTableView that enables MultiSelection selectionMode following a SelectRows behavior as follow:
QSqlQueryModel model = db_manager->get_all();
ui->tableView->setModel(model);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableView->setSelectionMode(QAbstractItemView::MultiSelection);
ui->tableView->show();
The selection is working properly before which the indexes of theses selected rows are put into QModelIndexList as follow:
selectedRowsIndexesList = ui->tableView->selectionModel()->selectedRows();
Now I need to extract the information where these indexes are pointing to. I do this at the moment manually as follow:
qDebug() << model->index(0,0).data().toString();
I change the first "0" manually. But I need to automate the process through a for-loop statement like this:
for (int i = 0; i < selectedRowsIndexesList.size(); i++){
qDebug() << model->index(??,0).data().toString();
}
How can I do that ?
You already have the indexes in a list, so why go back to the model?
You can simply access the data using the indexes you've stored:
for (int i = 0; i < selectedRowsIndexesList.size(); i++){
qDebug() << selectedRowsIndexesList[i].data().toString();
}
The QModelIndexList is just a typedef as per the documentation. It is just a synonym for QList<QModelIndex> and you can iterate through the indexes as you would do through any QList variables.
for(int i = 0; i < mindex_list.size(); i++)
qDebug() << mindex_list.at(i).toString();
Or something similar.
In Qt I understand that if you create an object that inherits from QObject and set it's parent, then the parent is responsible for deleting the children. I also understand that if you create objects on the stack that they are deleted when they go out of scope.
I'm not wrapping my head around the case where I want to explicitly delete items in a QStringList ( or any QList ) and then the list itself.
Consider if I create a QStringList and populate it like below:
QStringList multiPartPortionList;
multiPartPortionList = multiPartPortion.split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
Then I do stuff with it and call this method in attempt to explicitly delete the items contained within the list and then the list itself:
void MyClass::deleteList(QStringList list) {
// Now cleanup
QStringList *delList = new QStringList(list);
int numObjects = delList->count();
for (int i=0; i < numObjects; i++)
delete (new QString(list.takeAt(0)));
delList->clear();
delete delList;
delList = 0;
}
I can't call qDeleteall(list) because it expects a pointer, and I also can't do just do:
delete list;
Since it errors with 'argument given to 'delete', expected pointer', expecting a pointer.
I get segmentation faults with:
void MyClass::deleteList(QStringList list) {
// Now cleanup
QStringList *delList = &list; // <--- Seg fault with this
int numObjects = delList->count();
for (int i=0; i < numObjects; i++)
delete (new QString(list.takeAt(0)));
delList->clear();
delete delList;
delList = 0;
}
I also get segmentation faults with:
void MyClass::deleteList(QStringList list) {
// Now cleanup
QStringList *delList = &list;
int numObjects = delList->count();
for (int i=0; i < numObjects; i++)
delete &(list.takeAt(0)); // <--- Seg fault with this
delList->clear();
delete delList;
delList = 0;
}
And then I also get segmentation faults with:
void MyClass::deleteList(QStringList list) {
// Now cleanup
QStringList *delList = new QStringList(list);
int numObjects = delList->count();
for (int i=0; i < numObjects; i++)
delete (new QString(list.takeAt(0)));
delList->clear();
delete delList;
delList = 0;
delete &list; // <--- Seg fault with this
}
I don't think this is quite, or even close, to being right. How can I best achieve what I want - that is to explicitly delete all items in a list and then the list itself?
Your QStringList list gets deleted automagically at the end of the function. No need to worry about deleting pointers here.
In the same manner,
QStringList *delList = new QStringList(list);
should be
QStringList delList = QStringList(list);
better yet:
QStringList delList = list;
You should read about automatic storage and RAII. In your code, new and delete are superfluous.
To remove an element in the list, use removeAt. This has nothing to do with deleting.
I also suggest that you get a good book before continuing in C++.
I have small doubt about Qt memory management.
Let's take an example of Listview, in listview we add each item by allocating memory dynamically. So in this case do we need to delete all the "new"ed items manually.
E.g:
Qlistview *list = new Qlistview;
QStandardItemModel *mModel = new QStandardItemModel();
list ->setModel(mModel);
for(int I =0;i<10;i++)
{
QsandardItem *item = new QsandardItem(“Hi”);
mModel->appendRow(item);
}
In this example, should item be deleted manually?
QStandardItemModel takes ownership of items, so they will be automatically deleted when model is destroyed. You still need to delete the model itself (setModel() doesn't transfer ownership of model to the view, because one model can be used by multiple views).
Agree with chalup's answer , the answer for your question is:
if you called mModel->clear();, it will help you delele all of those items, you don't need to mannually delete items one by one, what's more if you want to totally delete model, you should called delete mModel;.
Run the example code provided by ChrisW67 here, you will have a better understanding:
#include <QCoreApplication>
#include <QDebug>
#include <QStandardItemModel>
class MyItem: public QStandardItem
{
public:
MyItem(const QString & text): QStandardItem(text) {
qDebug() << "Item created" << this;
}
~MyItem() {
qDebug() << "Item destroyed" << this;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QStandardItemModel* model = new QStandardItemModel;
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item =
new MyItem(QString("row %0, column %1").arg(row).arg(column));
model->setItem(row, column, item);
}
}
qDebug() << "Finished making model";
model->clear();
qDebug() << "Model cleared";
qDebug() << "===================";
QStandardItem *newitem1 = new MyItem(QString("new item"));
qDebug()<<"create new item at"<<newitem1;
QStandardItem *newitem2 = new MyItem(QString("new item"));
QStandardItem *newitem3 = new MyItem(QString("new item"));
QStandardItem *newitem4 = new MyItem(QString("new item"));
//because we didn't delete model so far, we can still append items to model.
model->appendRow({newitem1,newitem2,newitem3,newitem4});
model->clear();
qDebug() << "Model cleared again";
//although the memoty of newitem1 has already been deallocated, but the pointer still point to that address, now newitem1 is a dangling pointer
if(newitem1){
qDebug()<<"newitem1 address is"<<newitem1;
}
else{
qDebug()<<"newitem1 address in null";
}
// delete newitem1;//this will cause program crash because newitem1 acutally has been delete, double delete should not be allowed
newitem1 = nullptr;//Instead of delete, wo could set newitem1 pointet to null, this can avoid wild pinter/dangling pointer
delete model;
qDebug()<<"deleted model";
return a.exec();
I am learing c++ too, if there is something wrong above, please tell me, i will correct it.