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
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.
I have a Qt Widgets application, created and edited in Qt-Creator.
The main window (MainWindow class) has a menubar, with a button to open a small dialog (with text or widgets for settings).
To create a new "window" I open the "create new file" dialog in Qt-Creator and select Qt Designer Form Class, which creates the needed header, source, and ui files (dialogabout.h, dialogabout.cpp, dialogabout.ui).
If I follow along with the docs, I then open the QDialog like so:
QDialog * widget = new QDialog;
Ui::DialogAbout about_ui;
about_ui.setupUi(widget);
widget->exec();
This works, but if I modify the new dialog's instantiator to connect a pushbutton to the close signal, the connect statement (along with any other code there) is never reached.
DialogAbout::DialogAbout(QWidget *parent) :
QDialog(parent),
ui(new Ui::DialogAbout)
{
ui->setupUi(this);
qDebug() << "I'm alive!"; // No output happens
connect(ui->pushButton_close, SIGNAL(clicked(bool)), this, SIGNAL(please_close())); // No signal created on pushbutton click.
}
I suspect that this is because I haven't explicitly done widget = new DialogAbout(this). If I instead instantiate the new dialog this different way:
DialogAbout * newwindow;
newwindow = new DialogAbout(this);
newwindow->exec();
Then the connect statement and qDebug work.
My question is: what are the pitfalls of deviating from the documentation's recommended way to create dialogs? Is there a way to get this functionality with the prior instantiation method?
Note that DialogAbout is not the same as Ui::DialogAbout. Ui::DialogAbout is a class of build placed in the UI namespace, created automatically by uic. In your project, the name of this file should be "ui_dialogabout h".
class Ui_DialogAbout
{
public:
QPushButton *pushButton_close;
void setupUi(QDialog *DialogAbout)
{
...
} // setupUi
void retranslateUi(QDialog *DialogAbout)
{
...
} // retranslateUi
};
namespace Ui {
class DialogAbout: public Ui_DialogAbout {};
} // namespace Ui
Here you use a class QDialog and uses the Ui::DialogAbout to build a layout in it. Note that Ui::DialogAbout has the function to create the components in QDialog.
QDialog * widget = new QDialog;
Ui::DialogAbout about_ui;
about_ui.setupUi(widget);
widget->exec();
If you specialize QDialog for DialogAbout your code should look like this:
DialogAbout * widget = new DialogAbout();
Ui::DialogAbout about_ui;
about_ui.setupUi(widget);
widget->exec();
But as setupUi() is already within DialogAbout, you cannot call again, resulting in:
DialogAbout * widget = new DialogAbout();
widget->exec();
Firstly, look at the following image
In short, how to get the content(text) of TextEdit.
Notice: I have created the MDI subWindow(QWidget) and TextEdit dynamically by code not by Qt design.
The following is the code that wrote to create the MDI subWindow and TextEdit:
QWidget *widget = new QWidget(this);
QTextEdit *TextEdit = new QTextEdit(widget);
QMdiSubWindow *mdiWindows = ui->mdiArea->addSubWindow(widget);
mdiWindows->setGeometry(5, 5, 300, 250);
mdiWindows->setWindowTitle(finfo.baseName());
mdiWindows->setWindowState(Qt::WindowMaximized);
mdiWindows->layout()->addWidget(TextEdit);
mdiWindows->show();
And Now, how can I access to text property for TextEdit to get the content ?
QTextEdit::plainText() should get you there. Obviously, you'll need to keep a pointer to the QTextEdit that you've dynamically created around.
Getting the active subwindow is available by QMdiSubWindow * QMdiArea::activeSubWindow () const. You can also use the findChild to get a child with a specific name :
QTextEdit * textEdit = ui->mdiArea->activeSubWindow()->widget()->findChild<QTextEdit*>("myTextEdit");
But you should also set a name for your child widget when creating it :
QTextEdit *textEdit = new QTextEdit(widget);
textEdit->setObjectName("myTextEdit");
Also QList<T> QObject::findChildren ( const QString & name = QString() ) const returns all children with the given name that can be cast to type T. Omitting the name causes all object names to be matched. So you can access all children of the widget by :
QList<QTextEdit *> list = ui->mdiArea->activeSubWindow()->widget()->findChildren<QTextEdit *>();
foreach(QTextEdit *w, list)
{
...
}
In case anyone has the same question wrt PyQt/PySide (like me): to access the current widget in the active subwindow, use the following:
txtWidget=myQMdiArea.activeSubWindow().widget()
I'm trying to implement a custom widget hierarchy:
QMainWindow -> QFrame -> MyWidget -> QFrame -> MySubWidget
Here is how MyWidget class looks like:
class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget(QWidget *parent = 0, ...);
...
public slots:
void SlotFunction(int i);
...
private:
MySubWidget *sub_w;
QFrame *sub_frame;
...
}
If I try to create an MySubWidget during MyWidget constructor, then all MySubWidget elements are shown as intended:
MyWidget::MyWidget (...) : QWidget(parent) {
...
sub_frame = new QFrame(this);
...
sub_w = new MySubWidget(sub_frame); // commented out on a runtime test
}
But if I try to add subwidget during runtime, sub_frame remains blank. I.e. signal reaction:
void MyWidget::SlotFunction(int i) {
sub_w = new MySubWidget(sub_frame); // update, repaint, show and hide methods aren't helphul
}
I know this is an old question, but I was having a very similar issue and it turned out to be a lack of call to the QWidget::show(). Perhaps that was your problem as well?
My question here: Dynamically add instance inherited from QWidget
Cheers.
Are you reaching your function?
At the top of your function before making a new instance of MySubWidget put:
qDebug() << Q_FUNC_INFO;
Is the slot connected properly?
Qt will let you know if it is unable to connect a slot using a runtime warning. Look at the debug output that shows up in Qt Creator and it may mention a reason why the slot was never reached.
Is subframe visible?
If the parent of your object isn't visible, then showing or hiding the child object will only affect it when the parent is shown.
Hope that helps. Good luck.
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();
// ...
}