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.
Related
I am new to QT. I started creating a TODO app and I want to somehow connect my PushButtons that are placed in vector with CheckBoxes that are also placed in a different vector.
std::vector <QPushButton*> buttons;
std::vector <QCheckBox*> checks;
I thought that the best way to do that will be to make a for loop connecting every element of mentioned vectors
Something like:
for(int i=0; i<buttons.size(); ++i){
connect(buttons[i], SIGNAL(???), checks[i], SLOT(???));
}
But idea is the only thing that I have. I tried putting different things into SIGNAL() and SLOT() but none of them worked. By "none of them worked" I mean the fact that when button is clicked nothing happens. Program is normally compiled without any error.
What about just clicked(bool) for SIGNAL and toggle() for SLOT?
Something like that:
connect(pushButton, SIGNAL(clicked(bool)), checkBox, SLOT(toggle()));
Works for me - and you can store the widgets directly in a std::list: that avoids the need to mess with manual memory management. Let the libraries do it for you.
#include <QtWidgets>
#include <list>
int main(int argc, char **argv) {
QApplication app{argc, argv};
QWidget win;
QGridLayout layout{&win};
std::list<QPushButton> buttons;
std::list<QCheckBox> checkboxes;
QPushButton addButton{"Add"};
layout.addWidget(&addButton, 0, 0, 2, 1);
auto const clicked = &QAbstractButton::clicked;
auto const toggle = &QAbstractButton::toggle;
auto const add = [&,clicked,toggle]{
int const col = layout.columnCount();
auto const text = QString::number(col);
auto *button = &(buttons.emplace_back(text), buttons.back()); //C++11, not 14
auto *checkbox = &(checkboxes.emplace_back(text), checkboxes.back());
layout.addWidget(button, 0, col);
layout.addWidget(checkbox, 1, col);
QObject::connect(button, clicked, checkbox, toggle);
};
add();
QObject::connect(&addButton, clicked, add);
win.show();
return app.exec();
}
With Qt-5 you can now use lambda functions as slots (see connect version 5)
You can also do away with the need for the SIGNAL macro, and instead use member function pointers.
QObject::connect(buttons[i], &QPushButton::clicked, [=]
{
// toggle the check state
checks[i]->setChecked(!checks[i]->isChecked());
});
The first two parameters are a pointer to an object, and a member function pointer
buttons[i] is of type QPushButton*
&QPushButton::clicked is a member function pointer of the signal you want to connect to
The second parameter is a C++11 lambda, which captures checked and i by value, and then sets the QCheckBox checked state to the inverse of its previous value
I have a custom QWidget class called VideoWidget that looks something like this:
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent)
{
ClickableLabel *smallRed = new ClickableLabel(this)
//...
QObject::connect(smallRed,SIGNAL(clicked()),this,SLOT(removeVideo()));
}
void VideoWidget::removeVideo(){
//...remove a file
MainWindow* myParent = qobject_cast<MainWindow*>(this->parent());
QListWidget* myList = myParent->getList();
QListWidgetItem* item = myList->currentItem();
myList->removeItemWidget(item);
}
The VideoWidget widgets are created in my MainWindow class with the argument this and then added to a QListWidget. When clicking on the smallRed label in my VideoWidget I want my program to delete a file and then run the code to remove the widget from my QListWidget in my MainWindow. My problem is that the line MainWindow* myParent = qobject_cast<MainWindow*>(this->parent()); always returns NULL and I don't understand why. Any help is much appreciated.
See this code, I think you have something similar:
for(int r=0;r<2;r++)
{
QListWidgetItem* lwi = new QListWidgetItem;
ui->listWidget->addItem(lwi);
ui->listWidget->setItemWidget(lwi, new QCheckBox(QString("checkBox%1").arg(r),this));
qDebug()
<< ui->listWidget->itemWidget(lwi)->parent()
<< ui->listWidget->itemWidget(lwi)->parent()->parent()
<< ui->listWidget->itemWidget(lwi)->parent()->parent()->parent()
<< ui->listWidget->itemWidget(lwi)->parent()->parent()->parent()->parent();
}
As you can see I set this as a parent, but my first parent is qt_scrollarea_viewport too, because Qt reparent your widget. Output of my code is:
QWidget(0x27c64260, name = "qt_scrollarea_viewport")
QListWidget(0x27c64240, name = "listWidget")
QWidget(0x264bedd8, name = "centralWidget")
MainWindow(0x28fdcc, name = "MainWindow")
If you have same structure then use a few parent() calling
Yes, it is not very beautiful but as far as I know Qt has no something like findParent, only findChildren
As thuga suggested it works but it is not very good, your VideoWidget should not know about MainWindow. You should use signals and slots. Just emit signal from VideoWidget and catch this signal in MainWindow (write special slot and remove your item in this slot). It will be better than this magic with parent().
Widgets in QT are only automatically parented if you place them into a layout. Otherwise, if you create them without passing a parent pointer, they will be created without a parent, and will become a top-level window.
http://qt-project.org/doc/qt-4.8/qobject.html#QObject
How would I take the user input from the a spinbox and use that as a value? In other words if I wanted to store the input from the QSpinBox into a variable how would I go about doing this. Im really new at Qt GUI so any input would be greatly appreciated.
To react to GUI elements in Qt, you connect to the signals that those elements give off. Also if you have a pointer to the instance of it, you can query and change its states and properties.
Here is a quick example of what you are looking for
#include <QApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QSpinBox>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// The widget, contains a layout
QWidget * w;
w = new QWidget;
// The layout arranges and holds
// all the children of the widget
QVBoxLayout * vbox;
vbox = new QVBoxLayout;
// The user input element, the spinbox!
QSpinBox * spinbox;
spinbox = new QSpinBox();
spinbox->setValue(5);// example of using a pointer to edit its states
// now add it to the layout
vbox->addWidget(spinbox);
// add in an element to connect to,
// the infamous QLabel
QLabel * label;
label = new QLabel("spinbox output");
// add it also to the layout
vbox->addWidget(label);
// connection can happen anytime as long as the two elements
// are not null!
// This connection takes advantage of signals and slots
// and making their connection at runtime.
// if a connect call fails you should be able to see why in the
// application output.
QObject::connect(spinbox, SIGNAL(valueChanged(QString)),
label, SLOT(setText(QString)));
// associate the layout to the widget
w->setLayout(vbox);
// make the widget appear!
w->show();
return a.exec();
}
I usually put most of the initializing and connecting of GUI elements into the constructor or a method of the main QWidget or the QMainWindow. I often take the signal from a GUI input element, like a spinbox and I connect it to a custom slot defined on my subclassed QWidget. Then if I want to display it with the input value differently or increase the output by 2, I can do so easily.
// in the constructor of my Widget class
// after spinbox has been initialized
QObject(m_spinbox, SIGNAL(valueChanged(int)),
this, SLOT(on_spinboxValueChanged(int)));
void Widget::on_spinboxValueChanged(int i)
{
// here m_label is a member variable of the Widget class
m_label->setText(QString::number(i + 2));
// if accessing the value in this function is inconvenient, you can always
// use a member variable pointer to it to get its stored value.
// for example:
int j = m_spinbox->value();
qDebug() << "Spinbox value" << j;
}
Identical things can be done in QML and Qt Quick, and for many people it is easier and more intuitive because of how close it is to javascript and css.
Also Qt Creator has a tool for generating forms, and it provides another way to create your widgets with layouts and then when you access your elements you do it through a ui variable.
Also the Qt docs and examples are awesome. Taking time to learn them and read up on them is well worth the effort.
Hope that helps.
I was able to create a context menu for my QTreeWidget as below
QMenu* pContextMenu = new QMenu(this)
QTreeWidget* pTreeWidget = new QTreeWidget();
QAction* pOpenFile = new QAction(tr("Open A File"), pContextMenu);
pTreeWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
pTreeWidget->addAction(pOpenFile);
But I want a different popup for a branch than a leaf. How do I assign a different popup depending on the type of widgetitem clicked?
My tree:
Branch1 <-- Popup1
Leaf1
Leaf2 <-- Popup2
Branch2
Branch3
Leaf1
QWidget::actions() is not listed as virtual. Else I would have derived my own class from QTreeWidget & reimplemented actions().
Method 1: Override QTreeWidget
A context menu assigned to the QTreeWidget itself will not let you have different context menus for different items, as you have discovered.
As the Qt item views don't have special API for context menus, you have to implement this yourself. Fortunately, it's not very difficult; you just need to:
Create a subclass of QTreeWidget.
Connect the customContextMenuRequested(const QPoint&) signal to a custom slot.
Display the desired context menu.
I've posted a complete working example. Some details to note include:
QTreeWidgetItem provides a handy type property to let you identify items easily without casting, string parsing, or other awkward/fragile methods.
Custom QTreeWidgetItem type values should be greater than or equal to QTreeWidgetItem::UserType.
When displaying a context menu, you must pass a global position to exec(). To correctly map from a position in the widget's space in the slot, you must use the item's viewport widget.
Method 2: Override QItemDelegate (and QTreeWidget ...)
An alternate method is to implement your own QAbstractItemDelegate subclass, and assign it to your tree widget. In your item delegate, you can override editorEvent() to handle mouse presses in the same way.
Although this approach frres is actually more in line with Qt's item view API design, there are a few key disadvantages to this approach:
Item delegates use QModelIndex objects to represent items. To convert to a QTreeWidgetItem, you must use the QTreeWidget::itemFromIndex() method. Unfortunately, this is protected, so it will actually require you to subclass QTreeWidget anyway to provide this API for your delegate. This adds some more boilerplate complexity to your code.
The editorEvent() hook is invoked before the item view handles the event. This means that you can't easily display a context menu and allow the default behavior at the same time (such as selecting the item that was right-clicked).
Since the editorEvent() handler sees all kinds of different events, you must be even more careful to handle them correctly. You must also be careful not to let this monolithic handler grow out of control if your behaviors are complicated.
The core code is very similar, but again, there's a bit more boilerplate. I've posted an example of this approach, as well.
I've modified jmk's code slightly to show how this can be done with
setContextMenuPolicy(Qt::CustomContextMenu) and customContextMenuRequested(const QPoint&) signal.
mytreewidget.h
#include <QTreeWidget>
static const int ItemType1 = QTreeWidgetItem::UserType + 1;
static const int ItemType2 = QTreeWidgetItem::UserType + 2;
class MyTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
MyTreeWidget(QWidget *parent = 0);
private slots:
void showContextMenu(const QPoint &pos);
};
mytreewidget.cpp:
#include "mytreewidget.h"
#include <QMenu>
#include <QTreeWidgetItem>
MyTreeWidget::MyTreeWidget(QWidget *parent)
: QTreeWidget(parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
SLOT(showContextMenu(const QPoint&)));
}
void MyTreeWidget::showContextMenu(const QPoint &pos)
{
QMenu menu;
QTreeWidgetItem* item = itemAt(pos);
switch (item->type()) {
case ItemType1:
menu.addAction("This is a type 1");
break;
case ItemType2:
menu.addAction("This is a type 2");
break;
}
menu.exec(mapToGlobal(pos));
}
main.cpp:
#include <QApplication>
#include "mytreewidget.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyTreeWidget w;
// Add test items.
w.addTopLevelItem(new QTreeWidgetItem(QStringList("A (type 1)"),
ItemType1));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("B (type 1)"),
ItemType1));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("C (type 2)"),
ItemType2));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("D (type 2)"),
ItemType2));
w.show();
return app.exec();
}
I created a little widget on my own, including a QProgressBar and a QLabel in a QVBoxLayout. It has also a function which returns the text of the label (self-created).
Now in my MainWindow I have two other QHBoxLayouts and I want to drag and drop my widget from one to another. It also works when I click on the little free space between the QLabel and the QProgressBar. But when I click on one of them directly, the application crashed and burned painfully.
I also know where it fails. My mousePressEvent looks like this:
void DragDrop::mousePressEvent(QMouseEvent *event) {
// !!!!---- make sure ONLY MyWidgets are here, else: CRASH ----!!!!
MyWidget *child = static_cast<MyWidget*>(childAt(event->pos()));
if (!child)
return;
qDebug() << child->returnLabelText();
...
}
So when I click on the ProgressBar, it will cast the ProgressBar, not my own widget. And because the QProgressBar doesn't have a function like returnLabelText() (but my widget does) it fails.
What is a better method to get my widget?
QWidget::childAt(int,int) returns the child widget, not the parent widget. In your case, it returns the QProgressBar. You then try to cast into a MyWidget, which it is not. What you are looking for is for the parent of the QProgressBar (or QLabel).
static_cast does not verify the type of the object you are trying to cast, and will always yield a non-null pointer even if the cast is invalid. What you are looking for here is dynamic_cast, which will return NULL if the object is not of the type you are looking for. Since you are looking for the parent (or an ancestor) of the widget being clicked, you could use a loop to iterate through the clicked widget's ancestry to find the instance of MyWidget you are looking for.
void DragDrop::mousePressEvent(QMouseEvent *event) {
QWidget *widget = childAt(event->pos());
do {
MyWidget *myWidget = dynamic_cast<MyWidget*>(widget);
widget = widget->parentWidget();
} while (myWidget == NULL && widget != NULL)
if (myWidget == NULL)
return;
qDebug() << myWidget->returnLabelText();
// ...
}