QGraphicsPixmapItem, like QGraphicsItem, has a method update(x0, y0, width, height), in order to redraw a pixmap only partly on a QGraphicsScene. Calling this will schedule a paint() (in Qt's event loop) on the QGraphicsItem, and after this paint() is executed the boundingbox (x,y,width,height) will be redrawn to the QGraphcisScene.
The unfortunate part is that there is no way to schedule the paint-event with a boundingbox, meaning that QGraphicsPixmapItem::paint() is forced to repaint the whole QPixmap, therefore reimplementing this paint()-method in a subclass gives no way to only partly update the QPixmap, therefore making a small (local) update to the QPixmap unacceptably slow.
Such a subclass would look something like this:
class LocallyUdatablePixmapItem : public QGraphicsPixmapItem {
private:
QImage ℑ
public:
LocallyUdatablePixmapItem(QImage &img) : QGraphicsPixmapItem(), image(img) {}
paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QStyle *widget) {
//locall update, unfortunately without a boundig box :( therefore -> slow
}
};
Another option would be to keep the 'internal QPixmap' of the QGraphicsPixmapItem, and draw the QImage to it partly, like this:
//some initialization of variables
QGraphicsScene scene = ...;
QImage img = ...; //some image data that I wish to manipulate from time to time
QPixmap pixmap = QPixmap::fromImage(this->shown);
QPainter painter = new QPainter(&this->pixmap);
QGraphicsPixmapItem item = this->addPixmap(this->pixmap);
item->setPixmap(this->pixmap);
//this should not matter, but actually it does, as I will explain shortly
//delete painter;
//painter = new QPainter(item->pixmap());
//For some reason I decide to update (manimulate) img within a small boundigbox
int x0, y0, width, height; //bounding box, assume they are set to whatever is appropriate for the previous update
painter->drawImage (x0, y0, img, x0, y0, width, height);
//now the pixmap is updated, unfortunately the item is not. This does not affect it:
item->update(x0, y0, width, height);
//nor does this:
item->update();
//but this, which makes the whole thing slow, does:
item.setPixmap(&pixmap);
Given I that I needed to set the pixmap to fix it, I assumed it was somehow not set in the initialization, therefore uncommenting the mentioned lines before seemed like a nice idea. Unfortunately, the drawImage() call then segfaults into:
QPaintDevice: Cannot destroy paint device that is being painted
I would like to have an alternative to the "item.setPixmap(&pixmap);", which does not redraw the whole thing, but does work nicely. Any input is very well appreciated :)
Before I propose a solution, a few thoughts:
First, the Graphics View framework is intended to be a solution for displaying many graphic objects, so one large image isn't really that fitting. Of course, I realize your example is probably just a contrived one, so this point might not really apply. Second, since the framework is very transform-centric, it might not make sense to only redraw parts of a QGraphicsItem unless all the transforms are identity, there is no scrolling, etc.
Anyways, if you only want to draw part of a QGraphicsItem, you could simply store the rect that needs to be updated, and access it from inside your paint() method. For example:
CustomItem::setPaintRect(const QRectF &rect)
{
paintRect = rect;
update();
}
CustomItem::paint(QPainter *painter /* etc. */)
{
painter->fillRect(paintRect, brush);
}
Related
This is my first time using Qt and I have to make a MSPaint equivalent with Qt. I am however having trouble with painting my lines. I can currently draw a line by clicking somewhere on the screen and releasing somewhere else, however when I draw a second line the previous line is erased. How could I keep the previously painted items when painting another item?
void Canvas::paintEvent(QPaintEvent *pe){
QWidget::paintEvent(pe);
QPainter p(this);
p.drawPicture(0,0,pic);
}
void Canvas::mousePressEvent(QMouseEvent *mp){
start = mp->pos();
}
void Canvas::mouseReleaseEvent(QMouseEvent *mr){
end = mr->pos();
addline();
}
void Canvas::addline()Q_DECL_OVERRIDE{
QPainter p(&pic);
p.drawLine(start,end);
p.end();
this->update();
}
Canvas is a class that derives QWidget, it has 2 QPoint attributes start and end.
Class body:
class Canvas : public QWidget{
Q_OBJECT
private:
QPoint start;
QPoint end;
QPicture pic;
public:
Canvas(){paint = false;setAttribute(Qt::WA_StaticContents);}
void addline();
protected:
void paintEvent(QPaintEvent *);
void mousePressEvent( QMouseEvent * );
//void mouseMoveEvent( QMouseEvent * );
void mouseReleaseEvent( QMouseEvent * );
};
QPicture records QPainter commands. Also from its documentation you can read this:
Note that the list of painter commands is reset on each call to the
QPainter::begin() function.
And the QPainter constructor with a paint device does call begin(). So each time the old recorded commands are deleted.
It may sound tempting to use it, since it does say a few good things, for example, that it is resolution independent, but this is not how drawing applications work in reality. Switch to a QPixmap and your drawings will persist.
Also, don't forget to initialize the pixmap, because by default it will be empty and you will not be able to draw on it.
Canvas() : pic(width,height) {...}
Furthermore, if you would like the introduce the concept of brushes as in artistic brushes and not QBrush, you might want to look at this approach to draw the line.
EDIT: Note that you should be able to prevent QPicture from losing its content by not calling begin() on it more than once. If you create a painter, dedicated to only drawing on it at class scope, and call begin in the constructor, different recorded drawing operations should persist. But as their number increases it will take more and more time to draw the QPicture to your widget. You could come around that by using both a QPicture and a QPixmap, and draw to both, use the picture to record the actions and the pixmap to avoid continuously redrawing the picture, even though you will do double the work it will still be more efficient, while you still retain the possibility to use the picture to re-rasterize in a different resolution or save the drawing history. But I doubt QPicture will do well as your drawing application begins to take shape of an actual drawing application, for example when you start using pixmap brushe stencils and such.
The main window of my Qt/C++ program looks like this:
As you can see on the picture, the window consists essentially of:
a menu on the left
two "canvases"
What I want is straightforward to describe: I want that under resizing of the window, both canvases take as much space as possible, but still remain squares (width = height). I've been trying to achieve that unsuccessfully.
Let me quickly describe the objects:
The window is a class Window that I created deriving QWidget. It has a QGridLayout for a layout.
The window's layout has three widgets: the left menu LeftMenu *menu, and the canvases Canvas *leftCanvas, *rightCanvas. Both LeftMenu and Canvas are custom classes deriving QWidget.
(NB: the left menu actually consists of 3 different widgets (submenus), and the window also has a status bar and a top menu, but I don't think it matters for my question.)
I have been "playing" (not having fun the least bit) with QSizePolicy's etc to try to get the Canvases' sizes to behave like I want (be as large as possible inside the window, but keep height/width ratio = 1), unsuccessfully. Let me describe my latest attempt in case that is useful for you (if you already know a solution to my problem, you don't have to keep reading):
I overrode the methods heightForWidth(), sizeHint() and minimumSizeHint() for Canvas like so:
class Canvas : public QWidget
{
Q_OBJECT
friend class Window;
public:
explicit Canvas(Window* window);
...
private:
void resizeEvent(QResizeEvent *resizeEvent) override;
int heightForWidth(int width) const override {return width;}
QSize sizeHint() const override
{
int size = std::min(width(), height());
return QSize(size, size);
}
QSize minimumSizeHint() const override {return QSize(200,200);}
...
};
And the constructor of my class Window looks like (a bit simplified):
Window::Window(ActionHandler *handler)
{
leftMenu = new LeftMenu(this);
leftMenu->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
leftCanvas = new Canvas(this);
rightCanvas = new Canvas(this);
QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Maximum);
policy.setHeightForWidth(true);
leftCanvas->setSizePolicy(policy);
rightCanvas->setSizePolicy(policy);
layout = new QGridLayout;
layout->setColumnMinimumWidth(0, menuWidth());
layout->addWidget(leftMenu, 0, 0);
layout->addWidget(leftCanvas, 0, 1);
layout->addWidget(rightCanvas, 0, 2);
setLayout(layout);
}
My idea was that as long as the width of the canvases is the limiting factor, the sizePolicy of the canvases should be (QSizePolicy::Expanding, QSizePolicy::Maximum). And as soon as the height of the canvases becomes the limiting factor, I would change the sizePolicy of the canvases (probably in Canvas::resizeEvent()) to the opposite: (QSizePolicy::Maximum, QSizePolicy::Expanding). Does that sound too complicated?
Anyway, it already fails, and I don't understand why. If I shrink the window horizontally it gives me this:
So, the height of the canvases does not shrink. I do not understand this behavior. In the Qt documentation (http://doc.qt.io/qt-4.8/qsizepolicy.html#Policy-enum), I read:
QSizePolicy::Maximum The sizeHint() is a maximum. The widget can be shrunk any amount without detriment if other widgets need the space (e.g. a separator line). It cannot be larger than the size provided by sizeHint().
The behavior of my canvases here seems to contradict this: their height is larger than the height provided by their sizeHint(). (I did make sure by std::couting "live": the canvas sizeHint, its height, its sizePolicy and its hasHeightForWidth parameters).
I am a beginner in Qt, and I want to use QPainter.
My process is like this: I receive data coordinates (x,y) from the serial port, like (1,1), (2,3), etc. I want to draw these points in a window every time I receive data.
I see the QPainter is used in events, and just paints one time. How can I use it every time I receive data? Just like a have a signal DataCome() and a slot Paint().\
By the Way ,thx a lot to the Answer.Your advise is very Useful .
In short ,updata() or repaint() is work in this case .
I have another question .
Assume ,the serial port continuous to send the coordinate points to computer,
and I want to display all the point in the window. Is there some method ,I can leave those points came early on the window,and I just need to paint the new points?Like "hold on " in matlab. Or I need a container to store the coordinates ,and paint all of them very time.
I've set a quick example that will hopefully help you understand the mechanisms you need to utilize to accomplish your task.
It consists of a Listener class which listens for data and sends it to the Widget for drawing. In my example I've set it it up so that the data is randomly generated and sent on regular intervals using a timer, but in your case that will be your serial port data.
Since I assume what you want to do is a plot, you cannot use the paintEvent to draw single points, because each time it will show only one point and the points data will not accumulate, so you need to draw to a pixmap, which you just display in the paintEvent.
Here are the Widget and Listener classes:
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = 0) : QWidget(parent) {
resize(200, 200);
p = new QPixmap(200, 200);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.drawPixmap(0, 0, 200, 200, *p);
}
public slots:
void receiveData(int x, int y) {
QPainter painter(p);
painter.setBrush(Qt::black);
QPoint point(x, y);
painter.drawPoint(point);
data.append(point);
repaint();
}
private:
QPixmap *p;
QVector<QPoint> data;
};
class Listener : public QObject {
Q_OBJECT
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
};
... and main:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
Listener l;
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
w.show();
return a.exec();
}
So what happens is a random data will be generated every 200 msec, sent to the Widget, where it is added to the pixmap and the Widget is updated to show the new entry.
EDIT: Considering how small a point (pixel) is, you may want to draw small circles instead. You can also color the point based on its data values, so you can get a gradient, for example low values might be green, but the higher it gets it can turn yellow and finally red...
You also might want to add the received data to a QVector<QPoint> if you will need it later, this can be done in the receiveData slot.
Another thing that might be worth mentioning - in the example everything is in range 0-200, the data, the plot window - very convenient. In reality this won't be the case, so you will need to map the data to the plot size, which may be changing depending on the widget size.
Here is a template I commonly use to normalize values in some range. You may want to simplify it a bit depending on your requirements.
template <typename Source, typename Target>
Target normalize(Source s, Source max, Source min, Target floor, Target ceiling) {
return ((ceiling - floor) * (s - min) / (max - min) + floor);
}
Edit2: Added the data vector to store all the received points in numerical form.
QPainter can operate on any object that inherits from QPaintDevice.
One such object is QWidget. When one wants QWidget to re-render, you call repaint or update with the rectangular region that requires re-rendering.
repaint immediately causes the paintEvent to happen, whilst update posts a paintEvent on the event queue. Both these are slots, so it should be safe to hook them up to a signal from another thread.
Then you have to override the virtual method "paintEvent" and create a painter with the widget:
void MyWidget::paintEvent( QPaintEvent * evt )
{
QPainter painter( this );
//... do painting using painter.
}
You can look at the AnalogClock example that is distributed with Qt Help as example.
You use QPainter only in the paintEvent of a QWidget. You can do it like this:
Keep a list of received points as a member and in the paintEvent, traverse this list and paint the required points. When a new point is received, add it to the list and call widget->update(). This tells the widget to refresh itself, and the widget will call paintEvent when the time is right.
Create a QPixmap instance, then draw on that like this:
QPixmap pixmap(100, 100);
QPainter p(&pixmap);
// do some drawing
You can then do with the pixmap whatever you want: paint it in the paint event, write it to disk...
I'm learning about QPainter, and I've created a simple widget where each time the user clicks on the widget, a new circle appears at that point.
But Qt doesn't allow painting outside paintEvent, so each time I want to draw a new circle, I need to invalidate the widget area and redraw all the previous circles, too. That doesn't seem very efficient - what if there are hundreds or even thousands of elements.
It would be best if the previous circles weren't erased, and I just drew the new one on top of the widget. But on Qt I can't draw without first invalidating (and thus erasing) the previous content.
What is the recommended way of handling this situation in Qt?
The recommended way to handle that situation is to use a QGraphicsScene and QGraphicsView, and then populate the scene with QGraphicsItems. According to the docs, that is exactly what the framework is designed for.
In short, you would override QGraphicsScene::mousePressEvent(), and in the new method you would create a new QGraphicsEllipseItem.
There is no need to invalidate the entire widget. update() and repaint() can take coordinates that you want to repaint thus only re-drawing the part that changed.
void update ( int x, int y, int w, int h )
void update ( const QRect & rect )
void update ( const QRegion & rgn )
void repaint ( int x, int y, int w, int h )
void repaint ( const QRect & rect )
void repaint ( const QRegion & rgn )
So it appears that Qt4 doesn't let you draw on windows outside of a paint event. I have a lot of code that expects to be able to in order to draw rubber band lines (generic drawing code for a particular, proprietary interface that I then implement in the given UI). I've read about the pixmap method, it would be a lot of work and I don't think it's really what I want.
Is there a workaround that allows me to do what I want anyway? I just need to draw XOR bands on the screen.
Tried the WA_PaintOutsidePaintEvent flag. Then I saw the bit that says it doesn't work on Windows.
In modern compositing desktops window painting needs to be synchronized by the window manager so that the alpha blending and other effects can be applied, in order, to the correct back buffers - the result of which is then flipped onto the screen to allow tear-free window animations.
Invoking painting operations out-of-band of this process - while supported for legacy reasons on the underlying platforms - would subvert this process and cause a number of very non optimal code paths to be executed.
Basically, when you have painting to do on a window: Call the invalidate function to schedule the painting soon, and paint during the paint event.
Just paint to a QPixmap, and copy it to the real widget in the paintEvent. This is the only standard way. You shouldn't try to workaround it.
Seems like if you could get access to the Hwnd of the window in question, you could paint on that surface. Otherwise, I'm not sure. If by pixmap method you mean something like this, I don't think it's a bad solution:
m_composed_image = QImage(size, QImage::Format_ARGB32);
m_composed_image.setDotsPerMeterX(dpm);
m_composed_image.setDotsPerMeterY(dpm);
m_composed_image.fill(Qt::transparent);
//paint all image data onto new image
QPainter painter(&m_composed_image);
painter.drawImage(QPoint(0, 0), m_alignment_image);
As it's mentioned in one of the answers, The best way to do it will be to make a pixmap buffer. The painting works will be done in the buffer and when it's done, repaint() will be scheduled. And the paintEvent() function just paints the widget by copying the pixel buffer
I was trying to draw a circle on a widget area after user inputs values and pushes a button. This was my solution. connecting the drawCircle() slot to the clicked() signal.
class PaintHelper : public QWidget
{
Q_OBJECT
private:
QPixmap *buffer;
public:
explicit PaintHelper(QWidget *parent = 0) : QWidget(parent)
{
buffer=new QPixmap(350,250);// this is the fixe width of this widget so
buffer->fill(Qt::cyan);
}
signals:
public slots:
void drawCircle(int cx, int cy, int r){
QPainter painter(buffer);
painter.setBrush(QBrush(QColor(0,0,255)));
// A part of mid-point algorithm to draw 1/8 pacrt of circle
int x1=0,y1=r;
int p=1-r;
for(int i=0;y1>=x1;i++){
painter.drawPoint(x1+cx,y1+cy);
x1++;
if(p>0){
p+=3+x1;
}
else{
y1--;
p+=2*x1-2*y1;
p++;
}
}
this->repaint();
}
// QWidget interface
protected:
void paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(0,0,*buffer);
}
};