Suppose I have a 2D array full of data say 10 x 10. The contents, as well as a number of rows, can change any time.
Now I want to display this data in a QTableWidget.
I use a timer with time out 1sec to refresh the table contents. In the timeout slot if I use
void slot_timeOut()
{
//Iterate over the rows
//and for each cell do something like
ui->tw_data->setItem(row, 0, new TableWidgetItem(data[row][0]);
ui->tw_data->setItem(row, 0, new TableWidgetItem(data[row][1]);
//...
ui->tw_data->setItem(row, 0, new TableWidgetItem(data[row][9]);
}
the use out new TableWidgetItem worries me. I have no reference to it and I never delete it.
Over a period of time is this a memory leak, or is this managed by Qt, pls help...
There is no leak, as
The table takes ownership of the item.
(From QTableWidget::setItem()).
Ownership here means that the QTableWidget will take care of deleting the item when its not longer needed, or the QTableWidget itself is destroyed. Ownership is usually documented in the Qt documentation (if not, I'd consider that a Qt bug).
Once setItem() returns from being called on the same cell, the previously set QTableWidgetItem will be deleted:
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QTableWidget widget(2, 1);
QTableWidgetItem* foo = new QTableWidgetItem("Foo");
widget.setItem(0, 0, foo);
qDebug() << foo->text(); //works
widget.setItem(0, 0, new QTableWidgetItem("Bar")); //replaces foo with bar and deletes foo
qDebug() << foo->text(); // Undefined (usually, crash)
widget.show();
return app.exec();
}
If you're on Linux, you can also verify the behavior by running above code (without the second qDebug()) in valgrind with --leak-check=full.
From the Qt documentation:
void QTableWidget::setItem ( int row, int column, QTableWidgetItem *
item ) Sets the item for the given row and column to item. The table
takes ownership of the item.
This indicates that Qt will manage the memory for the object as necessary.
It will delete properly when the you add Q-objects to the cells.
I was pushing thousands of C-strings in cells and watched my memory blow up.
Made my "raw text data" a QString and my problem was solved.
Such problem takes place to be: Qt does not really clear the memory allocated through QTableWidgetItem.
for( int row = 0; row < 35; row++)
{
for(int i = 0; i < 9; i++)
{
QTableWidgetItem* item = new QTableWidgetItem();
item->setText(QString::number(i));
ui->tableWidget->setItem(row, i, item);
}
}
This code gives 116 bytes of leakage.
We found this solution: Instead of QTableWidgetItem, you can use QLineEdit:
QLineEdit* tableline = new QLineEdit();
m_qtablewidget->setCellWidget(row, column, tableline);
p.s. To search for leaks used Dr.memory
Related
I've a question that couldn't find anywhere. I have a QMap that's ignoring the QMap.insert(Key, Value) command. Here's the code:
//gets the selected problem index on the ProblemList
int selProblem = ui->tree_projects->currentItem()->data(0, Qt::UserRole).toInt();
//creates a new problem, sets its values and then replaces the old one on the ProblemsList variable
ProblemSets nProblem;
if(!problemsList.isEmpty()) //problemsList is an attribute of MainWindow
nProblem = problemsList.value(selProblem);
// some data collection that has been omitted because isn't important
// temporary maps that will carry the modifications
QMap<int, QString> nResName, nResType;
//data insertion into the maps
//these are fine
nResName.insert(fIdx, results_model->data(results_model->index(fIdx, 0)).toString());
nResType.insert(fIdx, results_model->data(results_model->index(fIdx, 1)).toString());
//replaces the old maps with the new ones
nProblem.SetProbResultsNames(nResName);
nProblem.SetProbResultsTypes(nResType);
//replaces the old problem with the new one
problemsList.insert(selProblem, nProblem); //this is the line that's doing nothing
}
That last line appears to be doing nothing! I've even tried to use
problemsList.remove(selProblem);
problemList.insert(selProblem, nProblem);
but got a similar result: the map not being inserted at the index selProblem. It got inserted, but with an outdated value - the same one of the deleted index -. I've checked on Debug and all the indexes and variables are correct, but when the .insert hits, nothing happens.
The most awkward thing is that this code is a copy/paste that I made from another method that I'm using that does similar thing, just changing the variable names, but that one works.
EDIT 1: This is the contents of nProblem, selProb and problemsList.value(selProblem)
Just before the Line:
problemsList.insert(selProblem, nProblem);
selProb: 0
nProblem:
ProbResultsNames: "NewRow0"
ProbResultsType: "Real"
problemsList.value(selProblem):
ProbResultsNames: non-existent
ProbResultsType: non-existent
After the line
problemsList.insert(selProblem, nProblem);
selProb: 0
nProblem:
ProbResultsNames: "NewRow0"
ProbResultsType: "Real"
problemsList.value(selProblem):
ProbResultsNames: non-existent
ProbResultsType: non-existent
EDIT 2:
class ProblemSets
{
public:
ProblemSets();
virtual ~ProblemSets();
ProblemSets(const ProblemSets& other);
ProblemSets& operator=(const ProblemSets& other);
//I hid getters and setters to avoid pollution on the post
private:
int index;
bool usingBenchmark;
QString functionSelected;
QString info;
QMap<int, QString> probVars_name, probVars_type, probResultsNames, probResultsTypes;
QMap<int, float> probVars_min, probVars_max;
QMap<int, int> probVars_stpSize, probVars_stp;
int varsNumber; // holds how many vars has been created, just for display purposes
int resNumber; // holds how many results has been created, just for display purposes
};
A simple test proves that QMap works as expected:
QMap<int, QString> mm;
mm.insert(1, "Test1");
qDebug() << mm[1]; // "Test1"
mm.remove(1);
qDebug() << mm[1]; // "" (default constructed value)
mm.insert(1, "Test2");
qDebug() << mm[1]; // "Test2"
Which means that the problem lies in your code.
This statement itself is highly suspicious:
That last line appears to be doing nothing!
Because then you go on to say that the map still contains the "old value". But you removed that key, so if the insert() method didn't work, you shouldn't be getting the old value, but a default constructed value.
Which means that the problem is most likely that nProblem has the same value as the one that is previously associated to that key in the map. The map works, you values are likely wrong.
Found the issue! I didn't have both the variables declared on the copy method of the ProblemSets class.
Solved simply adding them to the copy method
MainWindow::ProblemSets::ProblemSets(const ProblemSets& other)
{
// copy
index = other.index;
usingBenchmark = other.usingBenchmark;
functionSelected = other.functionSelected;
info = other.info;
probVars_name = other.probVars_name;
probVars_type = other.probVars_type;
probVars_min = other.probVars_min;
probVars_max = other.probVars_max;
probVars_stpSize = other.probVars_stpSize;
probVars_stp = other.probVars_stp;
//here
probResultsNames = other.probResultsNames;
probResultsTypes = other.probResultsTypes;
//
varsNumber = other.varsNumber;
resNumber = other.resNumber;
}
I had this issue before with the std::vector class, and that's why I suspected that could be that. Thanks to everyone that helped!
I am trying to get listview and tableview working together.
The listview must be used for display, the tableview must be used for editing data. The tableview is created on demand in a popup widget (and it may never be needed).
I populate the listview, on start, from a text file - each row a line, with 2 entries separated by a tab. Easy.
The tableview will have to edit 2 columns separately... also, on listview click, I must be able to retrieve the first part of the split...
I have created a model subclass of QStringListModel.
QListView *m_myView = new QListView();
StringList *m_myList = new StringList();
QTextStream in(&myFile);
while (!in.atEnd())
{
QString temp = in.readLine();
if(!temp.isEmpty())
m_myList->append(temp);
}
myFile.close();
m_myView->setModel(m_myList);
where
class StringList : public QStringListModel
{
public:
void append (const QString& string)
{
insertRows(rowCount(), 1);
QModelIndex m = index(rowCount() - 1);
setData(m, string, Qt::EditRole);
QStringList splist = string.split('\t');
setData(m, splist.at(0), Qt::ToolTipRole);
if(splist.size() > 1)
setData(m, splist.at(1), Qt::StatusTipRole);
else
setData(m, "", Qt::StatusTipRole);
qDebug() << data(m, Qt::EditRole).toString()
<< data(m, Qt::ToolTipRole).toString()
<< data(m, Qt::StatusTipRole).toString();
}
};
The EditRole displays correctly, the others display empty strings.
It seems that QStringListModel is incapable of storing anything except EditRole.
So I am left with 2 options - either do the split on each selection, and also when creating the table view, or - what I would like to try but don't know how - create a QStandardItemModel that can act as data sources for both the listview and the tableview, and can easily retrieve the partial data I require on click.
I thought I can simply use it to set the data on listview (like they do here).
QListView *m_myView = new QListView();
QStandardItemModel *m_myList = new QStandardItemModel();
QTextStream in(&myFile);
int r = 0;
while (!in.atEnd())
{
QString temp = in.readLine();
if(!temp.isEmpty())
{
QStringList splist = temp.split('\t'); // assume I know there will be 2 strings always
QStandardItem *item = new QStandardItem(splist.at(0));
m_myList->setItem(r, 0, item);
QStandardItem *item1 = new QStandardItem(splist.at(1));
m_myList->setItem(r, 1, item1);
++r;
}
}
myFile.close();
m_myView->setModel(m_myList);
connect(m_myListView, SIGNAL(clicked(QModelIndex)),
this, SLOT(listChangedSlot(QModelIndex)));
But this will only set the first string in the listview, I really need both, and I still don't know how to retrieve the data
void listChangedSlot(QModelIndex index)
{
qDebug() << m_myList->item(index.row(), 0)->data().toString()
<< m_myList->item(index.row(), 1)->data().toString();
} // shows empty strings
Or...
In the loading section, try:
if(!temp.isEmpty())
{
QStringList splist = temp.split('\t');
splist.append(QString()); // so I don't need to test
QStandardItem *item = new QStandardItem(temp);
m_myList->setItem(r, 0, item);
QModelIndex idx = m_myList->index(r, 0);
QMap<int, QVariant> roles;
roles.insert(Qt::UserRole + 1, QVariant(splist.at(0)));
roles.insert(Qt::UserRole + 2, QVariant(splist.at(1)));
roles.insert(Qt::DisplayRole, QVariant(temp));
m_myList->setItemData(idx, roles);
++r;
}
This works - displays fine and gets the correct content on click - but seems to be unusable for the tableview.
(And seems I am doing twice the work, with setItem and setItemData - so technically storing the content 3 times).
How can I get my listview have a datasource with 2 string items, show both, be able to set it on a tableview, and be able to retrieve the first item on click ?
I figured out a way to share the data source between the listview and tableview:
I create 3 columns - column 0 with the combined components, and columns 1 and 2 with the separated components.
The listview will display only column 0. For tableview, I will hide column 0.
The data I require for processing will be stored in columns 1 and 2. user edit of the data in tableview will require, upon accepting changes, the edit of corresponding column 0.
I am using QListWidgetItem to add Items in my QListWidget.
In some situations, I want some rows of my QListWidget become non selectable. (I mean I want some QListWidgetItem to be non selectable)
Is ther any way to do this?
PS: I tried
listWidgetItem->setFlags(Qt::NoItemFlags)
listWidgetItem->setSelected(false);
but they don't disable the selection of items.
Edit:
QStringList _strListClients = _strClients.split(",",QString::KeepEmptyParts,Qt::CaseInsensitive);
for(int i = 0; i < _strListClients.count(); i++)//Add Client's Check Boxes
{
QListWidgetItem* _listWidgetItem = new QListWidgetItem(_strListClients[i], listWidgetClients);
listWidgetClients->addItem(_listWidgetItem);
if(_strListClients[i] == "Unknown"){
_listWidgetItem->setSelected(false);
_listWidgetItem->setTextColor(Qt::red);
_listWidgetItem->setFlags(_listWidgetItem->flags() & ~Qt::ItemIsSelectable);
}
}
Just remove the Qt::ItemIsSelectable flag from each item:
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
Or remove Qt::ItemIsEnabled if you want to remove all interaction with the item.
E.g.
#include <QtWidgets>
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QListWidget widget;
for (int i = 0; i < 100; ++i) {
QListWidgetItem *item = new QListWidgetItem(QStringLiteral("Item %1").arg(i));
if (i % 2 == 0) // disable one every two
item->setFlags(item->flags() & ~Qt::ItemIsSelectable);
widget.addItem(item);
}
widget.show();
return app.exec();
}
You can try the following:
1) Override the clicked/selection event (sorry I don't remember the exact name.
Doing so you can have some sort of flag/bool value on the item, and if is set as not being selectable you just return.
2) Rather then override you just connect to the signal and perform the above check and if you don't want to select that item you un-select it afterwards.
A bit of a work around but I don't know if there is a built in way to do so.
Checking the documentation I did not see a disable method on the item itself.
If you go down the road of list view you should have more control on that, also on the display, so you might be able to displayed it greyed etc. A view is a bit more work though.
M.
In QT 4.7, I am trying to make one QTableWidgetItem in a QTableWidget Editable and the rest all columns should be read only for me. I am having problems here.
I have checked a number of samples through google and stackoverflow but failed to achieve this. Some of the options I tried are,
I create rows by calling insertRow(rownumber) for adding rows.
Trial 1: I do the following while inserting a row dynamically
Enable Edit triggers in the UI Dialog
Add columns using the following code for disabling edit
QTableWidgetItem qit("");
qit.setflags(qit.flags() & ~Qt::ItemIsEditable)
qtable.setitem(row,column, &qit);
And for others columns I don't set the flags
This above approach did not work. I am able edit all columns (even the one I negated the editable option)
Trial 2:
Do all the above with just qtable.setEditTriggers(Qt::NoEditTriggers) and then set the columns editable wherever required.
But this option renders all columns non-editable.
But I don't see anyone complaining like this in any forums. So I must be making some stupid mistake.
Have someone come across such an issue, if yes please help by answering.
Working example of QTableWidget
First item in added row is editable, second one is not editable.
#include <QtGui>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Prepare layout
QMainWindow *window = new QMainWindow;
QTableWidget *tablewidget = new QTableWidget;
window->setCentralWidget(tablewidget);
// Add data
tablewidget->insertRow(0);
tablewidget->insertColumn(0);
tablewidget->insertColumn(1);
QTableWidgetItem *item;
item = new QTableWidgetItem("editable");
tablewidget->setItem(0,0,item);
item = new QTableWidgetItem("non editable");
item->setFlags(item->flags() & ~Qt::ItemIsEditable); // non editable
tablewidget->setItem(0,1,item);
window->show();
return a.exec();
}
i want to set my font as bold in particular row column position of my tablewidget.
I did like this but getting break.
QFont font("Helvetica", 12, QFont::Bold);
overviewTable->item(2,2)->setFont(font);
Please Help
I think everything is ok. Here what docs said:
void QTableWidgetItem::setFont ( const QFont & font )
Sets the font used to display the item's text to the given font.
Maybe your overviewTable const?
ADDED:
This variant works fine for my Qt 4.6:
tableWidget = new QTableWidget(12, 3, this);
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 3; j++) {
QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
(i+1)*(j+1)));
tableWidget->setItem(i, j, newItem);
}
}
QFont font;
font.setBold(true);
tableWidget->item(2, 2)->setFont(font);
Maybe you are getting break because you didn't call setItem() to set an item for the cell (2, 2) before you use overviewTable->item(2,2). As the Qt document says,
QTableWidgetItem * QTableWidget::item(int row, int column) const
Returns the item for the given row and column if one has been set;
otherwise returns 0.
That is, your overviewTable->item(2,2) probably returns 0, thus causes a Segmentation fault in the setFont() call.
So your means to setting font is completely right. You just need to call setItem() at first as mosg's answer suggests.
ADDED:
if your overviewTable is a QTableWidget created in Qt Designer, then in the Designer a double-click on a cell (just to enter its editing mode, no need to actually enter anything) will have the effect of calling setItem() for that cell. Later in your code you can directly using the item() function without having to call setItem() first.