Effektives zeichnen mitels paintEvent()

Alles rund um die Programmierung mit Qt
Antworten
slash-ex
Beiträge: 239
Registriert: 30. März 2005 21:40

Effektives zeichnen mitels paintEvent()

Beitrag von slash-ex »

Ich möchte eine Tabelle zeichnen, die Daten darstellt. Dies stellt das erste recht rechenintensive Unterfangen dar und der Programmaufbau wäre bis dahin ungefähr so:

Code: Alles auswählen

void paintEvent(...) {
   QPainter painter;
   void paintGrid(&painter);
}
Nun möchte ich, darüber eine Art Auswahlrahmen zeichnen. Dazu habe ich eine weitere Funktion geschrieben und das Programm sieht ungefähr so aus:

Code: Alles auswählen

void paintEvent(...) {
   QPainter painter;
   void paintGrid(&painter);
   void paintSelectionBox(&painter);
}
Leider zeichnet das Programm dafür alles neu. Ich würde gerne wissen, wie man den ersten Zeichenteil zwischenspeichern könnte, sodass nur noch die Auswahlbox neu gezeichnet werden muss. QGraphicsItems sind übrigens nicht grade das warauf ich aus bin, da ich wie gesagt eine Tabelle mache.
upsala
Beiträge: 3946
Registriert: 5. Februar 2006 20:52
Wohnort: Landshut
Kontaktdaten:

Beitrag von upsala »

Und warum verwendest du keine QTableView?
slash-ex
Beiträge: 239
Registriert: 30. März 2005 21:40

Beitrag von slash-ex »

tja, gute frage. ich sollte halt keine tableview benutzen. außerdem soll das ganze wie in sonem musikprogramm bestimmte ebenen/spuren anzeigen und speziell skaliert werden können. zudem werden da keine symbole eingetragen sondern die kästchen angefärbt. und die beschriftung wird auch etwas anders.

so wie sich die richtung entwickelt, wird das wohl nicht so einfach mit dem zeichnen?! ich habe mir gedacht, dass es doch irgendeine möglichkeit geben muss, erst einenteil fertig zu zeichnen, den dann zwischen zu speichern und darüber dann eine neue schicht zu malen. geht sowas irgendwie?
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

ich peil die frage net so ganz...
wo wird da alles neu gezeichnet?
ich seh 2 funktionsaufrufe (eigentlich ne art deklarationen in ner funktion, was gare nie gehen kann... aber egal.)
wenn also alles neu gezeichnet wird, dann weil du das in der funktion machst, also zeig uns die implemetierungen.

und drüber eine neue schicht malen macht der painter selber, wenn an der stelle schon was war.
slash-ex
Beiträge: 239
Registriert: 30. März 2005 21:40

Beitrag von slash-ex »

ich wollte die zweite funktion, von der ersten entkoppeln. dasist bei einem paintevent aber nicht möglich. da muss ich doch keine implementation zeigen. das würde am problem doch nichts ändern.

und das da oben ist btw nur pseudo-code, ich wollte nur den typ noch mit angeben.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

slash-ex hat geschrieben:ich wollte die zweite funktion, von der ersten entkoppeln. dasist bei einem paintevent aber nicht möglich.
?!? Sry, aber warum nochmal sollte das nicht gehen?
Du kannst so viele Einzelfunktionen mit nem QPainter als Parameter in deinem paintEvent() aufrufen wie du willst. Was du nicht darfst, ist außerhalb von paintEvent() einen QPainter auf das QWidget ansetzen!

Und wenn du mit deinem aktuellen Code Probleme hast, wäre es nicht schlecht, diesen zu zeigen. Mit vagen Andeutungen und Vermutungen kann man dir auch keine klare Antwort geben :(
slash-ex
Beiträge: 239
Registriert: 30. März 2005 21:40

Beitrag von slash-ex »

Du kannst so viele Einzelfunktionen mit nem QPainter als Parameter in deinem paintEvent() aufrufen wie du willst.
ja das mache ich ja auch, aber durch das paintevent wird halt jedesmal auch die erste funktion neu aufgerufen, wenn ich die zweite benutzen möchte.
was ich möchte ist: ich will einen auswahlrahmen zeichnen, ohne die darunter liegenden kästchen neu zu zeichnen. irgendwie sowas wie zwischenspeichern und erst dann zeichnen, wenn benötigt.

im programm sieht das so aus:

Code: Alles auswählen

/*
 * protected functions
 */
void SelectorWidget::paintEvent ( QPaintEvent * /*event*/ ) {
	QPainter painter(this);
	QPen pen;  // creates a default pen

	/*
	 * rechne die größe des Zeichenbereiches aus
	 */
	m_iWinWidth = m_iNumOfFrames * m_rRectSize.width();
	m_iWinHeight = (m_iNumTracks+1) * m_rRectSize.height();

	/*
	 * zeichne für jeden Track den Namen in die ersten 250Pix
	 * dann zeichne die Kastenansicht
	 */
	QPoint pointTitles(m_rRectSize.x()+15, m_rRectSize.y() + m_rRectSize.height()/2+4);	// Position für den Titel des Tracks
	QPoint pointRects(m_iLabelSize, m_rRectSize.y()+0);										// Position für die Kastenansicht (etwas weiterrechts)

	/*
	 * LookAt Track zeichnen
	 */
	painter.drawText(pointTitles, "LookAt's");							// zeichne den Titel des LookAt Tracks
	DrawLookAtTrack(&painter, pointRects);
	pointTitles.setY(pointTitles.y() + m_rRectSize.height() );			// wechsle die Zeile je nach breite der Kastenansicht
	pointRects.setY(pointRects.y() + m_rRectSize.height() );			// wechsle die Zeile je nach breite der Kastenansicht

	/*
	 * Scene Tracks zeichnen
	 */
	for(int i = 0; i < m_iNumTracks; i++) {
		if(m_lTrackNames.size() > i) {
			painter.drawText(pointTitles, m_lTrackNames.at(i) );		// zeichne den Titel der Scene-Tracks
		}

		SelectorWidget::DrawSceneTracks(&painter, pointRects);

		pointTitles.setY(pointTitles.y() + m_rRectSize.height() );		// wechsle die Zeile je nach breite der Kastenansicht
		pointRects.setY(pointRects.y() + m_rRectSize.height() );		// wechsle die Zeile je nach breite der Kastenansicht
	}

	/*
	 * Zeichne Rahmen
	 */
	pen.setWidth(2);
	pen.setBrush(Qt::black);
	painter.setPen(pen);

	painter.drawRect(m_rRectSize.x() + m_iLabelSize, m_rRectSize.y(), m_iWinWidth, m_iWinHeight);
	painter.drawRect(m_rRectSize.x(), m_rRectSize.y(), m_iWinWidth + m_iLabelSize, m_iWinHeight);

	/*
	 * Zeichne Auswahlrahmen
	 */
	SelectorWidget::DrawSelectionRect(&painter);
}
Der Auswahlrahmen wird durch ein MouseEvent getriggert.

Code: Alles auswählen

void SelectorWidget::DrawSelectionRect(QPainter *pPainter) {
	QPen pen;  // creates a default pen
	pen.setWidth(1);
	pen.setBrush(Qt::red);
	pPainter->setPen(pen);

	int startX;
	int startY;

	QPoint mouseDelta;
	if(m_pMouseMove.x() >= m_pMouseDown.x() && m_pMouseMove.y() >= m_pMouseDown.y() ) {
		startX = /*m_rRectSize.x() + */ m_iLabelSize + m_pMouseDown.x() * m_rRectSize.width();
		startY = m_rRectSize.y() + 0 + m_pMouseDown.y() * m_rRectSize.height();

		mouseDelta = m_pMouseMove - m_pMouseDown;
		mouseDelta += QPoint(1, 1);
	}
	else if(m_pMouseMove.x() <= m_pMouseDown.x() && m_pMouseMove.y() <= m_pMouseDown.y() ) {
		startX = /*m_rRectSize.x() + */ m_iLabelSize + m_pMouseMove.x() * m_rRectSize.width();
		startY = m_rRectSize.y() + 0 + m_pMouseMove.y() * m_rRectSize.height();

		mouseDelta = m_pMouseDown - m_pMouseMove;
		mouseDelta += QPoint(1, 1);
	}

	else if(m_pMouseMove.x() >= m_pMouseDown.x() && m_pMouseMove.y() <= m_pMouseDown.y() ) {
		startX = /*m_rRectSize.x() + */ m_iLabelSize + m_pMouseDown.x() * m_rRectSize.width();
		startY = m_rRectSize.y() + 0 + m_pMouseMove.y() * m_rRectSize.height();

		mouseDelta.rx() = m_pMouseMove.x() - m_pMouseDown.x();
		mouseDelta.ry() = m_pMouseDown.y() - m_pMouseMove.y();
		mouseDelta += QPoint(1, 1);
	}

	else if(m_pMouseMove.x() <= m_pMouseDown.x() && m_pMouseMove.y() >= m_pMouseDown.y() ) {
		startX = /*m_rRectSize.x() + */ m_iLabelSize + m_pMouseMove.x() * m_rRectSize.width();
		startY = m_rRectSize.y() + 0 + m_pMouseDown.y() * m_rRectSize.height();

		mouseDelta.rx() = m_pMouseDown.x() - m_pMouseMove.x();
		mouseDelta.ry() = m_pMouseMove.y() - m_pMouseDown.y();
		mouseDelta += QPoint(1, 1);
	}

	int width = mouseDelta.x() * m_rRectSize.width();
	int height = mouseDelta.y() * m_rRectSize.height();

	if(m_eCurrentMouseAction == ePressDown)
		pPainter->drawRect(startX, startY, m_rRectSize.width(), m_rRectSize.height() );

	if(m_eCurrentMouseAction & eSelecting)
		pPainter->drawRect(startX, startY, width, height);
}

Code: Alles auswählen

void SelectorWidget::mouseMoveEvent ( QMouseEvent * event ){

	if(m_eCurrentMouseAction == ePressDown)
		m_eCurrentMouseAction = MouseAction(ePressDown | eSelecting);

	QPoint point = event->pos();

	int currentColumn;
	int currentRow;
	int curX = point.x() - m_iLabelSize; // ist um 150 Pixel wegen Label verschoben
	int curY = point.y() - m_rRectSize.y();

	currentColumn = curX / m_rRectSize.width();
	currentRow = curY / m_rRectSize.height();

	if(currentRow < m_iNumTracks+1 && currentColumn < m_iNumOfFrames && currentRow >= 0 && currentColumn >= 0) {
		m_pMouseMove = QPoint(currentColumn, currentRow);
		std::cout << "MouseM: X=" << currentColumn << "; Y=" << currentRow << std::endl;
	}

	SelectorWidget::update();
	QWidget::mouseMoveEvent ( event );
}
unspektakulär sehen die scene-tracks aus:

Code: Alles auswählen

void SelectorWidget::DrawSceneTracks(QPainter *pPainter, QPoint startPt) {
	QPen pen;  // creates a default pen

	/*
	 * Zeichne einzelne Blöcke
	 */
	pen.setWidth(1);
	pen.setBrush(Qt::black);
	pPainter->setPen(pen);

	for(int i = 0; i < m_iNumOfFrames; i++) {
		QRect rect(startPt.x() + i*m_rRectSize.width(), startPt.y(), m_rRectSize.width(), m_rRectSize.height());
		if(m_rRectSize.width() > 1)
			pPainter->drawRect(rect);
		pPainter->fillRect(rect, QColor(192, 128, 0, 200) );
	}
}
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

Da das grid unter der selection liegt, musst du wohl oder übel auch Teile darunter malen.
Außerdem gibt dir das QPaintEvent ein QRect bzw. QRegion für den Bereich, der gemalt werden muss. Gib deinen Zeichenmethoden eins von beiden mit und mal nur den Bereich.
Außerdem solltest du überlegen, ob nicht die ganzen Berechnungen für die Selection zusammen mit deinem MouseEVent passieren. Denn das ist eine richtige Performance-Bremse beim malen, das deutlich öfter auftreten wird als die Selection ändern.

Wenn das grid im Hintergrund kaum Aktualisierungen erfährt, kannst du ja auch dieses einfach in ein QPixmap rendern, wenn es neue Sachen darstellen soll, und einfach aus dem Pixmap den Teil des QPaintEvent::rect() malen.
slash-ex
Beiträge: 239
Registriert: 30. März 2005 21:40

Beitrag von slash-ex »

danke ich werde das mal probieren und abwarten was passiert.
slash-ex
Beiträge: 239
Registriert: 30. März 2005 21:40

Beitrag von slash-ex »

ich habe mal das paintevent->rect() ausgeben lassen und zeichne nur noch den bereich, den ich benötige. das hat super funktioniert. und die berechnungen hbe ich auch ausgelagert. danke erstmal, kaum zu glauben, wie viel schneller das rendert.
Antworten