I am learning Qt, and I want to draw lines randomly on a widget and keep appending the new lines. The code below draws a random line in the paintEvent whenever an update is called on the widget, but how do I stop the widget from clearing the previously drawn line when paintEvent is called? Is there any way to just append drawn objects?
Obviously I could store all the lines and repaint them every time, but that seems unnecessary for what I will be doing with this application.
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 2));
painter.drawLine(QPointF(qrand() % 300, qrand() % 300), QPointF(qrand() % 300,qrand() % 300));
}
void MainWindow::on_b_toggleDrawing_triggered()
{
this->update();
}
You could draw the lines on an off-screen surface and blit them to the widget in the paint event. A QImage would be ideal as it is a QPaintDevice and could be drawn using QPainter::drawImage. The snippet below assumes that this->image is a pointer to a QImage with the same size as the MainWindow.
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawImage(this->rect, *this->image);
}
void MainWindow::on_b_toggleDrawing_triggered()
{
QPainter painter(this->image);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 2));
painter.drawLine(QPointF(qrand() % 300, qrand() % 300),
QPointF(qrand() % 300,qrand() % 300));
this->update();
}
An alternative would be to build a path using QPainterPath. In that case, you would simply maintain a QPainterPath instance, add lines as needed and then draw the path in the paint event handler. I'm not as familiar with painter paths. So, I'm not sure how the performance compares with the previous approach.
Set autoFillBackground to false. It's erasing (filling with background color) before calling paintEvent if set.
Or, insert command
this->setAttribute( Qt::WA_NoSystemBackground, bool ) ;
prior to calling
this->update() ;
bool = true - leaves the paint area intact and allows
new items to be added to the paint area.
bool = false - erases the paint area before drawing items.
Each time you want to create the next line you could create a QGraphicsLineItem (link) object and add it to a QGraphicsScene (link) widget.
Note that in this solution you have to bother neither about repainting the lines nor about destroying them when quitting the program, because QGraphicsScene will take care of both actions.
Related
I would like to have certain things drawn on QGraphicsScene, but not be QGraphicsItem (it would interfere with the processing of the QGraphicsItem collection).
Example: a scene bounding rectangle, a grid
I am overriding the drawBackground(QPainter *painter, const QRectF &rect) for that purpose. (I should subclass the scene... )
void MyView::showHideBounds()
{
m_showBackgroundBounds = !m_showBackgroundBounds;
// can't triger an update ???
update(); // neither does anything
viewport()->update();
}
void MyView::drawBackground(QPainter *painter, const QRectF &rect)
{
QPen pen;
if(m_showBackgroundBounds)
pen = QPen(QColor(0, 0, 0), 10, Qt::PenStyle(Qt::SolidLine));
else
pen = QPen(QColor(255, 255, 255), 10, Qt::PenStyle(Qt::SolidLine));
painter->setPen(pen);
painter->drawRect(QRect(QPoint(-scene()->sceneRect().size().toSize().width()/2,
-scene()->sceneRect().size().toSize().height()/2),
scene()->sceneRect().size().toSize()));
}
I would like the option to show/hide either the bounding rectangle or the grid.
The only thing I can think of is paint over them with the color of the background brush ? Is there any other option ?
As I have written it above, it works - except I need user action on items (or a zoom or some other scene changing action) to trigger refresh, or call an update... (the function showHideBounds doesn't - not sure how to make it force a refresh)
I would call the drawBackground from the showHideBounds function - but I don't know how to get the painter
[Also, the drawBackground seems to be drawn automatically... how can I give it the rect argument it needs ? (it seems if I draw the rect it does draw the scene rectangle but I only see the right and bottom edges)]
In order to redraw a particular section of scene, you can call
QGraphicsScene->invalidate(rect_to_redraw, Backgroundlayer)
Note that if drawBackground(*painter, rect) paints over area outside rect, it will not update automatically. In that case invalidate has to be called with appropriate rect parameters.
Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?
The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).
My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.
If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?
The used Qt version is Qt 4.8.6
My questions are:
Is it possible to achieve what I want with the mentioned strategy?
Is it possible in any way to clip all the children, too?
Is my strategy appropriate or is it a bad Idea to solve it that way?
Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?
Additional Information
I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:
This works.
/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry()
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setPen(QColor(255, 0, 0));
painter.setBrush(QColor(255, 0, 0));
painter.setOpacity(1);
painter.drawRect(rect);
}
But the following doesn't change anything:
/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry();
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setRenderHint(QPainter::Antialiasing);
painter.setOpacity(1);
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.
The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:
case PE_Widget:
if (w && !rule.hasDrawable()) {
QWidget *container = containerWidget(w);
if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
&& (container == w || !renderRule(container, opt).hasBackground())) {
//we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
// (this may happen if we have rules like :focus)
p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
}
break;
}
As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:
if (q->testAttribute(Qt::WA_StyledBackground)) {
painter->setClipRegion(rgn);
QStyleOption opt;
opt.initFrom(q);
q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}
Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.
To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:
QPixmap pixmap(widget->size());
widget->render(&pixmap);
And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.
I have a QGraphicsScene which stores QGraphicsLinesItems and QGraphicsRectItems.
I am using the QGraphicsScene method to itemsAt() and I pass through x and y co ordiantes to return the QGraphicsItem and I use the following:
QPointF getItemPos= this->mapToScene(this->mapFromGlobal(QCursor::pos()));
QGraphicsItem *itm = scene->itemAt(getItemPos.x(),getItemPos.y());
QGraphicsLineItem *lineItm;
QGraphicsRectItem *rectItm;
if((lineItm = dynamic_cast<QGraphicsLineItem*>(itm))){
// do stuff with as_pnedge
qDebug("Line");
}else if((rectItm = dynamic_cast<QGraphicsRectItem*>(itm))){
// do stuff with as_pnitem
qDebug("Rect");
}
else
{
qDebug("Select Item");
}
The issue I am having is that QGraphicsRectItem is returned fine and can be detected. But no matter where I click around the QGraphicsLineItems it can never detect and return the item. Any assistance would be very much appreciated.
If your line uses a cosmetic pen (width of zero), it means that the shape() method will return a zero width QPainterPath (source code, search for "qt_graphicsItem_shapeFromPath").
You will have to derive from QGraphicsLineItem and reimplement shape() to clamp the minimum pen width for QPainterPathStroker to something reasonable.
I want to draw an image, pixel by pixel at run time. I use QPainter and paintEvent to draw. But when paintEvent is called each time, the previously drawn image is cleared and the new point has been drawn.
How to avoid clearing the previously drawn parts? I just want to append the new pixel point to the previously drawn points.
Lines::Lines(QWidget *parent)
: QWidget(parent)
{
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_timer->start();
m_x = 0;
m_y = 0;
}
void Lines::paintEvent(QPaintEvent *event)
{
QPen pen(Qt::black, 2, Qt::SolidLine);
QPainter painter(this);
painter.setPen(pen);
painter.drawPoint(m_x, m_y);
}
void Lines::updateStatus()
{
m_x++;
m_y++;
update();
}
paintEvent is supposed to do a complete redraw of the widget region specified in the event.
So you are responsible for buffering previous results.
It doesn't really make sense to change the desired output in paintEvent, as it may be randomly called and when it is called is out of your control.
If you want to avoid that you can use a QGraphicsView.
Buffering could be done using a QPixmap, which would be part of the Lines class. You draw the pixel in the pixmap (not in the paint event, in updateStatus), and draw the pixmap in the paint event.
QWidget::setAttribute( WA_OpaquePaintEvent, true );
prevents clearing the widget. However, this is just for optimization in case the widget does a complete repaint anyway.
You should follow Dr. Hirsch's advice.
I am drawing few lines using paintEvent(QPaintEvent * event). Sometime later if I want to draw new lines then the previously drawn lines are not cleared/removed. How could I draw the new lines only, by removing/clearing old lines. Is there any property to clear the previously drawn lines.Please let me know.
void QGraphWidget::paintEvent(QPaintEvent * event)
{
const QRect & rect = event->rect();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
drawLines(painter, rect);//calling painter.drawLine() inside
}
Thanks...
In my opinion, the correct approach is to clear the area before drawing new lines. This can be achieved either by using the autoFillBackground property as proposed by Stephen Chu, or by calling manually the QPainter::eraseRect() before drawing your lines in the QGraphWidget::paintEvent method.
void QGraphWidget::paintEvent(QPaintEvent * event)
{
const QRect & rect = event->rect();
QPainter painter(this);
painter.eraseRect(rect);
painter.setRenderHint(QPainter::Antialiasing);
drawLines(painter, rect);
}
Another option is to draw "negatively" the lines from the previous call to QGraphWidget::paintEvent. If you store the coordinate of your old lines, you might first draw lines using the background brush and then draw your new lines using the foreground brush. See code sample that might fit into you drawLines method. Note that if you draw something else in your widget, drawing the lines negatively might erase some of the other graphics which is why the first approach, erasing all the GraphWidget area, is better.
// save the current brush
QBrush currentBrush = painter.brush();
// draw the old line with the background brush
painter.setBrush(background());
painter.drawLine(oldLine);
// draw the new line with the current brush
painter.setBrush(currentBrush);
painter.drawLine(newLine);
You want to fill your widget with window background color before redraw. Set autoFillBackground to true after you create the widget and Qt will handle this for you
I don't think there is a specific call to remove the line. If you're repainting the entire area each time paintEvent() function is called you shouldn't see previous lines. If you're not repainting the area, you'll have to draw over the line yourself. This code for instance is drawing a line in a different position each time the method is invoked:
QPainter painter(this);
painter.setBrush(QBrush(Qt::red));
painter.drawRect(rect());
painter.setPen(QPen(Qt::yellow));
static int i = 0;
painter.drawLine(QPointF(i, i), QPointF(i, height() - i));
i++;
but "previous lines" are cleared completely. If you want to keep those you'll have to repaint only a specific area or you'll have to repaint those.
This is one way to delete whole line from QT.
me->setFocus();
int pos;
QTextCursor tc= me->textCursor();
pos=tc.columnNumber();
tc.select(QTextCursor::LineUnderCursor);
QString str=tc.selectedText();
tc.removeSelectedText();
tc.movePosition(QTextCursor::NextBlock,QTextCursor::MoveAnchor);
tc.insertText(str);
tc.insertBlock();
tc.movePosition(QTextCursor::PreviousBlock,QTextCursor::MoveAnchor);
tc.movePosition(QTextCursor::StartOfLine,QTextCursor::MoveAnchor);
me->setTextCursor(tc);
return true;