PDA

View Full Version : Clone QGraphicsView (master-slave display)



quimnuss
14th August 2015, 12:49
How could I display part of the QMainWindow's widgets to a slave window/dockwidget ?

Essentially I have two docked widgets and a view with a scene containing a geographical map in a pixmap, many painted paths and a marker of the current gps position in it. I would like to replicate what's displayed on the central view and one of the two docks. This replica would be in a different window of a different screen.

So far I've tried adding the main scene to a new QGraphicsView but of course zoom and position of the view is not preserved from master to slave. I could re-do any transformation, but it feels there should be a better way. I guess I could also create a secondary MainWindow and relate them somehow. I am unsure which choice is the most appropriate and efficient or if there are other possibilites. I've also seen it's possible to display screenshots of a window in a QLabel, but that wouldn't allow me to remove the extra dock and toolbars.

The only difference between master-slave is size and position and the removal of this one dock and (if possible) toolbars and so on. Any ideas on how to achieve this?

anda_skoa
14th August 2015, 14:02
Aside from showing the same scene in two views. you could try rendering the primary view into an image and display that image.

You'll have to experiement if calling render() on the graphicsview or its viewport() results in the content you want.

Cheers,
_

quimnuss
14th August 2015, 17:53
That sounds like another good possibility since I could more or less choose what to display without any more hassle.

I've messed a bit with QGraphicsView's render() but I can't but get a grey square.

The relevant parts of what I did are:


pixmap = new QPixmap(800,600);
painter2 = new QPainter(pixmap);

label = new QLabel();
label->setPixmap(*pixmap);
label->show();


And the following line gets called periodically:



scene->render(painter2, pixmap->rect(), ui->centralView->rect());


I've also tried putting the pixmap in a scene and that one on a QGraphicsView as well as rendering directly to a QDockWidget.


Added after 1 6 minutes:

I've discovered the function

qwidget.html#grab.html


QPixmap QWidget::grab(const QRect & rectangle = QRect( QPoint( 0, 0 ), QSize( -1, -1 ) ))

which makes the rendering way more easy than using 'render'.

I did this:


QRect slaveRect = ui->centralWidget->frameGeometry().united(ui->dockWidget_3->frameGeometry());
label->setPixmap(this->grab(slaveRect));


This is pretty close to what I wanted (maybe just use two labels instead of uniting the rects). I wonder if memory-wise is suitable, since we are creating pixmaps at every frame. I still feel there will be a better way of doing this. Cloning the two widgets or something.

d_stranz
14th August 2015, 23:30
I wonder if memory-wise is suitable, since we are creating pixmaps at every frame.

The QLabel will make a copy of whatever pixmap you pass into it, and QWidget::grab() creates a new pixmap with every call, so you have no choice in the matter. With the code as you are doing it now, the pixmap returned by the grab exists for only as long as the call to setPixmap so you only briefly use space for two pixmaps.

For the sake of efficiency, it makes no sense to grab the pixmap with every paintEvent() if the contents of the scene haven't changed. You should probably connect a slot to QGraphicsScene::changed() and do your grab there.

The code you posted earlier, specifically this line:

pixmap = new QPixmap(800,600);

causes a memory leak because you don't delete the pixmap once you load it into the label.

quimnuss
15th August 2015, 01:10
Thanks d_stranz.

The setPixmap is done already in a slot and the resize events. I am concerned because this slot gets called every quarter of a second and updates the scene with new information that changes the display. So I guess there isn't much that can be done about that, althought as you pointed out, it's not a memory problem but more of a allocating-dealocating problem, which hopefully will be handled properly behind the scenes.

For now I am happy :) thank you all for the help.

I finally splitted the two required widgets into two labels/pixmaps and now I'm struggling to get them to expand, keep aspect ratio and optimize the window space on a windowed QWidget, but that's another matter.

quimnuss
23rd September 2015, 14:31
Hello everyone,

I am coming back to this because the grab function isn't fast enough. It consumes 70% of the processing time and on slow computer forces me to skip frames. And I was also forced to activate FullViewportUpdate because the grab misleads the repaint of the grabbed widget and glitches appear (see my other post for details on that).

How could I achieve a similar result with better performance? Can a scene have two parents or is there a way to have a clone of the scene? If I do that, I guess I'll have to manually keep track of the current scrollbar values so I am sure that they are displaying the same, correct?

All suggestions are welcome.

anda_skoa
23rd September 2015, 14:59
And the following line gets called periodically:



scene->render(painter2, pixmap->rect(), ui->centralView->rect());


Do you call begin()/end() properly before/after each drawing attempt?
Do you set the label's pixmap after drawing?


Can a scene have two parents or is there a way to have a clone of the scene?

The scene's parent is just for the QObject model, for automatic deletion with the parent.
The QObject parent has not impact at all on what the scene does.

If you mean two views, then yes, the view gets the scene as an argument to its setScene() method.
It doesn't own or control the scene.

Cheers,
_

quimnuss
23rd September 2015, 16:14
About render:
I am currently using grab instead render(). I didn't manage to get render to work properly, but if you say it would help, I'll retry. I believe grab might ultimately use render, do you happen to know? Because if that's the case it won't improve the performance.

About having two views of one scene, I will try that and see how it goes. I presume I will have to manually set the scroll position and scale, or maybe I can link a signal of the main qgraphicsview to the slave one, I'll check that out.

Added after 22 minutes:

Two views of the scene works, thanks! The slave view does not share the position, scale and rotation of the other view, but I guess I can call the function for each view or check if it's convenient to rotate/translate the scene.

Actually it would be better to share the View itself. Is it possible to add the QGraphicsView to another widget without taking ownership? Adding it with AddWidget I guess reparents since then the main window loses that scene.

And the second problem are the labels outside the scene. Do I have to create a replica and update them as well or is there an alternate way? Setting the same widget in two places would come handy here.

But, all in all, the best would be to render the widget twice. If grab does something else than render and therefore render won't make the performance hit that grab does, I will totally go for that approach since I won't have to duplicate code and I'll ensure slave displays the same as master.

Added after 36 minutes:

From the qwidget source here (https://github.com/Vitallium/qt5/blob/master/qtbase/src/widgets/kernel/qwidget.cpp), the grab function:


QPixmap QWidget::grab(const QRect &rectangle)
{
Q_D(QWidget);
if (testAttribute(Qt::WA_PendingResizeEvent) || !testAttribute(Qt::WA_WState_Created))
sendResizeEvents(this);

const QWidget::RenderFlags renderFlags = QWidget::DrawWindowBackground | QWidget::DrawChildren | QWidget::IgnoreMask;

const bool oldDirtyOpaqueChildren = d->dirtyOpaqueChildren;
QRect r(rectangle);
if (r.width() < 0 || r.height() < 0) {
// For grabbing widgets that haven't been shown yet,
// we trigger the layouting mechanism to determine the widget's size.
r = d->prepareToRender(QRegion(), renderFlags).boundingRect();
r.setTopLeft(rectangle.topLeft());
}

if (!r.intersects(rect()))
return QPixmap();

QPixmap res(r.size());
if (!d->isOpaque)
res.fill(Qt::transparent);
render(&res, QPoint(), QRegion(r), renderFlags);

d->dirtyOpaqueChildren = oldDirtyOpaqueChildren;
return res;
}

Since it uses render utimatelly, I guess it's not worth trying to use it to improve performance, right?

anda_skoa
24th September 2015, 10:46
Actually it would be better to share the View itself. Is it possible to add the QGraphicsView to another widget without taking ownership? Adding it with AddWidget I guess reparents since then the main window loses that scene.

A widget can only always be in one place.



Since it uses render utimatelly, I guess it's not worth trying to use it to improve performance, right?
Right.

What you could try is not using QGraphicsView to render the scene.
Instead render the scene to a pixmap and then displaying the same pixmap twice, probably in your own custom widget.

Cheers,
_

quimnuss
24th September 2015, 11:04
Thanks for the support anda_skoa,

I imagine creating the pixmap would be costly again, even if I use it twice afterwards.

It is surprising, but apparently it's cheaper to recreate the widgets than grabbing a pixmap for display. I've created a branch to test this by duplicating the labels and the scene. We'll see. ATM it stands true for the main scene, let's see what happens when I add the second scene I have and the other widgets.

I don't know, but maybe creating a pixmap prevents Qt to be smart on the rendering. It's still surprising.

anda_skoa
24th September 2015, 14:01
I imagine creating the pixmap would be costly again, even if I use it twice afterwards.

The graphics view would have to render the scene as well, rendering into a pixmap can't be more costly.

Cheers,
_

quimnuss
24th September 2015, 14:16
Exactly my thoughts, but it seems like it is like this for a reason I can't understand. So far I've only tested with just the main scene and not the rest. That is, adding the scene to the slave viewport, without the turning. The bottleneck disappeared without noticeable effect.

I will now compare using grab versus adding a view+scene with the turning of the scene. If the results are significantly different, we have a mystery.

anda_skoa
24th September 2015, 17:58
Does QGraphicsScene have a "grab" method?
Or do you mean using render() to "grab" a pixmap?

Cheers,
_

quimnuss
24th September 2015, 18:58
No, QGraphicsView does, or the QWidgets.

I didn't understand the second question...

What I want to try now is to compare calling ->grab on the QGraphicsView and show the pixmap in a label versus setting the scene in a view. To be fair, the view has to rotate as I rotate the main View. (on second thought, maybe I should rotate the scene, so I don't have to worry about it, will do).

anda_skoa
25th September 2015, 12:14
No, QGraphicsView does, or the QWidgets.

Right, hence my puzzlement.

If you have a QGraphicsView then it will already render the scene, grabbing it might just render it a second time.


I didn't understand the second question...

Well, I thought the idea was to only render the scene once.
Which means, as suggested in comment #9, to render the scene into a pixmap and displaying the pixmap twice.
Since you wrote "grab" I was confused since QGraphicsScene does not have such a method, so I wondered if you just used "grab" to mean "render to pixmap".



What I want to try now is to compare calling ->grab on the QGraphicsView and show the pixmap in a label versus setting the scene in a view.
Ok.
If you think that rendering the scene and grabbing the view is what generates the overhead, you can still try to render the scene into a pixmap and displaying that twice.

Cheers,
_

quimnuss
25th September 2015, 13:55
Aah ok now I get what you were saying, correct me if I misunderstood. You are saying that it might be the render() inside the grab() of the QWidget which is slow, but not the render() of the scene alone, because the former causes the overhead. Might be that the process of rendering the widget in addition of a scene (add save it into a pixmap?) is different and extremely costly. Because we've seen that grab ultimately calls render(), it's just that it is an arbitrary region render.

What I observed; rendering the scene twice in two QGraphicsView is fast. For a recap, what I wish to achieve is to clone two QGraphicsView and several labels. With no interaction on the slave, only window resize keeping aspect ratio. What's displayed in the scenes of the slave windowed widget has to be the same (scale, position) than the master window's scenes.

THe first approach was to use grab() on the united rect of the main QGraphicsView plus the other widget that contains another QGraphics view, a qcustomplot and several labels. That works perfect but it's too slow.

The second approach is to create another form with the same graphic elements and share the two scenes and the qcustomplot. At each update update the labels in the main window and in the slave window. I still have to figure out how to duplicate a qcustomplot content. Also, this approach has the problem that currently I scale and rotate the main QGraphicsView to rotate and scale all the elements in the scene. On the replica, I have to reapply those transformations. Also, when scaled (zoomed in) the slave window content might differ if the window sizes are different. I'd rather avoid that, but it would be acceptable.

I will soon implement the second approach, given that the first one didn't cut it. Do you suggest that the appropriate call to render could get me the main scene (+ scale, position and rotation), the other scene, the qcustomplot and the labels?

anda_skoa
25th September 2015, 14:24
You are saying that it might be the render() inside the grab() of the QWidget which is slow, but not the render() of the scene alone, because the former causes the overhead.

No. What I was saying is that you could render the scene to a pixmap and display that pixmap twice.
There is no QGraphicsView or grab used anywhere in this case.



What I observed; rendering the scene twice in two QGraphicsView is fast.

Ah, ok, then you don't need to do anything for that anyway.

Cheers,
_

quimnuss
25th September 2015, 14:40
No. What I was saying is that you could render the scene to a pixmap and display that pixmap twice.


The rendering of the scene is fast, it's when I use the grab method that ultimately does a render _on the widget_ that becomes slow. There's something different on rendering a scene and rendering a widget with a scene. The first is quick, the second is slow. That is, unless there's something wrong with the grab method of QWidget.

I believe you understood that the rendering of the scene is slow and you suggested that I could cut the time in half by displaying a rendered pixmap twice. But that might not be the case. The 'grab' method which I use to get a pixmap that I set on a QLabel in a slave QWidget takes 90% of the computing time, if it were the render the problem, it should be at most 50%, given that there's the mainwindow render first. Then, doing what you suggested would work. But it's 90%.

I don't know why the grab() method is so expensive... I still can't make sense of it. Nor I know how to provoke a second render efficiently. I discarded the idea of trying to re-render() myself because that's what grab() does. I would like that to be wrong because it's much more convenient to re-render() a widget than to set a new form and share the scenes and other elements and manually sync their state.

anda_skoa
25th September 2015, 17:33
You would always have to synchronize the other elements if the view is just one of many widgets that need to be in both windows.

Since viewing the scene twice is fast there is no need to share the rendering of the scene.

Cheers,
_

quimnuss
25th September 2015, 17:50
You would always have to synchronize the other elements if the view is just one of many widgets that need to be in both windows._

Not if I use something like grab() on the Rect() that takes all the involved widgets. I'd get a windowshot of the involved parts with no need to sync anything but the shot pixmap. If only it were fast...



Since viewing the scene twice is fast there is no need to share the rendering of the scene.


Sure, but if I could share the rendering of the three QWidgets (and not solely the scene) efficiently it would simplify things a great deal. I still haven't figure out how to 'view' a qcustomplot and widget labels.