Embedding a Graphics View in a Scene
-
Hi All. I am relatively new to Qt and I have having an issue with QGraphicsView and QGraphicsScene. I have read the documentation and looked at a log of posts in the this forum and other forums, but I cannot quite find what I need. I have using Qt 5.14.0 on RedHat Linux 8.2. I build Qt and QtCreator myself to get a 32-bit version.
I have a picture of what I need to help with my description. I am updating and modernizing an older system so the look and feel needs to be similar to what we had before.
I am creating a customer area to plot points in NMI vs FT. I have a QGraphicsArea with a QGraphicsScene. I set the scene size to be same size as my graph area with a little extra room for my y-axis. I thought that would be easiest for plotting the points to just use the scene size. The screen area is -1.7 to 20 NMI by -200 FT to 16000 FT. The QGraphicsArea is added to a QWidget with a horizontal layout. The QWidget was setup through the Designer in QCreator. The QGraphicsArea is added programmatically. The QGraphicsArea is a custom GraphicsArea. The resizeEvent and showEvents has been overridden to ignore the aspect ratio and fit in view.
fitInView(scene()->sceneRect(), Qt::IgnoreAspectRatio);
The QGraphicsArea and QGraphicsScene with axis lines are as follows. I did look into using Charts, QCustomPlots, and QWT, but it did not match the look I needed.
elDisplayGraphicsView = new SceneSizeGraphicsView(); ui->elControllingAreaWidget->layout()->addWidget(elDisplayGraphicsView); elDisplayGraphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); elDisplayGraphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); elDisplayGraphicsView->setRenderHint(QPainter::Antialiasing); // Create the scene view for the default setup elDisplayScene = new QGraphicsScene(-1.7,-2000., 21.7,18000); //NMIxFT elDisplayGraphicsView->setAlignment(Qt::AlignBottom|Qt::AlignLeft); // Create x-axis with defaults xElAxisLineItem = new QGraphicsLineItem(0., 0., 20., 0.); xElAxisLineItem->setPen(axisMarkersPen); elDisplayScene->addItem(xElAxisLineItem); xElAxisLineItem->setPos(0,0); // Set the x-axis tick marks for (int xTickMarks = 0; xTickMarks <= 20; xTickMarks++) { QGraphicsLineItem *xTopTickMarkLine = new QGraphicsLineItem(0, -10, 0, 10, xElAxisLineItem); xTopTickMarkLine->setPen(axisMarkersPen); xTopTickMarkLine->setFlag(QGraphicsItem::ItemIgnoresTransformations); xTopTickMarkLine->setPos(xTickMarks, 0); QGraphicsTextItem *xTickMarkText = new QGraphicsTextItem(QString::number(xTickMarks),xTopTickMarkLine); xTickMarkText->setFont(defaultFont); xTickMarkText->setDefaultTextColor(palette().alternateBase().color()); xTickMarkText->setFlag(QGraphicsItem::ItemIgnoresTransformations); if (xTickMarks < 10) xTickMarkText->setPos(XTICKTEXTSINGLEDIGITXCOORD, XTICKTEXTYCOORD); else xTickMarkText->setPos(XTICKTEXTDOUBLEDIGITXCOORD, XTICKTEXTYCOORD); } // Create y-axis with as larger as it will go yElAxisLineItem = new QGraphicsLineItem(0., ELMINFEET, 0., ELMAXFEET); yElAxisLineItem->setPen(axisMarkersPen); elDisplayScene->addItem(yElAxisLineItem); yElAxisLineItem->setPos(-0.7,0); vector<int> yTickMarksScale = elevationScaleMap[elevationRange[5]]; for (int yTickMarks : yTickMarksScale) { QGraphicsLineItem *yTickMarkLine = new QGraphicsLineItem(-10, 0, 10, 0, yElAxisLineItem); yTickMarkLine->setPen(axisMarkersPen); yTickMarkLine->setFlag(QGraphicsItem::ItemIgnoresTransformations); yTickMarkLine->setPos(0, yTickMarks); QGraphicsTextItem *yTickMarkText = new QGraphicsTextItem(QString::number(yTickMarks),yTickMarkLine); yTickMarkText->setFont(defaultFont); yTickMarkText->setDefaultTextColor(palette().alternateBase().color()); yTickMarkText->setFlag(QGraphicsItem::ItemIgnoresTransformations); // Add FT to the top. Move top and bottom values to be seen. // Top value is always the last value in the vector if (yTickMarks == yTickMarksScale.back() || yTickMarks == yTickMarksScale.front()) { if (yTickMarks == yTickMarksScale.back()) { yTickMarkText->setPos(YTICKTEXTFIVEDIGITXCOORD, YTICKTEXTYCOORD + 5); QGraphicsTextItem *yTickMarkFtText = new QGraphicsTextItem("FT",yTickMarkLine); yTickMarkFtText->setFont(defaultFont); yTickMarkFtText->setDefaultTextColor(palette().alternateBase().color()); yTickMarkFtText->setFlag(QGraphicsItem::ItemIgnoresTransformations); if (yTickMarks >= 10000) yTickMarkFtText->setPos(YTICKFTTEXTFIVEDIGITXCOORD - 6, YTICKTEXTYCOORD + 20); else if (yTickMarks >= 1000) yTickMarkFtText->setPos(YTICKFTTEXTFIVEDIGITXCOORD - 12, YTICKTEXTYCOORD + 20); else if (yTickMarks <= -1000) yTickMarkFtText->setPos(YTICKFTTEXTFIVEDIGITXCOORD - 8, YTICKTEXTYCOORD - 20); else yTickMarkFtText->setPos(YTICKFTTEXTFIVEDIGITXCOORD - 14, YTICKTEXTYCOORD - 20); } else yTickMarkText->setPos(YTICKTEXTFIVEDIGITXCOORD, YTICKTEXTYCOORD - 5); } else yTickMarkText->setPos(YTICKTEXTFIVEDIGITXCOORD, YTICKTEXTYCOORD); } elDisplayGraphicsView->setScene(elDisplayScene); elDisplayGraphicsView->scale(1,-1);
Now to my problem. When a user selects a target, it needs to shown in another small graph area on top of the existing QGraphicsArea/QGraphicsScene. The target will be shown in comparison to the blue line (which I have not coded yet). The small graph area goes from (-1000FT ,-500FT , 1000FT , 500FT). I want to be able to create another QGraphicsArea, so I can create another QGraphicsScene that I can plot the points based on the scene size. Pet the Embedded Widget Support section of the Graphics View Framework Qt Documentation. It says that QGraphicsArea can be embedded and scenes nested. I created a QGraphicsProxyWidget and added my new QGraphicsArea and QGraphicsScene with the scene coordinates, but it does not work correctly. The lines and location are not as expected. I know that QGraphicsProxyWidget works off the QGraphicsScene coordinates, but I thought the QGraphicsArea would use QGraphicsProxyWidget coordinates. I have tried different positions and sizes for the QGraphicsProxyWidget, but the lines still come up incorrect and there are weird gray lines on the sides.
QPen axisMarkersPen = QPen {Qt::red, 0}; QPen axisMarkersPen2 = QPen {Qt::blue, 0}; QGraphicsView *whiGraphicsView = new SceneSizeGraphicsView(); whiGraphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); whiGraphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); whiGraphicsView->setRenderHint(QPainter::Antialiasing); // Create the scene view for the default setup QGraphicsScene *whiScene = new QGraphicsScene(-1000,-500,2000,1000); //FTxFT // Add line to show offset from Glide Slope Line QGraphicsLineItem *offGlideSlopeLine = new QGraphicsLineItem(-1000, 0, 1000, 0); offGlideSlopeLine->setPen(axisMarkersPen); whiScene->addItem(offGlideSlopeLine); offGlideSlopeLine->setPos(0,0); offGlideSlopeLine->setFlag(QGraphicsItem::ItemIgnoresTransformations); // Add line to show offset from Center Line QGraphicsLineItem *offCenterLine = new QGraphicsLineItem(0, -500, 0, 500); offCenterLine->setPen(axisMarkersPen2); whiScene->addItem(offCenterLine); offCenterLine->setPos(0,0); offCenterLine->setFlag(QGraphicsItem::ItemIgnoresTransformations); // Add scene to graphics view whiGraphicsView->setScene(whiScene); widthHeightIndicator = elDisplayScene->addWidget(whiGraphicsView); widthHeightIndicator->setMinimumSize(5, 16000); widthHeightIndicator->setMaximumSize(5, 16000); widthHeightIndicator->setPos(0,0);
Any help or advice would be greatly appreciated. If you think there is a better approach for what I am trying to do, I am all for it. Also if you know way to keep the size/location of the QGraphicsProxyWidget from changing when I change the range or height of the scene, please let me know too. I know that you can stop the transformation for QGraphicsItems, but not widgets that I know of. I have seen this post https://stackoverflow.com/questions/18411250/draw-an-item-in-a-static-location-relative-to-the-qgraphicsview for keeping things in the same location on a scene, but I didn't think it would work for my situation.Thanks.
-
Hi and welcome to devnet,
Wouldn't it be simpler to simply have that second area on top of the main one rather than using a proxy item ?
It could also be a pixmap item that you painted with the data you want.
-
@SGaist Thanks for the reply. I am all about making this as simple as possible. How would I go about having a second area on top of the main one? I looked into this before, but I could not figure it out. Everything I read seemed to say that widgets should not overlap. Would I need to break the layout? I wanted to make sure that that my QGraphicsArea would always increase and decrease with the window size, so that is why I chose the GridLayout.
The data will be coming in continuously (at least 1 per second), so I would assume a pixmap would not be the right way to go in my case.
-
Most of the time you can use layouts and be done with it. However, in your case manually handling that top view might be simpler. The constraint is that it's up to you to do the positioning and resizing but in might be overall easier to achieve your goal.
-
@SGaist Thank you. I guess I got so into "I have to use layouts" and " Qt says don't overlap widgets" that I did not even think about not using them and doing things manually. I have been able to create what I need for my small graphing area. I still need to override the resize method for the main window to adjust the QGraphicArea to fill in the widget area when the main window changes size, which shouldn't be that bad (fingers crossed).
-
These are recommandation that apply to 90-95% of the time and then you have special cases like yours.
Technically speaking you are not overlapping you have some sort of OSD. A bit like the minimap is some video games which would rather be seen as an overlay.Not that you can mix the two, have you main widget managed with a layout because it does what you need and only handle your overlay manually. That can simplify your code as well.