we all know this Warning from
bool QPainter::begin(QPaintDevice * device)
Warning: A paint device can only be painted by one painter at a time.
http://doc.qt.io/qt-5/qpainter.html#begin
But what if I have two object sharing one pixmap, and one object Bar contains other object Foo.
class Foo
{
public:
QPixmap* barPixmap;
void draw()
{
QPainter painter(barPixmap);
painter.drawText(0,0,"FooText");
}
}
class Bar
{
public:
QPixmap* barPixmap;
Foo* fooObject;
}
and I got something like this
Bar::paintEvent(QPaintEvent* )
{
QPainter painter(barPixmap);
painter.drawText(50,50,"BarText");
fooObject->draw();
}
Is it multiple drawing? Compiler throws nothing and code seems working.
The warning tells you about creating multiple QPainters at one time. Since all paint events are processed in the main thread, they are processed consequently. As long as QPainter object is destroyed at the end of your event handler, the warning will not appear. Multiple consequent paintings on one device is fine.
However the architecture is questionable. For instance, if multiple widgets are painted this way, one of widgets will display old version of pixmap while second widget will display updated version. This inconsistency can be a problem. Putting any logic in paint event handlers is generaly pointless (and sometimes harmful). You should change pixmap when available data changes and just paint it in the paint event.
Related
I'm a beginner in programming in C++/Qt.
I created a widget called wexample in Qt. When displayed, there is an button event that will open another window of the same widget wexample, and so on. My question is how to close all the windows of that widget?
I open my first widget as follows:
wexample *w = new wexample;
w-> show();
Inside the widget, I also have these events:
void wexample::on_pushButton1_clicked()
{
wexample *w = new wexample;
w -> show();
}
void wexample::on_pushButton2_clicked()
{
QWidget::close();
}
So when button 1 is pressed, it will open a new window and so on. When button 2 is pressed, it will close the window where the button is. Is there a way to close all of the windows from that widget all at once? Or even better, is there a way to close specific windows (for example, all the windows after the 3rd one)?
I have tried using signal and slot but I can't connect them since they are all of the same name. I would have to declare all of the widgets beforehand for it to work but I cannot know how many of them the user will need.
I'm sorry if my question isn't clear. I am really a beginner and have been searching for a while but couldn't find an answer. Maybe the structure as a whole doesn't make sense. Please help. Thanks.
You should add your newly created wexample's to a list and then iterate through them when you'd like to close them:
class wexample : public QDialog
{
...
private Q_SLOTS:
void on_pushButton1_clicked() {
wexample *w = new wexample(this);
m_wexamples.append(w);
w->show();
}
void wexample::on_pushButton2_clicked() {
foreach (wexample *w, m_wexamples)
w->close();
}
private:
QList<wexample*> m_wexamples;
};
A few extra points here:
Notice that I added (this) to the wexample constructor above. This ensures that the dialog is properly parented (and therefore will be cleaned up if you don't manually delete the object yourself). Previously, you would have been leaking memory every time you showed the dialog
The slot for the second button simply iterates through the list of dialogs and closes them. This does NOT mean that the dialogs have been deleted (and in fact are still in the list after closing them). If you'd prefer to fully remove them, then use of the "take" methods of QList, removing the dialog from the list and then call dialog->close(); dialog->deleteLater(); on it
This is a somewhat contrived example, you probably won't be opening the dialog from within the dialog's implementation in practice
I have 2 classes: one maintains some loop (at leas for 2-3 minutes; and is inherited from QObject) and another shows up a progress dialog (inherited from QDialog).
I want to start the loop as soon as the dialog is shown. My first solution was:
int DialogClass::exec()
{
QTimer::singleShot(0, LoopClassPointer, SLOT(start()));
return __super::exec();
}
There is a problem with throwing exceptions from slots. so I considered a possibility to make public slot start() just a public function. But now I don't know how to make it works well. Things like this:
int DialogClass::exec()
{
LoopClassPointer->start();
QApplication::processEvents();
return __super::exec();
}
don't help. The dialog doesn't appears.
Is there a common approach to this kind of situations?
some details, according to questions:
I have to work with system with its own styles, so we have a common approach in creating any dialogs: to inherit them from stytle class, which is inherited from QDialog.
my 'LoopClassPointer' is an exported class from separate dll (there is no UI support in it).
I have a 'start' button in main app, which connected with a slot, which creates progress dialog and 'LoopClassPointer'. at the moment I send 'LoopClassPointer' instance in the dialog and don't whant to make significant changes in the architecture.
Take a look at QtDemo->Concurrent Programming->Run function
e.g. in Qt 4.8: http://qt-project.org/doc/qt-4.8/qtconcurrent-runfunction.html
In this situation, I recommend you separate the logic of the loop from the dialog. Gui elements should always be kept separate.
It's great that your worker class is derived from QObject because that means you can start it running on a separate thread: -
QThread* m_pWorkerThread = new QThread;
Worker* m_pWorkerObject = new Worker; // assuming this object runs the loop mentioned
// Qt 5 connect syntax
connect(m_pWorkerThread, &QThread::started, m_pWorkerObject, &WorkerObject::start);
connect(m_pWorkerThread, &QThread::finished, m_pWorkerThread, &QThread::deleteThis);
m_pWorkerObject->moveToThread(m_pWorkerThread);
m_pWorkerThread->start();
If you're not familiar with using QThread, then start by reading this.
The only other thing you require is to periodically send signals from your worker object with progress of its work and connect that to a slot in the dialog, which updates its display of the progress.
I have my own classes (MyGraphicsScene, MyGraphicsView, MyGraphicsItem) derived from QGraphicsScene, QGraphicsView and QGraphicsItem.
In my main window, I then create nine (9) instances of MyGraphicsScene, shown through nine instances of MyGraphicsView. All nine MyGraphicsScenes contain pointers to each other.
How can I drag an instance of MyGraphicsItem in one MyGraphicsScene, and then automatically drag certain MyGraphicsItem instances (in the eight remaining MyGraphicsScenes) for the same amount/distance/vector?
My first idea was to reimplement MyGraphicsItem::itemChange (with change == QGraphicsItem::ItemPositionChange) and then call setPos() for remaining instances of MyGraphicsItem (contained within other MyGraphicsScenes). However this won't work because I will get infinite recursion (setPos() would trigger itemChange() as well, including for the originating MyGraphicsItem).
Any other ideas from experienced Qt-ers?
I do know for a fact you can use the same QGraphicsScene in multiple views. So if you're just trying to have multiple views of the same thing, call MyGraphicsView all with the same scene.
If you define derived class for QGraphicsItem (I think you already have, right?) and reimplement the mouseMoveEvent so it calls the move method to all 9 objects.
Remember that you have at your disposal all the attributes of QMouseEvent, e.g. buttons(), pos().
void MyGraphicsItem::mouseMoveEvent( QMouseEvent *event )
{
if (event->buttons() & Qt::LeftButton) {
QPoint pos = event->pos();
// ...
}
}
Hope that helped!
The easy way to create the long cross line cursor (as long as viewport) is create a cross line graphicsItem, when mouse moved, set the item's pos property.
But this way will be very slow when the scene is complex, because it should update the whole viewport to update the cursor's pos.
The another easy way is setCursor(QCursor(..)),use a QPixmap to define the long cross line, this way will very fast , but the cursor will exceed the viewport rect.
Is there another way to show a long cross line cursor fastly?
Thanks very much!
If I understand correctly, you want to draw an horizontal line and a vertical line, crossing at the cursor position, and being as large as the viewport is.
A poosible solution would be to reimplement QGraphicsScene::drawForeground() to draw the two lines with the painter.
The problem is that the scene doesn't know about the mouse position. This means the view will have to track it and inform the scene when the mouse position has changed.
To do that, you'll have to create your own GraphicsScene (inheriting QGraphicsScene) and your own GraphicsView (inheriting QGraphicsView).
On your GraphicsView constructor, you'll have to start mouse tracking. This will make your you receive a mouseMoveEvent each time the mouse moves inside the view :
GraphicsViewTrack::GraphicsViewTrack(QWidget* parent) : QGraphicsView(parent) {
setMouseTracking(true);
}
void GraphicsViewTrack::mouseMoveEvent(QMouseEvent* pEvent) {
QPointF MousePos = this->mapToScene(pEvent->pos());
emit mousePosChanged(MousePos.toPoint());
}
As you can see in the code snippet above, the view is emitting a signal (mousePosChanged) to which the scene will be connected. This signal contains the mouse position, converted to the scene's coordinates.
Now, on the scene side, you have to add a slot which will be called when the mouse position changed, store the new mouse position in a member variable and reimplement QGraphicsScene::drawForeground() :
void GraphicsSceneCross::drawForeground(QPainter* painter, const QRectF& rect) {
QRectF SceneRect = this->sceneRect();
painter->setPen(QPen(Qt::black, 1));
painter->drawLine(SceneRect.left(), m_MousePos.y(), SceneRect.right(), m_MousePos.y());
painter->drawLine(m_MousePos.x(), SceneRect.top(), m_MousePos.x(), SceneRect.bottom());
}
void GraphicsSceneCross::onMouseChanged(QPoint NewMousePos) {
m_MousePos = NewMousePos; // Store the mouse position in a member variable
invalidate(); // Tells the scene it should be redrawn
}
The last thing to do is connect the GraphicsView's signal to the GraphicsScene slot.
I'll let you check if this solution is acceptable performance wise.
Based on Jerome answer and using python I created this code in my QGraphicsScene subclass:
def drawForeground(self, painter, rect):
if self.guidesEnabled:
painter.setClipRect(rect)
painter.setPen(self.guidePen)
painter.drawLine(self.coords.x(), rect.top(), self.coords.x(), rect.bottom())
painter.drawLine(rect.left(), self.coords.y(), rect.right(), self.coords.y())
def mouseMoveEvent(self, event):
self.coords = event.scenePos()
self.invalidate()
It should be straighforward for you to write its appropiate C++ code. Note that I take advantage of the rect argument passed by Qt Api framework and I clip the painter to that
area, since it's the visible area to be drawn.
I also cache the pen object, since I realized in other experiments that creating objects while painting will penalty performace and doing it so you also give the user the chance to set a custom pen in your program options.
I have found a way to do this!
I develops under Windows system, so can use lower GDI api jumping out of Qt's painting system.
The detail is get the HDC of the QGraphicsView's viewPort. Then in the QMouseEvent of QGraphicsView use "MoveToEx" and "LineTo" drawing two lines on the viewport,then I should do is erase the "old" cursor, It's easy to do this using "setROP2(HDC dc,R2_NOT)",then draw the old Cursor stored again.
This method doesn't enter the QPainter system, so the GraphicsItems under the cursor will not be repaint.
To solve the filker problem when the mouse moves fast, I don't use "double buffer". I used QTimer to update the cursor at CPU's idle. The detail is in QMouseEvent ,don't update the cursor at time ,but store the position to a list, When CPU idle, draw the cursor at the list of positions
I wish this will help others who meets the same problem with me.
Thanks Jérôme, who gave me useful tip of QGraphicsScene.
I am working on QT GUI project. In this application I have a QWidget as main window. I make the cursor from data coming from some source. When I set the cursor of widget. It gives me the following error.
QPixmap: It is not safe to use pixmaps outside the GUI thread
My code is as follows
void ImageWindow::setMouseCursor(unsigned char* data,unsigned char* maskbits,unsigned int length,int xHotSpot, int yHotSpot)
{
QBitmap bitmapData;
QBitmap bitmapMaskData;
bitmapData.loadFromData(data,length);
bitmapMaskData.loadFromData(maskbits,length);
this->setCursor(QCursor(bitmapData,bitmapMaskData,xHotSpot,yHotSpot));
this->update();
}
Function setMouseCursor is called from other class, which set the data of cursor.
ImageWindow is my customized QWidget class.
Apparently, the object which calls setMouseCursor lives outside the GUI thread as far as i can tell. In order to avoid this, make setMouseCursor a slot. Do not call the slot directly, instead emit a signal from the caller object, and connect that signal to setMouseCursor slot using Qt::QueuedConnection.
See : ConnectionType
Two problems:
don't use a QBitmap outside the GUI-thread
don't call gui objects setCursor outside the GUI-thread
Creating a Paint Device
One advantage of using QImage as a
paint device is that it is possible to
guarantee the pixel exactness of any
drawing operation in a
platform-independent way. Another
benefit is that the painting can be
performed in another thread than the
current GUI thread.