PDA

View Full Version : select QGraphicsItems by mouseclick



Shawn
28th July 2007, 06:39
I wana to select QGraphicsItems by mouseclick, and then generate new QGraphicsItems as children of the one being clicked. It's supposed to be like this:

I used to look for signals like isSelected or something, of course with no luck. Since QGraphicsItem even doesn't inherit QObject.
Now I have no idea of how it could be implemented. Can anyone help?
Many thanks in advance!

Shawn
28th July 2007, 06:45
BTW, the rectangle is drawn this way:

void test::drawRect(QGraphicsScene &scene, int x, int y, int m, int n)
{
QAbstractGraphicsShapeItem *i = scene.addRect( QRectF(x, y, m, n));
i->setFlag(QGraphicsItem::ItemIsSelectable);
i->setBrush( QColor(233,104,21) );
i->setPen( QPen(QColor(246,153,90), 3));
i->setZValue(2);
}

Gopala Krishna
28th July 2007, 07:31
You can use, QGraphicsScene::selectionChanged() signal and use QGraphicsScene::selectedItems() to get the selected item.

One more better way is to subclass the required QGraphicsItems and provide the required functionality using mouse*Event handlers. Or may be implement QGraphicsItem::itemChange() to intercept selection and then take required action.

Finally if you dont want to reimplement each and every shape items, you can create a custom item , implement the QGraphicsItem::sceneEventFilter() method to do appropriate things on mouse click and install this as event filter. (see QGraphicsItem::installSceneEventFilter( ) )

Shawn
30th July 2007, 02:45
Gopala Krishna, Thanks for your quick reply!

I tried the first way u gave:

test::test(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
......
ui.graphicsView_3->setScene(&scene3);
QObject::connect(&scene3, SIGNAL(selectionChanged()), this, SLOT(testslots()));
}


void test::drawText(QGraphicsScene &scene, QString *str, int x, int y)
{
QGraphicsTextItem* i = scene.addText(*str);
i->setPos(x, y);
i->setZValue(3);
}

void test::testslots()
{
QString tmp = "OK";
drawText(scene3, &tmp, 0, 0);
}

But after I change the selection, I mean I select the items one by another, nothing happens !:confused:
Is there anything wrong ?

Gopala Krishna
30th July 2007, 05:22
Is the slot being called atleast once ? I am asking this because the docs for selectionChanged() signal quote

QGraphicsScene emits this signal only once for group selection operations. For example, if you set a selection area, select or unselect a QGraphicsItemGroup, or if you add or remove from the scene a parent item that contains several selected items, selectionChanged() is emitted only once after the operation has completed (instead of once for each item).

A better way to verify if the slot was called is to use qDebug() in the slot.

Gopala Krishna
30th July 2007, 06:06
Checkout this. This example works for me

object.h

#include <QObject>
#include <QMessageBox>
class Receiver : public QObject
{
Q_OBJECT
public:
Receiver() : QObject()
{}

public slots:
void test()
{
QMessageBox::information(0, "This", "Is working");
}
};


main.cpp

#include <QtGui>
#include "object.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene sc(0,0,1055,768);
Receiver r;
QGraphicsView v(&sc);
QObject::connect(&sc, SIGNAL(selectionChanged()), &r, SLOT(test()));
QGraphicsLineItem *li = sc.addLine(10,25,326,230,QPen(Qt::green));
QGraphicsRectItem *ri = sc.addRect(50,23,50,50,QPen(Qt::red));
li->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
ri->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
v.show();
return app.exec();
}

Shawn
30th July 2007, 06:52
I checked the reference and found that the selectionChanged() signal is introduced since Qt4.3, and I am using Qt4.2 now:crying:

Gopala Krishna
30th July 2007, 07:52
In that case you are better of to reimplement the item's itemChange method. Actually you can derive your item even from QObject and you can emit custom signal when selection changes.


class MyCustomItem : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
MyCustomItem()
..

signals:
void selectionChanged(bool newState);

protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemSelectedChange) {
bool newState = value.toBool();
//This sometimes might not be safe to emit signal
//here since the state is still unchanged. Better option might
//be to use a single shot timer to emit this signal rather than
//emitting it directly
emit selectionChanged(newState);
}
return QGraphicsItem::itemChange(change, value);
}
};

P.S: I'm not sure of my comment in the program though :)

aamer4yu
30th July 2007, 19:07
Another way of catching events is -
Install filter on the scene, check if any item is presnt where the mouse is pressed. U can get the item by itemAt() function, and then perform the action u want.

However from the thumbnail I see u have more of a tree structure kind items.
I want to know why arent u using a tree itself ?? U can create a custom QTreeWidgetItem and have a pointer to the QGraphicsItem to it..

like -
class MyTreeWidgetItem : public QTreeWidgetItem
{
QGraphicsItem *pGraphicsItem;
};

and u can have a map of graphics items and tree nodes -
QHash <QGraphicsItem *, MyTreeWidgetItem*> item_treeMap;

Hope I am getting ur problem correctly .

Gopala Krishna
31st July 2007, 05:15
Another way of catching events is -
Install filter on the scene, check if any item is presnt where the mouse is pressed. U can get the item by itemAt() function, and then perform the action u want.


I guess this might not work. Actually even i stated above that eventFilter is an alternative.
Since the event filter is called before the event handlers the item wont be selected when mouse is pressed and control reaches eventFilter. So you have no way here to determine whether the item is selected or not. You cant even execute the event handler in eventfilter since the handlers are protected/private.
Please correct me if i am wrong :)

EDIT: Sorry misread your post. You select the item just by coordinate right(using itemAt). I thought you were looking at selectedItems in the event handler. But you have to make sure that you are checking the button and also set the state of the item to selected.

Shawn
31st July 2007, 07:58
I did what u said

class MyRectItem : public QObject, public QAbstractGraphicsShapeItem
{
Q_OBJECT
public:
MyRectItem();
signals:
void selectionChanged(bool newState);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemSelectedChange)
{
bool newState = value.toBool();
emit selectionChanged(newState);
}
return QGraphicsItem::itemChange(change, value);
}
};


void test::drawRect(QGraphicsScene &scene, int x, int y, int m, int n)
{
MyRectItem *i = scene.addRect( QRectF(x, y, m, n));
i->setFlag(QGraphicsItem::ItemIsSelectable);
i->setBrush( QColor(233,104,21) );
i->setPen( QPen(QColor(246,153,90), 3));
i->setZValue(2);
}

and there is an error :
error C2440: 'initializing' : cannot convert from 'QGraphicsRectItem *' to 'MyRectItem *'
How can I deal with this ?
I am not familiar with the multi-inheritance.
Hope you can help me, and thanks in advance.

Gopala Krishna
31st July 2007, 08:44
The scene.addRect returns new QGraphicsRectItem* which is now way connected to MyRectItem other than that they both have common base.
To add any of your custom item use scene.addItem(new CustomItem()) (in your case new MyRectItem).

One more thing , you don't seem to store any rect coordinates nor provide the required constructor. One way to tackle this is to inherit MyRectItem from QGraphicsRectItem rather that QAbstractGraphicsShapeItem and implement the constructors as needed :)

Shawn
31st July 2007, 09:04
I inherite MyRectItem from and implement the constructor

class MyRectItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
public:
MyRectItem(int x, int y, int w,int h)
{
QRectF(x, y, w, h);
}
....
};

and then

void test::drawRect(QGraphicsScene &scene, int x, int y, int m, int n)
{
MyRectItem *i = new MyRectItem(x, y, m, n);
scene.addItem(i);
i->setFlag(QGraphicsItem::ItemIsSelectable);
}
But there is no Rect now:crying:

Gopala Krishna
31st July 2007, 09:11
I guess the following changes will fix your code
Firstly your constructor


MyRectItem(qreal x, qreal y, qreal w, qreal h) : QGraphicsRectItem(x,y,w,h)
{}

and secondly set some pen and brush to the item

i->setPen(QPen(Qt::darkBlue));

Shawn
31st July 2007, 09:26
Yes I 've set pen and brush to the item, I discard them just to make my post short:)

And I revise the constructor as u said and it works !

Thanks a lot :D

the next problem is that :

seems no signal is emited ?
because when I select the MyRectItems one after another, and nothing happens? :confused::(

Singal and Slot definition and connection is as below.

Latest:

void test::drawRect(QGraphicsScene &scene, int x, int y, int m, int n)
{
MyRectItem *i = new MyRectItem(x, y, m, n);
QObject::connect(i, SIGNAL(selectionChanged()), this, SLOT(testslots()));
scene.addItem(i);
....
}Now it is OK ~

Gopala Krishna
31st July 2007, 10:26
You used the wrong signature of signal in the connect statement.
Shouldn't that be SIGNAL(selectionChanged(bool)) ?

Shawn
31st July 2007, 12:16
Yes, I also change the signal to make it more simple...

The problem is that the signal is emited whenever the selection of an item is changed, which also means that when I unselecte the item, the signal will also be emited.

This is not the what I want, which I want exactly is that when I selected an item, which would be better, double click an item, there can be a signal, and then I can write some slots to deal with this signal.

Gopala Krishna
31st July 2007, 12:24
Thats very easy to fix:)

if(newState == true)
emit itemSelected();
Notice that i changed the signal name to be more meaningful. Change everywhere as needed :)

Shawn
31st July 2007, 12:35
Thanks a lot !
It works fine.

Anyway, if I want to selected that item by double click, what should I do ?

PS:seems I am really lazy. But maybe I should know the way to do it first, then I can find out why I should do it that way, right ?:p

Gopala Krishna
31st July 2007, 13:17
I guess you can do it yourself if you get to know some common mistakes ;)

Anyway for your question the answer involves
QGraphicsItem::mouseDoubleClickEvent()

QGraphicsItem::setSelected()

I hope you got the approach :)

Shawn
31st July 2007, 13:23
hehe.

Anyway, thanks a lot for your great patience.:D