Rubberband selection in QGraphicsScene / QGraphicsView
-
Hi,
how can i achieve following behavior?
I got a Scene in a view with many pixmap items. each item is selectable.
they are layouted in a grid. However there are too many items to fit in the view so the scrollbars get enabled. Now i want to have a rubber band selection, that starts scrolling the view when reaching the top or bottom border, to select more items and keep the old ones selected too.can someone help me?
-
You can use "QGraphicsView::setDragMode()":http://qt-project.org/doc/qt-4.8/qgraphicsview.html#dragMode-prop with "QGraphicsView::RubberBandDrag":http://qt-project.org/doc/qt-4.8/qgraphicsview.html#DragMode-enum
-
yes i do that. but when i reach the bottom with that rubber band, the view doesn't start scrolling. as well as when i use mouse wheel to scroll, the rubber band moves but doesn't resize and doesn't update the selection.
-
you can simply add a check in the mouse move event, if drag mode is on and you are near a border of the scene you can either scroll or scale the scene to be able to cover more area with your selection
-
you got a code sample for that ? imo it isn't that easy since it doesn't work like that with mouse wheel scrolling i doubt i can just scroll. you have to create an own rubber band, determine startups and size of it, and update selection area of scene. as well as you have to find an algorithm of how fast you start to scroll.
-
So your problem is you can either use the drag or the select mode. So you will either have to use drag and implement selection, or use the select mode and implement dragging, or implement both.
The big question is whether your mouseMoveEvent is being called while you are dragging. If so, it is not that hard to calculate how close you are to the edges of the window, and if you are within a given threshold, let's say 20 pixels, you either scroll or scale the view.
Determining how fast you scroll is so easy I wouldn't even call it an algorithm. Just set a scrollSpeed variable to like 21, and when you cursor is within 20 pixels of a border, just scroll by (scrollSpeed - distanceFromBorder). This way if your mouse is 18 pixels away from the border, your will scroll by 3 pixels, if it is 2 pixels from the border, you will scroll by 19 pixels.
Providing a working code sample would take too much time, you should be able to do it yourself, provided mouseMoveEvent is being called while dragging.
edit:
OK, found some time, here is some very basic and potentially flawed example, enough to get you started. What it does is it tracks the cursor position relative to the center of the view, and once the cursor is within a certain distance of a border (scrollThreshold value) it give you two numbers, ranging from 0 to scrollThreshold depending on how close to the border you are, the closer you are the bigger values you will get, then you simply scroll by PLUS/MINUS those values depending in which region of the view you are in and the direction you want to scroll in.@
//header
class MGraphicsView : public QGraphicsView
{
Q_OBJECT
public:
MGraphicsView(QGraphicsScene* parent);
protected:
void mouseMoveEvent(QMouseEvent *event);
};//CPP
float scrollThreshold = 30;
void clamp(QPointF &value)
{
if ((value.x() > scrollThreshold) || (value.x() < 0)) value.rx() = 0;
else value.rx() = qAbs(value.x() - scrollThreshold);
if ((value.y() > scrollThreshold) || (value.y() < 0)) value.ry() = 0;
else value.ry() = qAbs(value.y() - scrollThreshold);;
}MGraphicsView::MGraphicsView(QGraphicsScene *parent) : QGraphicsView(parent)
{
setDragMode(QGraphicsView::RubberBandDrag);
}void MGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
QGraphicsView::mouseMoveEvent(event);QPointF loc = event->posF(); QPointF d; if ((loc.x() <= rect().center().x()) && (loc.y() <= rect().center().y())) { //top left d.setX(frameGeometry().left() + loc.x()); d.setY(loc.y()); clamp(d); if (d.x() || d.y()) scrollContentsBy(d.x()/2, d.y()/2); repaint(); } else if ((loc.x() <= rect().center().x()) && (loc.y() > rect().center().y())) { //bottom left d.setX(frameGeometry().left() + loc.x()); d.setY(frameGeometry().bottom() - loc.y() - scrollThreshold); clamp(d); if (d.x() || d.y()) scrollContentsBy(d.x()/2, -(d.y()/2)); repaint(); } else if ((loc.x() > rect().center().x()) && (loc.y() > rect().center().y())) { //bottom right d.setX(frameGeometry().right() - loc.x()); d.setY(frameGeometry().bottom() - loc.y() - scrollThreshold); clamp(d); if (d.x() || d.y()) scrollContentsBy(-(d.x()/2), -(d.y()/2)); repaint(); } else { //top right d.setX(frameGeometry().right() - loc.x()); d.setY(loc.y()); clamp(d); if (d.x() || d.y()) scrollContentsBy(-(d.x()/2), d.y()/2); repaint(); }
}@
It does work a little buggy for me. It scrolls correctly but I get painting artefacts and corruption and even thou it scrolls the item remain in the same location after refresh. Don't know if it is a bug in Qt (I wasn't even able to subclass QGraphicsView in 4.8, compiler gave me a ton of errors, with 4.7.4 it is OK) or flaw in this hasty implementation. It should get you started though.