Qt Drag&Drop with own widgets? - c++

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();
// ...
}

Related

Do you need to delete widget after removeItemWidget from QTreeWidget?

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.

Qt: Pass QGraphicsSceneContextMenuEvent from QGraphicsView

I have derived from both QGraphicsView and QGraphicsRectItem. I overloaded the contextMenuEvent on both classes to provide popup menus. I want the QGraphicsView context menu when you click on white space the the QGraphicsItem popup menu when you click on an item.
At first implementation, I got the QGraphicsView popup no matter where I clicked. So I modified the contextMenuEvent as follows:
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
if (QGraphicsItem *item = itemAt(event->pos())) {
MyRect* rect = dynamic_cast<MyRect*>(item);
QGraphicsSceneContextMenuEvent* context_event = dynamic_cast<QGraphicsSceneContextMenuEvent*>(event);
if (rect && context_event)
rect->contextMenuEvent(context_event);
}
else {
QMenu menu;
... create the QGraphicsView popup menu
}
}
The dynamic_cast for the QGraphicsSceneContextMenuEvent fails so I never call the contextMenuEvent for the rect. It won't compile if I just try to pass the event to the rect->contextMenu(), so I tried the cast.
What is the right way to do this?
This is a learning project to just create/move/rotate/delete 2D shapes using Qt. If someone wants to look at the whole thing, let me know.
OK, so I figured it out. Just make sure to pass the event through the base class method. Simple! This also works for the mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent functions.
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
// if the event is on a GGraphicsItem just pass the event along
if (itemAt(event->pos())) {
QGraphicsView::contextMenuEvent(event);
}
else
{
QMenu menu;
... create popup for the CustomGraphicsView

Qt Widget class' parent is always NULL

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

QDialog not positioned correctly

I have a problem with QDialog which is not displayed centered on the parent window.
The following snippet explains it:
void MyWidget::showDialog() {
QObject* p = parent();
while (p!=0) {
qDebug() << p;
p = p->parent();
}
qDebug() << QApplication::activeWindow();
MyClassDerivedFromQDialog dlg( this );
if ( dlg.exec() != dlg.Accepted ) {
return;
}
... do something
}
The output on qDebug is the following
QSplitter(0x2d89930, name = "splitter")
MyWidget(0x2d89670, name = "widget")
MainWindow(0x27ef20, name = "application")
MainWindow(0x27ef20, name = "application")
Executing my example opens the dialog somewhere on the screen. Passing QApplication::activeWindow() as a parent to the dialogs constructor results in a dialog centered on the main window. So why is that and how to track down the problem?
I found that this behavior is related to the timing of the dialog creation.
If you create a QDialog (or a derived class) before the dialog parent is shown (e.g., in the parent constructor), the dialog is displayed at a non-predictable location (or at least, not where you'd expect it to show).
However, if the dialog is created after the parent is displayed, then you get the expected behavior.
For example, if you have a button invoking your dialog. Both the button and the dialog are children of the same widget, so the dialog parent is the same as the button parent. In this case, it is advised to delay the dialog creation until the button is clicked, not before that.
This way, you ensure the dialog is created only after the parent is shown.
I am not sure if understand your problem.
QDialogs are always centered on the widget you pass as parent. This is by design. So if you pass "activeWindow()" as parent it is centered on the active window. If you pass "this" as a parent the dialog is centered above MyWidget.
In which way does your dialog not respect these rules?
The Dialog class is instantiated via
MyClassDerivedFromQDialog::MyClassDerivedFromQDialog(QWidget *parent)
: QDialog(parent),
ui(new Ui::MyClassDerivedFromQDialog)
{
ui->setupUi(this);
//remove the ? button in titlebar
Qt::WindowFlags flags = windowFlags();
Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint;
flags = flags & (~helpFlag);
setWindowFlags(flags);
}
And i always use it as in the showDialog function in the initial post. sometimes it works... And no, i do not have two MyWigets which are parents of each other.

Is there a way to know what activated QAction?

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).