Seite 1 von 1

QGraphicsView: scrollen selbst implementieren

Verfasst: 1. Juni 2009 12:27
von shapoc
Hallo zusammen,
da hab ich mich wohl grad selber verirrt und hoffe ihr könnt Licht ins dunkel bringen...

Habe eine eigene QGraphicsView-Klasse, der ich per

Code: Alles auswählen

setDragMode(QGraphicsView::ScrollHandDrag);
das verschieben beigebracht hab. Au0erdem in der wheelEvent Methode selbst das zoomen implementiert.
Funktioniert soweit.

Nun mein Problem. Ich möchte das "Verschieben" ändern, d.h. weiterhin per ScrollHandDrag das Verschieben ermöglichen, aber wenn der Benutzer die Maustaste dann loslässt, soll die View noch ein wenig weiter in die gezeigte Richtung verschieben, also so eine Art ausfaden des Verschiebens.

Ich dachte nun, ich mach das indem ich die MousePress und MouseRelease-Methoden überschreibe, und mir dann ein delta ausrechne, dass ich dann im release dazurechnen kann.

Leider passiert nun gar nix mehr, auch nicht der standard-scrollhanddrag :-(

Hat jemand eine Idee, wie ich das umsetzen könnte bzw. weiß jemand, ob ich da auf dem Holzweg bin?

Würde mich über Hilfe sehr freuen.

Verfasst: 1. Juni 2009 17:47
von franzf
Du musst das Verschieben über die verticalScrollBar und horizontalScrollBar des QGraphicsView (=QAbstractScrollArea) machen. Macht übrigens Qt selber so, schau mal in die Sources was bei dem ScrollHandDrag passiert.

Hatten wir hier vor ein paar Monaten aber schon mal.

Verfasst: 2. Juni 2009 11:42
von shapoc
Hallo franzf,
ja, das habe ich bereits herausgefunden. Ich wollte eigentlich gerne wissen, ob mein Ansatz korrekt war, die Methoden einfach zu überschreiben in meiner View.

Denn mein Problem ist, dass das Standard-Draggen per Maus dann nicht mehr funktioniert. So wie ich es nun verstanden habe, muss ich wenn ich daran irgendwas ändere, diese Funktionalität komplett selbst "nachbilden" ?!
Also das normale verschieben dann wahrscheinlich per MouseDragEvent und das "ausfaden"/weiterlaufen lassen per MouseReleaseEvent.

Ist das korrekt?!

Edit:
Hier nochmal mein bisheriger Code dazu in meiner View-Klasse:

Code: Alles auswählen

// funktioniert super zum zoomen
void View::wheelEvent(QWheelEvent *event)
{
	double numDegrees = -event->delta()/8.0;
	double numSteps = numDegrees/15.0;
	double factor = std::pow(1.125, numSteps);
	
	scale(factor, factor);
}

void View::mouseReleaseEvent(QMouseEvent *event)
{

	if (event->button() == Qt::LeftButton)
	{
		QPoint p = event->pos();
		scrollContentsBy(p.x() - lastPoint.x(), p.y() - lastPoint.y() );
		lastPoint = event->pos();
	}
}

void View::mousePressEvent ( QMouseEvent * event )
{
	lastPoint = event->pos();
}

void View::scrollContentsBy(int dx, int dy)
{
	scroll(dx,dy);
}

// hier wollte ich das kontinuierliche verschieben implementieren
// funzt aber nich, hier geht er gar nich rein
void View::dragMoveEvent(QDragMoveEvent *event)
{
	QPoint p = event->pos();
	scrollContentsBy(p.x() - lastPoint.x(), p.y() - lastPoint.y() );	
	lastPoint = event->pos();
}

Verfasst: 2. Juni 2009 12:55
von franzf
Ungetestet:
Mach dir nen Member "bool m_isMoving". Im mouseMoveEvent() setzt du den auf true, falls die linke Maustaste gedrückt ist, ansonsten auf false. Danach musst du natürlich die mouseMoveEvent() der Basisklasse aufrufen (wg. dem ScrollHandDrag für normales Scrollen). Im mouseReleaseEvent() frägst du nach, ob das "m_isMoving" true ist. falls nicht ignorieren, falls ja berechnest du dir aus vorheriger und aktueller Position die Richtung und setzt die Position selber neu. Aber jetzt bitte nicht in ner Schleife im mouseReleaseEvent() immer neu setzen. Das blockiert nämlich die ganze Applikation. Besser al Member zielPos und startPos, sowie numSteps merken, und per z.B. QTimer::singleShot() immer wieder (bis numSteps-- == 0) neu aufrufen.

Bedenke, dass du auch irgendwie die Geschwindigkeit der Bewegung einbeziehen soltest. Es wirkt auf Dauer lästig, wenn einem bei langsamem Verschieben beim Loslassen der viewport davonspringt, oder aber bei schneller Bewegung beim Loslassen das Bild absolut langsam dahintuckelt...

Dafür kannst du dir in ner Liste mit begrenzter Anzahl (z.B. 10) bei jedem mouseMoveEvent() einen Wert anhängen ( QTime::currentTime() -> "t.sec()*1000 + t.msec();" sollte reichen, brauchst ja keine Minuten von mouseMoveEvent zu mouseMoveEvent :P)
Aus diesen kannst du dir dann ableiten, wie schnell deine eigene Fading-Bewegung sein muss, und auch wie stark du abbremsen willst (bei langsamen Bewegungen weniger abbremsen, bei schnellen stärker)

Viel Spaß beim Basteln :P

Grüße
Franz

Verfasst: 3. Juni 2009 12:33
von shapoc
Erstmal danke für die Hinweise. Vor lauter Bäumen ist mir nicht mehr aufgefallen, dass ich natürlich die Event-Methoden der Basisklasse aufrufen muss!
Damit funktioniert das normale Scrollen per ScrollHandDrag wieder!!!

Problem ist nun das Faden. Dein Ansatz war sicher richtig, einziges Problem ist die Umsetzung ;-)

Ich krieg beim MouseMove nicht die Abfrage auf den Button hin. War hier im Forum zwar schonmal, aber funzt net so richtig... :-(

Wollte es so machen:

Code: Alles auswählen

void View::mouseMoveEvent(QMouseEvent *event)
{
	if (event->button() == Qt::LeftButton)
	{
		isMoving = true;
	        lastPoint = event->pos();
	}
	else
		isMoving = false;
	
	QGraphicsView::mouseMoveEvent(event);
}
da geht er aber gar net rein!

Mein zweites Problem ist, dass anscheinend die scrollContentsBy Methode nich das macht, was ich will. Bisher habe ich das mit dem Timer so gebaut:

Code: Alles auswählen

void View::mouseReleaseEvent(QMouseEvent *event)
{	
	currentPoint = event->pos();
	QTimer::singleShot(100, this, SLOT(fadeMove()));
	QGraphicsView::mouseReleaseEvent(event);
}

void View::fadeMove()
{
	QGraphicsView::scrollContentsBy(currentPoint.x() - lastPoint.x() , currentPoint.y() - lastPoint.y() );
}
Er ruft zwar beim loslassen der Maustaste meine Methode auf, aber die "scrollt" nich...
Oben gabs ja schon den Hinweis, die Scrollbars zu benutzen, find dafür aber das Beispiel nicht mehr. Kann mir da jemand noch aushelfen... :oops:

Verfasst: 10. Juni 2009 10:04
von shapoc
OK, ich habs jetzt per QScrollbar hinbekommen, dass er die Scene "scrollt", oder besser, dass er nach dem verschieben springt.
DAzu habe ich folgendes gemacht:

Code: Alles auswählen

void View::mouseReleaseEvent(QMouseEvent *event)
{
	QGraphicsView::mouseReleaseEvent(event);
	
	currentPoint = event->pos();
	QTimer::singleShot(100, this, SLOT(fadeMove()));
}

void View::fadeMove()
{
	QScrollBar *hBar = horizontalScrollBar();
        QScrollBar *vBar = verticalScrollBar(); 

	int deltaX = currentPoint.x() - lastPoint.x();
	int deltaY = currentPoint.y() - lastPoint.y();
	for (int i = 0; i < deltaX*10; ++i)
	{
		hBar->setValue(hBar->value() + deltaX );
		vBar->setValue(vBar->value() - deltaY ); 
	}
}
Das Problem ist jetzt, dass der Abstand zwischen lastPoint und currentPoint sehr klein ist, da ich ja den lastPoint beim verschieben jedesmal setze, um die Richtung für das nachsliden zu berechnen.

Frage: Wie könnte ich ein "weiches" nachsliden implementieren, also nich wie jetzt in der for-schleife, sondern eher als exponentialfunktion, also dass das sliden abnimmt.

Wär für jede Hilfe dankbar!!!

Verfasst: 10. Juni 2009 10:21
von franzf
In dem du dir überlegst was faden heißt.
Hier z.B.: Bei konstanter Zeiteinheit wird die zurückzulegende Distanz je Zeiteinheit immer kleiner. Oder: Bei konstanten Wegstrecken wird die Zeiteinheit zum nächsten move immer größer.

In beiden Fällen gibst du dir eine bestimmte Strecke vor, und ein Zeitintervall, in dem die Bewegung stattfinden soll. Aus den beiden Werten kannst du dir ableiten, wie viele einzelne Steps du brauchst, damit deine Bewegung flüssig und wahrnehmbar ist (also halt nicht zu schnell und nicht zu langsam).
-> int numSteps als privaten Member hinzufügen.
Den Wert für numSteps errechnest du jetzt in mouseReleaseEvent(). Aber BITTE wie schon geschrieben noch nen Member bool isMoving hinzufügen, den du bei mouseMoveEvent auf true setzt, falls auch ein Button gedrückt ist. Denn auch ohne mouseMove kannst du klicken und loslassen, und du willst doch nicht, dass da dann deine Scrollarea davonspringt :P

Per QTimer::singleshot in mouseReleaseEvent (falls isMoving true ist!) fadeMove anstoßen.
Darin den ersten Step machen, numSteps-- (also runterzählen, und wiedrum per QTimer::singleshot fadeMove mit den neuen Werten anstoßen.

Anstatt mit QTimer::singleShot kannst du natürlich auch nen fixen Timer verwenden. Oder mit QObject::startTimer()+timerEvent(). Geht aber nur wenn du dich oben für die konstanten Timer-Intervalle entscheidest.

Jetzt hab ich dir das/Teile des Denken(s) abgenommen, implementieren darfst du :P

Verfasst: 10. Juni 2009 10:30
von shapoc
Dank dir für die Hinweise!!!
Das mit der konstanten Zeiteinheit hört sich sehr gut an, ich versuch mich daran mal...
franzf hat geschrieben:Aber BITTE wie schon geschrieben noch nen Member bool isMoving hinzufügen, den du bei mouseMoveEvent auf true setzt, falls auch ein Button gedrückt ist. Denn auch ohne mouseMove kannst du klicken und loslassen, und du willst doch nicht, dass da dann deine Scrollarea davonspringt :P
Das wollte ich auch schon machen, siehe 2. Post, aber bekomme die Abfrage auf den Button nicht hin, sprich: er setzt mein bool-flag nich... :-(
franzf hat geschrieben: Jetzt hab ich dir das/Teile des Denken(s) abgenommen, implementieren darfst du :P
Ähmm, ja ;-) Aber mir hats auf jeden Fall ein bischen weitergeholfen und deshalb THX!!!

Verfasst: 10. Juni 2009 10:45
von franzf
shapoc hat geschrieben: Das wollte ich auch schon machen, siehe 2. Post, aber bekomme die Abfrage auf den Button nicht hin, sprich: er setzt mein bool-flag nich... :-(
Dann schau doch mal in die Doku. Da findest du das hier:
Qt::MouseButton QMouseEvent::button () const

Returns the button that caused the event.

Note that the returned value is always Qt::NoButton for mouse move events.

See also buttons() and Qt::MouseButton.
Und nun? Wunderts dich? :D

Code: Alles auswählen

if( event->buttons() & Qt::LeftButton ) { // usw.
ist angebrachter...

Verfasst: 10. Juni 2009 10:47
von shapoc
Ach, so eine s.... Natürlich hast du recht!!! :oops:

Ok, dann mach ich mich mal an die steps und meld mich später nochmal...