PDA

View Full Version : QTreeWidget: lost click after drag and drop



cresta
29th December 2010, 16:04
Hi!
I have a problem with Drag and Drop in my application. I have added Drag and Drop to my QTreeWidget subclass (class MyTreeWidget : public QTreeWidget) and it works almost fine. The only problem I experience is - the first click on the MyTreeWidget after drag and drop action is lost. I click (after drag and drop action) on a tree element but nothing happens. The element must become selected, but it doesn't.

If I use QListWidget instead of QTreeWidget then everything is alright.

I have wrote simplest example to demonstrate this issue:



// main.cpp
#include <QtGui>
#include "MyTreeWidget.h"

int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyTreeWidget w;
w.show();
return app.exec();
}




// MyTreeWidget.h
#ifndef PROJECTLISTWIDGET_H
#define PROJECTLISTWIDGET_H

#include <QTreeWidget>

class MyTreeWidget : public QTreeWidget
{
Q_OBJECT

public:
MyTreeWidget(QWidget *parent = 0);

protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);

private:
void performDrag();

QPoint startPos;
};

#endif




// MyTreeWidget.cpp
#include <QtGui>
#include "MyTreeWidget.h"

MyTreeWidget::MyTreeWidget(QWidget *parent) : QTreeWidget(parent) {
setAcceptDrops(true);

(new QTreeWidgetItem(this))->setText(0, "1-one");
(new QTreeWidgetItem(this))->setText(0, "2-two");
(new QTreeWidgetItem(this))->setText(0, "3-three");
(new QTreeWidgetItem(this))->setText(0, "4-four");
(new QTreeWidgetItem(this))->setText(0, "5-five");
(new QTreeWidgetItem(this))->setText(0, "6-six");
(new QTreeWidgetItem(this))->setText(0, "7-seven");
}

void MyTreeWidget::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton)
startPos = e->pos();
QTreeWidget::mousePressEvent(e);
}

void MyTreeWidget::mouseMoveEvent(QMouseEvent *e) {
if (e->buttons() & Qt::LeftButton) {
int distance = (e->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
performDrag();
}
QTreeWidget::mouseMoveEvent(e);
}

void MyTreeWidget::dragEnterEvent(QDragEnterEvent *e) {
}

void MyTreeWidget::dragMoveEvent(QDragMoveEvent *event) {
}

void MyTreeWidget::dropEvent(QDropEvent *event) {
}

void MyTreeWidget::performDrag() {
QTreeWidgetItem *item = currentItem();
if (item) {
QMimeData *mimeData = new QMimeData;
mimeData->setText(item->text(0));
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
delete item;
}
}


I also looked in what is happening with a debugger and I have found out that QTreeWidget state() member function returns DragSelectingState when I click MyTreeWidget the first time after drag and drop action. Looks like this prevents element from being selected. So I have wrote this simple dirty-fix:



void MyTreeWidget::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton)
startPos = e->pos();
if (DragSelectingState == state()) // this two lines
setState(NoState); // fix the problem
QTreeWidget::mousePressEvent(e);
}



But I would like to know the reason of this behavior. Is it my bug (incorrectly implemented Drag and Drop) or Qt's one. I can not reproduce this behavior with QListWidget. So it looks like it is Qt's bug.

Any ideas would be greatly appreciated!

Thanks in advance!

johnc
29th December 2010, 16:28
Your drag and drop implementation is incomplete. The body of your dragEnter, dropMove, and dragDrop methods are empty. I'm not sure what will happen here, but it would be a good bet that at a minimum, not calling the base class implementation of these will leave the drag operation open. When you start a drag event, the dragEnter method normally accepts or ignores the event, which will then "hand-off" to the move and potentially the drop methods, which also accept or ignore the event. Your "fix" appears to be patching this over, but not implementing this correctly. If you are content with the simplicity of your implementation, and don't need to perform a real "drop", I would remove the empty methods and allow the calls to pass through to the base implementations.

cresta
29th December 2010, 18:53
Thank you for you answer, johnc!


Your drag and drop implementation is incomplete. The body of your dragEnter, dropMove, and dragDrop methods are empty.
If I understand correctly, these methods are allowed to be empty. Anyway, I tried different variants (not to override them, override and accept the event in these methods, override and ignore the event in these methods) but it didn't helped - the first click after drag and drop action is lost.

Moreover, if you replace QTreeWidget with QListWidget in this minimalist example and build it, then the first click after Drag and Drop action will not be lost. So I think that the reason of this misbehavior is not in the empty dragEnter, dropMove, and dragDrop methods.


I'm not sure what will happen here, but it would be a good bet that at a minimum, not calling the base class implementation of these will leave the drag operation open. When you start a drag event, the dragEnter method normally accepts or ignores the event, which will then "hand-off" to the move and potentially the drop methods, which also accept or ignore the event. Your "fix" appears to be patching this over, but not implementing this correctly.
They are not supposed to call base class implementation... No?



If you are content with the simplicity of your implementation, and don't need to perform a real "drop", I would remove the empty methods and allow the calls to pass through to the base implementations.
Yes, I do not need to perform a real drop. I just want to find the reason of the lost click. I tried to remove those empty methods, but it didn't helped(

johnc
29th December 2010, 21:47
Have you tried setting a breakpoint in the mouse press event button to see if it is even getting called at the point you are missing the click? If you don't hit the breakpoint, then there is a decent chance it's a Qt bug, but I would still guess the event is somehow getting "swallowed" in relation to the dragEnter, dragMove, or dragDrop methods.

Other than that, I'm not really sure what to tell you. As far as leaving methods empty, it's not a very good coding practice. I'm not sure if you were asking, but leaving the body of the function empty does not call the base class method - you have to do this explicitly. If you don't implement the method, e.g. don't provide a function body, the base class method will get called, which is probably what you want in this case anyway. Normally when implementing a true drag and drop, you can check the dragged item and decide whether or not to accept or ignore the event. In the drag enter method, if you ignore it, you will see the circle with a line through it indicating you can't drop in the widget. if you accept it, then the drag move event will be called, and if you drop, the same thing goes with the accept/ignore. I should also mention, there is a difference between accept and acceptProposedAction, which is what I was actually referring to here (sorry, going from memory), which is available as a method on QDropEvent, QDragMoveEvent, and QDragEnterEvent. That could possibly be related as well...? If you haven't already done so, take a look at the drag and drop page in the QtAssistant. Following the example there I have never had issues with drag and drop.

Good luck...

Wong
30th December 2010, 05:56
Are u ok? I lost click too, after drag:(


void ProjectTreeWidget::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
startPos = event->pos();
}
QTreeWidget::mousePressEvent(event);
qDebug()<<"Click";
}

But debug show "Click" twice! I don't know why

cresta
30th December 2010, 07:44
Have you tried setting a breakpoint in the mouse press event button to see if it is even getting called at the point you are missing the click?
Yes, sure - it is getting called at the point I'm missing the click. Below is a call stack:

QtGuid4.dll!QTreeViewPrivate::expandOrCollapseItem AtPos(pos={...})
QtGuid4.dll!QTreeView::mousePressEvent(event=0x001 2d734)
drag_and_drop_lost_click.exe!MyTreeWidget::mousePr essEvent(e=0x0012d734)
QtGuid4.dll!QWidget::event(event=0x0012d734)
QtGuid4.dll!QFrame::event(e=0x0012d734)
QtGuid4.dll!QAbstractScrollArea::viewportEvent(e=0 x0012d734)
QtGuid4.dll!QAbstractItemView::viewportEvent(event =0x0012d734)
QtGuid4.dll!QTreeView::viewportEvent(event=0x0012d 734)
QtGuid4.dll!QAbstractScrollAreaPrivate::viewportEv ent(event=0x0012d734)
QtGuid4.dll!QAbstractScrollAreaFilter::eventFilter (o=0x00a00bf8, e=0x0012d734)
QtCored4.dll!QCoreApplicationPrivate::sendThroughO bjectEventFilters(receiver=0x00a00bf8, event=0x0012d734)
QtGuid4.dll!QApplicationPrivate::notify_helper(rec eiver=0x00a00bf8, e=0x0012d734)
QtGuid4.dll!QApplication::notify(receiver=0x00a00b f8, e=0x0012d734)
QtCored4.dll!QCoreApplication::notifyInternal(rece iver=0x00a00bf8, event=0x0012d734)
QtCored4.dll!QCoreApplication::sendSpontaneousEven t(receiver=0x00a00bf8, event=0x0012d734)
QtGuid4.dll!QApplicationPrivate::sendMouseEvent(re ceiver=0x00a00bf8, event=0x0012d734, alienWidget=0x00a00bf8, nativeWidget=0x0012febc, buttonDown=0x65cc7fbc, lastMouseReceiver={...}, spontaneous=true)
QtGuid4.dll!QETWidget::translateMouseEvent(msg={.. .})
QtGuid4.dll!QtWndProc(hwnd=0x0002067a, message=513, wParam=1, lParam=5439533)

In method


bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
{
Q_Q(QTreeView);
// we want to handle mousePress in EditingState (persistent editors)
if ((state != QAbstractItemView::NoState
&& state != QAbstractItemView::EditingState)
|| !viewport->rect().contains(pos))
return true; // here execution flow goes out of this method

value of data-member state is equal to QAbstractItemView :: DragSelectingState. So control flow goes out of this method with return value equal to 'true'. My dirty-fix simply sets this data-member to NoState:


void MyTreeWidget::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton)
startPos = e->pos();
if (DragSelectingState == state()) // this two lines
setState(NoState); // fix the problem
QTreeWidget::mousePressEvent(e);
}

And then everything seems to be working correctly.

Added after 6 minutes:


Are u ok? I lost click too, after drag:(
But debug show "Click" twice! I don't know why
Hi Wong! Thank you for your answer!
I can't reproduce twice-click... When I click the tree item after dragging, then MyTreeWidget::mousePressEvent(QMouseEvent *e) method is called once. From this method base class implementation QTreeWidget::mousePressEvent() is called. But it seems that QTreeWidget::mousePressEvent() call doesn't perform all the necessary operations because of a premature return.

Wong
30th December 2010, 08:00
Yes, sure - it is getting called at the point I'm missing the click. Below is a call stack:

QtGuid4.dll!QTreeViewPrivate::expandOrCollapseItem AtPos(pos={...})
QtGuid4.dll!QTreeView::mousePressEvent(event=0x001 2d734)
drag_and_drop_lost_click.exe!MyTreeWidget::mousePr essEvent(e=0x0012d734)
QtGuid4.dll!QWidget::event(event=0x0012d734)
QtGuid4.dll!QFrame::event(e=0x0012d734)
QtGuid4.dll!QAbstractScrollArea::viewportEvent(e=0 x0012d734)
QtGuid4.dll!QAbstractItemView::viewportEvent(event =0x0012d734)
QtGuid4.dll!QTreeView::viewportEvent(event=0x0012d 734)
QtGuid4.dll!QAbstractScrollAreaPrivate::viewportEv ent(event=0x0012d734)
QtGuid4.dll!QAbstractScrollAreaFilter::eventFilter (o=0x00a00bf8, e=0x0012d734)
QtCored4.dll!QCoreApplicationPrivate::sendThroughO bjectEventFilters(receiver=0x00a00bf8, event=0x0012d734)
QtGuid4.dll!QApplicationPrivate::notify_helper(rec eiver=0x00a00bf8, e=0x0012d734)
QtGuid4.dll!QApplication::notify(receiver=0x00a00b f8, e=0x0012d734)
QtCored4.dll!QCoreApplication::notifyInternal(rece iver=0x00a00bf8, event=0x0012d734)
QtCored4.dll!QCoreApplication::sendSpontaneousEven t(receiver=0x00a00bf8, event=0x0012d734)
QtGuid4.dll!QApplicationPrivate::sendMouseEvent(re ceiver=0x00a00bf8, event=0x0012d734, alienWidget=0x00a00bf8, nativeWidget=0x0012febc, buttonDown=0x65cc7fbc, lastMouseReceiver={...}, spontaneous=true)
QtGuid4.dll!QETWidget::translateMouseEvent(msg={.. .})
QtGuid4.dll!QtWndProc(hwnd=0x0002067a, message=513, wParam=1, lParam=5439533)

In method


bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos)
{
Q_Q(QTreeView);
// we want to handle mousePress in EditingState (persistent editors)
if ((state != QAbstractItemView::NoState
&& state != QAbstractItemView::EditingState)
|| !viewport->rect().contains(pos))
return true; // here execution flow goes out of this method

value of data-member state is equal to QAbstractItemView :: DragSelectingState. So control flow goes out of this method with return value equal to 'true'. My dirty-fix simply sets this data-member to NoState:


void MyTreeWidget::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton)
startPos = e->pos();
if (DragSelectingState == state()) // this two lines
setState(NoState); // fix the problem
QTreeWidget::mousePressEvent(e);
}

And then everything seems to be working correctly.

Added after 6 minutes:


Hi Wong! Thank you for your answer!
I can't reproduce twice-click... When I click the tree item after dragging, then MyTreeWidget::mousePressEvent(QMouseEvent *e) method is called once. From this method base class implementation QTreeWidget::mousePressEvent() is called. But it seems that QTreeWidget::mousePressEvent() call doesn't perform all the necessary operations because of a premature return.

Thank you very much! It's ok after setState(NoState).
It beset me long time. Thanks again!

cresta
30th December 2010, 08:12
Thank you very much! It's ok after setState(NoState).
It beset me long time. Thanks again!
No at all!:)
But I would like to know is it bug in Qt or in my drag and drop implementation. Any ideas and considerations are welcome!

cresta
30th December 2010, 12:40
I have replaced QTreeWidget with QListWidget in my minimalist example (the thread's first post):



// main.cpp
#include <QtGui>
#include "MyListWidget.h"

int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyListWidget w;
w.show();
return app.exec();
}




// MyListWidget.h
#ifndef MYLISTWIDGET_H
#define MYLISTWIDGET_H

#include <QListWidget>

class MyListWidget : public QListWidget
{

public:
MyListWidget(QWidget *parent = 0);

protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
// void dragEnterEvent(QDragEnterEvent *event);
// void dragMoveEvent(QDragMoveEvent *event);
// void dropEvent(QDropEvent *event);

private:
void performDrag();

QPoint startPos;
};

#endif




// MyListWidget.cpp
#include <QtGui>
#include "MyListWidget.h"

MyListWidget::MyListWidget(QWidget *parent) : QListWidget(parent) {
setAcceptDrops(true);

addItem("1-one");
addItem("2-two");
addItem("3-three");
addItem("4-four");
addItem("5-five");
addItem("6-six");
addItem("7-seven");
}

void MyListWidget::mousePressEvent(QMouseEvent *e) {
if (e->button() == Qt::LeftButton)
startPos = e->pos();
QListWidget::mousePressEvent(e);
}

void MyListWidget::mouseMoveEvent(QMouseEvent *e) {
if (e->buttons() & Qt::LeftButton) {
int distance = (e->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
performDrag();
}
QListWidget::mouseMoveEvent(e);
}

// void MyListWidget::dragEnterEvent(QDragEnterEvent *e) {
// }
//
// void MyListWidget::dragMoveEvent(QDragMoveEvent *event) {
// }
//
// void MyListWidget::dropEvent(QDropEvent *event) {
// }

void MyListWidget::performDrag() {
QListWidgetItem *item = currentItem();
if (item) {
QMimeData *mimeData = new QMimeData;
mimeData->setText(item->text());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
delete item;
}
}


This Drad and Drop example with QListWidget works fine without any dirty-fixes - no lost clicks!

So I incline to the opinion, that QTreeWidget drag and drop issue with the lost click described above is the Qt's bag, not mine.

Wong
30th December 2010, 17:18
In Qt src, QTreeView reimplement mousePressEvent


void QTreeView::mousePressEvent(QMouseEvent *event)
{
Q_D(QTreeView);
bool handled = false;
if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseT ype, 0, this) == QEvent::MouseButtonPress)
handled = d->expandOrCollapseItemAtPos(event->pos());
if (!handled && d->itemDecorationAt(event->pos()) == -1)
QAbstractItemView::mousePressEvent(event);
}


And QListView don't reimplement it. I’m not really sure it has bug. I know very little about d-pointer

cresta
30th December 2010, 17:52
In Qt src, QTreeView reimplement mousePressEvent
And QListView don't reimplement it. I’m not really sure it has bug. I know very little about d-pointer

If I understand you right, you think that the lost click issue isn't a bug of QTreeWidget? Then what do you think about my "ditry-fix":


if (DragSelectingState == state())
setState(NoState);

Wong
31st December 2010, 03:40
Haha, I find other interesting matters !:)

It's a happenchance, I forgot to write mouseMoveEvent when reimplement
But it's ok after drag!


void ProjectTreeWidget::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
int distance = (event->pos() - startPos).manhattanLength();
if(distance >= QApplication::startDragDistance())
{
performDrag();
}
}
// QTreeWidget::mouseMoveEvent(event); //Don't lost click after drag
}


If add "QTreeWidget::mouseMoveEvent(event)", lost click
I think you are right, it like a bug of QTreeWidget

cresta
31st December 2010, 12:31
I think you are right, it like a bug of QTreeWidget

Vote for this bug report Mouse click doesn't select item in QTreeWidget after Drag and Drop (http://bugreports.qt.nokia.com/browse/QTBUG-16379) then! :)