In my ui I have a button, which adds QComboBox (with some items) and QLabel(with some text) when clicked. Variable "index" is the number of added QComboBoxes and QLabels. "ol" is qvector with some data.
void MainWindow::on_iAddOtherButton_clicked()
{
QComboBox *p1 = new QComboBox(this);
p1->setObjectName("comboBox"+QString::number(index));
QLabel *p2 = new QLabel(this);
p2->setObjectName("othLabel"+QString::number(index));
for(int i = 0; i < static_cast<int>(ol.size()); ++i){
p1->addItem(ol.at(i).getName());
ui->otherLayout->addWidget(p1,index+1,0);
}
p2->setText(...some text...));
ui->otherLayout->addWidget(p2,index+1,1);
index++;
}
And this works well, in the layout they are in pairs like this:
QComboBox1 QLabel1
QComboBox2 Qlabel2
Now I want to change value of the QComboBox1, after which, text of the QLabel1, will automatically change to something else.
I tried to do this with connect, but QComboBox on currentTextChanged() emits only QString with a new value. Is there some way to emit name of object + new value?
Or there is some completely other solution to do this?
From within your slot, you can call sender() to get a pointer to the sender of the signal. From there you can decide what to do next based on sender()->objectName().
You might also consider using setProperty(...) on the combobox objects to store an index to a vector of pointers to QLabel instances with the vector being a class member. Then you could retrieve the index inside your slot by calling sender()->property(...) and use it to access the correct QLabel widget from the vector.
Related
I have a QTreeWidget with two columns: one for property name and one for property value. The value can be edited via a widget. For example one property is Animal. When you double click the property value column I make a (custom) combobox with different animal types via this code:
QTreeWidgetItemComboBox* comboBox = new QTreeWidgetItemComboBox(treeItem, 1);
// treeitem is a pointer to the row that is double clicked
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
ui.treeWidget->setItemWidget(treeItem, 1, comboBox);
When the row loses focus I remove the widget again (and the value is put as text of the QTreeWidgetItem). For removing I use
ui.treeWidget->removeItemWidget(treeItem, 1);
Now I'm wondering, since I've used new, do I neww to also delete the widget. I know this is the case if you use takeChild(i) for example. But I didn't see something similar for an itemWidget.
Do I need to delete it what would be the right order?
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
ui.treeWidget->removeItemWidget(treeItem, 1);
delete comboBox;
or
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
delete comboBox;
ui.treeWidget->removeItemWidget(treeItem, 1);
When the widget is added ot the QTreeWidget, it indeed takes ownership of the widget. But it only implies that the widget will be deleted when the parent is destroyed.
So if you just want to remove the widget while keeping the parent QTreeWidget alive, you indeed have to delete it manually.
The correct solution is the first one, remove the widget from the QTreeWidget first, and then delete it with one of the following ways:
delete comboBox;
comboBox = nullptr;
or:
comboBox.deleteLater();
The second one is preferred.
EDIT:
I don't change the answer since it could be a dishonest to change what was already accepted, ...
But as #Scopchanov mentioned, by reading the source code, the QTreeWidget::removeItemWidget() already calls the deleteLater() method on the old widget. We don't have to do it manually.
Anyway, the documentation says it is safe to call deleteLater() more than once:
Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.
Therefore, manually deleting the widget after calling QTreeWidget::removeItemWidget() becomes useless.
You are not allowed to delete the item widget as the tree is the owner of the widget once it has been passed to the tree with setItemWidget().
From the documentation of setItemWidget():
Note: The tree takes ownership of the widget.
EDIT: In case you want a new widget, simply call setItemWidget() once more or call removeItemWidget() in case you do not need the widget anymore. The tree will ensure that no memory gets lost.
Explaination
You should not manually delete a widget, added to a QTreeWidget, since it is automatically deleted either by
destructing its parent tree widget
This is a direct consequence of the Qt's parent-child mechanism.
calling QTreeWidget::removeItemWidget anytime the tree widget still lives.
This one is not so obvious, since the documentation simply sais:
Removes the widget set in the given item in the given column.
However, looking at the source code it becomes pretty clear what is indeed happening, i.e.
QTreeWidget::removeItemWidget calls QTreeWidget::setItemWidget with a null pointer (no widget)
inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
{ setItemWidget(item, column, nullptr); }
QTreeWidget::setItemWidget in turn calls QAbstractItemView::setIndexWidget
void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
{
Q_D(QTreeWidget);
QAbstractItemView::setIndexWidget(d->index(item, column), widget);
}
Finally QAbstractItemView::setIndexWidget checks if there is already a widget at this index, and if there is one, calls its deleteLater method
if (QWidget *oldWidget = indexWidget(index)) {
d->persistent.remove(oldWidget);
d->removeEditor(oldWidget);
oldWidget->removeEventFilter(this);
oldWidget->deleteLater();
}
Simply put (and this should be made clear in the documentation of both methods of QTreeWidget), any call to QTreeWidget::setItemWidget or QTreeWidget::removeItemWidget deletes the widget (if any) already set for the item.
Example
Here is a simple example I have prepared for you in order to demonstrate the described behaviour:
#include <QApplication>
#include <QBoxLayout>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
struct MainWindow : public QWidget
{
MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
auto *item = new QTreeWidgetItem(treeWidget);
auto *button = new QPushButton(tr("Remove combo box"), this);
auto *comboBox = new QComboBox();
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
treeWidget->setItemWidget(item, 0, comboBox);
l->addWidget(button);
l->addWidget(treeWidget);
connect(comboBox, &QComboBox::destroyed, [](){
qDebug("The combo box is gone.");
});
connect(button, &QPushButton::clicked, [treeWidget, item](){
treeWidget->removeItemWidget(item, 0);
});
resize(400, 300);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Result
The described ways of destroyng the widget could be tested with the application
Simply closing the window destroys the tree widget together with its child combo box, hence the combo box's destroyed signal is emitted and the lambda prints
The combo box is gone.
After pressing the button the lambda function connected to its clicked signal is called, which removes the combo box from the tree widget. Because the combo box is deleted (automatically) as well, the lambda from the second connect statement is called, which also prints
The combo box is gone.
This is a follow up question of Efficient way to make an array of labels.
I have an array of buttons made by code (not designer) which are all added to a gridlayout. What I want is to be able to click any button on that gridlayout and call one same function with the row and column as parameters. Why I want this is because I do not feel like writing 15x15 functions which all do the same thing.
Is there a way or should I try to find another solution?
Ps. All my other input is made in the qt designer via "go to slot" so if it has to happen otherwise, I'll be clueless about how to.
Edit: The array of labels is now an array of buttons.
You could connect all of your buttons to a slot with no parameters and then get the position of the sender in this steps:
Cast the sender QObject to a QWidget via qobject_cast
Retrieve the index of that QWidget using QLayout::indexOf(QWidget *widget)
Then get the row, column, column span and row span with the QGridLayout::getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan)
The example code would look like this:
void MyWidgetWithAllLabels::commonSlot()
{
QWidget *buttonWidget = qobject_cast<QWidget*>(sender());
if (!buttonWidget)
return;
int indexOfButton = ui->gridLayout->indexOf(buttonWidget);
int rowOfButton, columnOfButton, rowSpanOfButton, columnSpanOfButton;
ui->gridLayout->getItemPosition(indexOfButton,
&rowOfButton, &columnOfButton, &rowSpanOfButton, &columnSpanOfLabel);
// Now you can get a reference to that specific QPushButton
QLayoutItem *item = ui->gridLayout->itemAtPosition(rowOfButton, columnOfButton);
QPushButton *clickedButton = qobject_cast<QPushButton*>(item->widget());
if (!clickedButton)
return;
// ... do something with that clickedButton
}
Referring to the code in your related post, you can connect your buttons to that slot like this:
connect( ui->tile_0_0, SIGNAL(clicked()),
this, SLOT(commonSlot()));
connect( ui->tile_0_1, SIGNAL(clicked()),
this, SLOT(commonSlot()));
// ...
By default, a QLabel has no "clicked" signal.
But you can do your own QLabel with 2 integers (row, col) and when you've got a mouseReleaseEvent (or mousePressEvent), you send a custom signal that looks like this: clicked(int row, int col).
You can also use a QSignalMapper:
http://qt-project.org/doc/qt-4.8/qsignalmapper.html#details
I have created instance of QAction inside QGraphicsView child class and connected it to my slot in the same class.
QAction *action = new QAction(tr("New"), this);
action->setObjectName("addStopAction");
action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_N));
connect(action, SIGNAL(triggered()), this, SLOT(addNew()));
addAction(action);
Slot is a function creating new instance of QGraphicsItem on scene assigned to QGraphicsView.
void MyGraphicsView::addNew() {
// Insert new item at cursor position
}
I also add this action to a QMenu which serves as my class context menu.
QMenu *contextMenu = new QMenu(this);
contextMenu->addAction(action);
Everything works fine. When I press Command/Ctrl + N new item is created at cursor position. But when I right-click and select action from context menu I want new item to be created at menu positon.
I can, of course, do some little hack to flag if SLOT was called after contextMenuEvent or something like that, but what I would like to know is:
Is there any way to find out what made QAction emit its triggered() signal inside connected SLOT? That way I could handle when I should place new item at cursor position and when at context menu position inside SLOT implementation.
Of course, you can find out what signal emit inside connected SLOT.
Just use QObject::sender(). In you case:
void MyGraphicsView::addNew() {
QAction* pAction = qobject_cast<QAction*>(sender());
Q_ASSERT(pAction);
// do something with pAction
}
I think you can use custom data that a QAction object can contain.
You can set it when you create a context menu:
void showContextMenu(const QPoint &pos)
{
...
action->setData(pos);
...
}
And in the addNew() function you check if data exists and reset it in the end:
void addNew()
{
QPoint pos;
QPoint posFromAction = action->data()->toPoint();
if (posFromAction.isNull())
{
pos = QCursor::pos(); ///< pos will be current cursor's position
}
else
{
pos = posFromAction; ///< pos will be menu's position
}
doYourStuffAt(pos)
action->setData(QPoint()); ///< reset action's data
}
i managed something similar by connecting the menu to a function like connect (menu, SIGNAL( triggered(QAction*) ), this, SLOT( menuAction_triggered(QAction*) ));
when you execute you context menu, the QMenu::exec(QPoint) will return you the pointer to the action, so you may not need a extra function/slot for it.
you can check for the name of the action with its text QAction::text() or if you have stored your pointers somewhere by comparing the address.
soo long zai
You can reference self.sender() in the function that is called when by customContextMenuRequested signal.
self.tree1.customContextMenuRequested.connect(self.menu1pop) # rightclick menu signal
...
def menu1pop(self, pos):
widget = self.sender()
item = widget.itemAt(pos)
if item is None: return # only show contextmenu if on an item
self.setProperty("mywidget", widget) # pass current widget
self.menu1.popup(QCursor.pos()) # show menu at right click cursor position
self.sender() will return your widget object and then you can set the widget object inside a property.
Then when your action function is called you can recall the widget object by reading the property.
widget = self.property("mywidget")
It's a bit of a hack, but a simple and reliable way to know which widget your action was called from.
You MIGHT be able to use QObject::sender() in the slot that receives the call. Not tried this for actions though. It's probably a bit uglier than your proposed 'hack' (where you could actually implement that quite nicely with a scoped class).
I am having difficulty finding documentation on this or an example.
Could someone concretely show me how to access the QVariant of the currently selected index in a QComboBox
QComboBox * combo = new QComboBox();
combo->addItem("Bla1", QVariant(1));
combo->addItem("Bla2", QVariant(2));
combo->addItem("Bla3", QVariant(3));
combo->addItem("Bla4", QVariant(4));
connect(combo, SIGNAL(currentIndexChanged(int)), this, slot(HANDLEITMAN(int))
And of course else where in the source
void TheCooler::HANDLEITMAN(int index)
{
//What do I do with index?
//sender()?
}
First, make combo a member of TheCooler, or otherwise put HANDLEITMAN in a class which has combo as a member. Unless it's available to TheCooler::HANDLEITMAN somehow you can't get the data, and this is the logical way to do it. Then it's just
void TheCooler::HANDLEITMAN(int index)
{
QVariant data = combo->itemData(index);
}
If you don't want to make combo a member of the class TheCooler, you can use the sender() function that returns a pointer to the QObject that sent the triggering signal (in this case, currentIndexChanged(int)).
void TheCooler::HANDLEITMAN(int index)
{
QComboBox * combo = qobject_cast< QComboBox * >(sender());
if (combo == 0)
return; // something wrong happened
QVariant data = combo->itemData(index);
}
If combo is null, then you probably tried to call the slot by yourself, or you have connected it with a signal emitted by a class that is not a QComboBox.
I know there is many questions about memory management in Qt. Also I read these SO questions:
Memory Management in Qt
Memory management in Qt?
Qt memory management. What's wrong?
But in my case, I confused again!
I have a QTableWidget with name myTable. I add run-time widgets to it by setCellWidget:
void MyClass::build()
{
for (int i=LOW; i<HIGH; i++)
{
QWidget *widget = new QWidget(myTable);
//
// ...
//
myTable->setCellWidget(i, 0, widget);
}
}
Then, I delete all items like below:
void MyClass::destroy()
{
for (int i = myTable->rowCount(); i >= 0; --i)
myTable->removeRow(i);
}
These methods call many times during long time. And myTable as the parent of those widgets will live along program's life-time.
Dose method destroy() release memory quite and automatically? Or I have to delete allocated widgets myself like below?
void MyClass::destroy2() // This maybe causes to crash !!
{
for (int i = myTable->rowCount(); i >= 0; --i)
{
QWidget *w = myTable->cellWidget(i, 0);
delete w;
myTable->removeRow(i);
}
}
Generally speaking, when in doubt or confused about how to use a class, consult the documentation that should've came with it. Fortunately, QTableWidget::setCellWidget() does in fact come with documentation:
void QTableWidget::setCellWidget ( int row, int column, QWidget * widget )
Sets the given widget to be displayed in the cell in the
given row and column, passing the ownership of the widget to the
table [emphasis mine]. If cell widget A is replaced with cell widget B, cell widget
A will be deleted. For example, in the code snippet below, the
QLineEdit object will be deleted.
setCellWidget(index, new QLineEdit);
...
setCellWidget(index, new QTextEdit);
After the call to myTable->setCellWidget(), the table now owns the widget you passed into it. That means that myTable is responsible for deleting the widgets you pass to setCellWidget(). You don't need to do delete w; when you're removing rows. Your first destroy() function should be sufficient.
From the documentation:
void QTableWidget::setCellWidget ( int row, int column, QWidget * widget )
Sets the given widget to be displayed in the cell in the given row and column, passing the ownership of the widget to the table.
If cell widget A is replaced with cell widget B, cell widget A will be deleted. For example, in the code snippet below, the QLineEdit object will be deleted.
That is, you don't clean up manually, as freeing of resources happens automatically down the Qt object tree, of which your widget has become a part.