QGraphicsView: scrollen selbst implementieren

Alles rund um die Programmierung mit Qt
Antworten
shapoc
Beiträge: 33
Registriert: 31. Mai 2009 12:18

QGraphicsView: scrollen selbst implementieren

Beitrag 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.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag 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.
shapoc
Beiträge: 33
Registriert: 31. Mai 2009 12:18

Beitrag 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();
}
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag 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
shapoc
Beiträge: 33
Registriert: 31. Mai 2009 12:18

Beitrag 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:
shapoc
Beiträge: 33
Registriert: 31. Mai 2009 12:18

Beitrag 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!!!
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag 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
shapoc
Beiträge: 33
Registriert: 31. Mai 2009 12:18

Beitrag 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!!!
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag 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...
shapoc
Beiträge: 33
Registriert: 31. Mai 2009 12:18

Beitrag 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...
Antworten