Consider this class called Hex to draw a hexagon :
// Hex.h
#include <QGraphicsPolygonItem>
#include <QPointF>
#include <QVector>
#include <QImage>
#include <QBrush>
#include <QPen>
class Hex : public QGraphicsPolygonItem {
public:
// constructor
Hex(QGraphicsItem* parent = NULL);
// getters/setters
QPolygonF *getHexagon() {return hexagon;}
QBrush *getBrush() {return brush;}
protected:
QBrush *brush;
QPolygonF *hexagon;
};
and
// Hex.cpp
#include "hex.h"
Hex::Hex(QGraphicsItem *parent) {
// set Points
QVector<QPointF> hexPoints;
hexPoints << QPointF(1, 0) << QPointF(0, 1) << QPointF(0, 2)
<< QPointF(1, 3) << QPointF(2, 2) << QPointF(2 ,1);
// scale the poly
int SCALE_BY = 40;
for (size_t i = 0, n = hexPoints.size(); i < n ; i++) {
hexPoints[i] *= SCALE_BY;
}
//create a QPyolygon with the scaled points
hexagon = new QPolygonF(hexPoints);
// draw the polygon
setPolygon(*hexagon);
}
In main.cpp :
#include <QGraphicsView>
#include <QGraphicsScene>
#include "hex.h"
int main(int argc, char *argv[]){
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView();
Hex *hex0 = new Hex();
Hex *hex1 = new Hex();
hex0->setPos(400, 400);
hex1->setPos(300, 300);
scene->addItem(hex0);
scene->addItem(hex1);
scene->setSceneRect(0,0 , 1024, 768);
view->setScene(scene);
view->show();
return a.exec();
}
Ok. this is ok and setPos works fine.
But using addPolygon instead of addItem to set picture for the hexagons results in setPos not working and all the hexagons cover each other at the first coordinates given to them.
Like this :
// main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include "hex.h"
int main(int argc, char *argv[]){
QApplication a(argc, argv);
QGraphicsScene *scene = new QGraphicsScene();
QGraphicsView *view = new QGraphicsView();
Hex *hex0 = new Hex();
Hex *hex1 = new Hex();
hex0->setPos(400, 400);
hex1->setPos(300, 300);
// set picture for hexagons:
QBrush* brush = new QBrush(QImage("image.png"));
scene->addPolygon(*hex0->getHexagon(), QPen(Qt::black) ,*brush);
scene->addPolygon(*hex1->getHexagon(), QPen(Qt::black) ,*brush);
scene->setSceneRect(0,0 , 1024, 768);
view->setScene(scene);
view->show();
return a.exec();
}
Why does setPos() has this behavior and how to move the pictured hexagons.
Thanks in advance.
setPos () does not modify the QPolygonF but moves the item in scene coordinates, instead QPolygonF are drawn with respect to the internal coordinate system of the item. Therefore, if you want to observe the initial behavior, you have 2 options:
Move the QGraphicsPolygonItem:
QGraphicsPolygonItem *p0 = scene->addPolygon(*hex0->getHexagon(), QPen(Qt::black) ,*brush);
QGraphicsPolygonItem *p1 = scene->addPolygon(*hex1->getHexagon(), QPen(Qt::black) ,*brush);
p0->setPos(400, 400);
p1->setPos(300, 300);
Move the QPolygonF:
scene.addPolygon((*hex0->getHexagon()).translated(400, 400), QPen(Qt::black));
scene.addPolygon((*hex0->getHexagon()).translated(300, 300), QPen(Qt::black));
Your confusion comes from the fact that QGraphicsPolygonItem already has a QPolygonF and a QBrush member, you don't need extra ones.
Not only that, but addPolygon constructs a whole new QGraphicsPolygonItem:
Creates and adds a polygon item to the scene, and returns the item pointer. The polygon is defined by polygon, and its pen and brush are initialized to pen and brush.
I would suggest that Hex shouldn't be a class, it should be a function:
QGraphicsSceneItem * makeHex(QPointF pos, int scale = 40) {
QVector<QPointF> hexPoints{ QPointF(1, 0), QPointF(0, 1), QPointF(0, 2), QPointF(1, 3), QPointF(2, 2), QPointF(2 ,1) };
// scale the poly
for (auto & point : hexpoints) {
point *= scale;
}
auto * hex = new QGraphicsPolygonItem();
hex->setPolygon(hexPoints);
hex->setPos(pos);
hex->setBrush(QImage("image.png"));
return hex;
}
Or
QGraphicsSceneItem * addHex(QGraphicsScene *scene, int scale = 40) {
QVector<QPointF> hexPoints{ QPointF(1, 0), QPointF(0, 1), QPointF(0, 2), QPointF(1, 3), QPointF(2, 2), QPointF(2 ,1) };
// scale the poly
for (auto & point : hexpoints) {
point *= scale;
}
return scene->addPolygon(hexpoints, QPen(Qt::black), QImage("image.png"));
}
Related
I am originally loading image in QGraphicsView and using this method for basic zoom out and zoom in functionality.
However, I am unable to retrieve actual image pixel position using mapToScene functionality in eventFilter function of Graphics_view_zoom class. The below code produces behaviour exactly as windows photo viewer zooming only selected region.
MapToScene() returns same Point as mouse event position.
Here is the class which deals with zooming.
#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>
Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
: QObject(view), _view(view)
{
_view->viewport()->installEventFilter(this);
_view->setMouseTracking(true);
_modifiers = Qt::ControlModifier;
_zoom_factor_base = 1.0015;
}
void Graphics_view_zoom::gentle_zoom(double factor) {
_view->scale(factor, factor);
_view->centerOn(target_scene_pos);
QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
_view->viewport()->height() / 2.0);
QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
_view->centerOn(_view->mapToScene(viewport_center.toPoint()));
emit zoomed();
}
void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
_modifiers = modifiers;
}
void Graphics_view_zoom::set_zoom_factor_base(double value) {
_zoom_factor_base = value;
}
bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
QPointF delta = target_viewport_pos - mouse_event->pos();
// Here I want to get absolute image coordinates
if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
target_viewport_pos = mouse_event->pos();
target_scene_pos = _view->mapToScene(mouse_event->pos());
}
} else if (event->type() == QEvent::Wheel) {
QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
if (QApplication::keyboardModifiers() == _modifiers) {
if (wheel_event->orientation() == Qt::Vertical) {
double angle = wheel_event->angleDelta().y();
double factor = qPow(_zoom_factor_base, angle);
gentle_zoom(factor);
return true;
}
}
}
Q_UNUSED(object)
return false;
In mainwindow.cpp,
I am creating object of this class and loading an image as below:
m_GraphicsScene = new QGraphicsScene();
pixmapItem = new QGraphicsPixmapItem();
m_GraphicsScene->addItem(multiview[i].pixmapItem);
view_wrapper = new Graphics_view_zoom(ui->GraphicsView);
ui->GraphicsView->setScene(multiview[i].m_GraphicsScene);
pixmapItem->setPixmap(QPixmap::fromImage("img.jpg"));
multiview[view].m_GraphicsView->fitInView(QRectF(0,0,640,320),Qt::KeepAspectRatio);
Can anyone help with how do I achieve this ?
Keep in mind that the scaling you use only scales the scene, not the items. Given this, the position of the pixel can be obtained, so the algorithm is:
Obtain the mouse position with respect to the QGraphicsView
Transform that position with respect to the scene using mapToScene
Convert the coordinate with respect to the scene in relation to the item using mapFromScene of the QGraphicsItem.
Considering the above, I have implemented the following example:
#include <QtWidgets>
#include <random>
static QPixmap create_image(const QSize & size){
QImage image(size, QImage::Format_ARGB32);
image.fill(Qt::blue);
std::random_device rd;
std::mt19937_64 rng(rd());
std::uniform_int_distribution<int> uni(0, 255);
for(int i=0; i< image.width(); ++i)
for(int j=0; j < image.height(); ++j)
image.setPixelColor(QPoint(i, j), QColor(uni(rng), uni(rng), uni(rng)));
return QPixmap::fromImage(image);
}
class GraphicsView : public QGraphicsView
{
Q_OBJECT
Q_PROPERTY(Qt::KeyboardModifiers modifiers READ modifiers WRITE setModifiers)
public:
GraphicsView(QWidget *parent=nullptr): QGraphicsView(parent){
setScene(new QGraphicsScene);
setModifiers(Qt::ControlModifier);
auto item = scene()->addPixmap(create_image(QSize(100, 100)));
item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
item->setPos(40, 40);
fitInView(QRectF(0, 0, 640, 320),Qt::KeepAspectRatio);
resize(640, 480);
}
void setModifiers(const Qt::KeyboardModifiers &modifiers){
m_modifiers = modifiers;
}
Qt::KeyboardModifiers modifiers() const{
return m_modifiers;
}
signals:
void pixelChanged(const QPoint &);
protected:
void mousePressEvent(QMouseEvent *event) override{
if(QGraphicsPixmapItem *item = qgraphicsitem_cast<QGraphicsPixmapItem *>(itemAt(event->pos()))){
QPointF p = item->mapFromScene(mapToScene(event->pos()));
QPoint pixel_pos = p.toPoint();
emit pixelChanged(pixel_pos);
}
QGraphicsView::mousePressEvent(event);
}
void wheelEvent(QWheelEvent *event) override{
if(event->modifiers() == m_modifiers){
double angle = event->orientation() == Qt::Vertical ? event->angleDelta().y(): event->angleDelta().x();
double factor = qPow(base, angle);
applyZoom(factor, event->pos());
}
}
private:
void applyZoom(double factor, const QPoint & fixedViewPos)
{
QPointF fixedScenePos = mapToScene(fixedViewPos);
centerOn(fixedScenePos);
scale(factor, factor);
QPointF delta = mapToScene(fixedViewPos) - mapToScene(viewport()->rect().center());
centerOn(fixedScenePos - delta);
}
Qt::KeyboardModifiers m_modifiers;
const double base = 1.0015;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GraphicsView *view = new GraphicsView;
QLabel *label = new QLabel;
QObject::connect(view, &GraphicsView::pixelChanged, label, [label](const QPoint & p){
label->setText(QString("(%1, %2)").arg(p.x()).arg(p.y()));
});
label->setAlignment(Qt::AlignCenter);
QWidget w;
QVBoxLayout *lay = new QVBoxLayout(&w);
lay->addWidget(view);
lay->addWidget(label);
w.show();
return a.exec();
}
#include "main.moc"
It may be better to use a custom graphics scene subclassed from QGraphicsScene as this makes extracting the necessary coordinates much simpler. The only snag is you have to have the QGraphicsPixmapItem::pos available in the custom QGraphicsScene class - I have included a full working example which uses Graphics_view_zoom.h and Graphics_view_zoom.cpp from the linked question. The position of the QGraphicsPixmapItem is passed to a member of the QGraphicsScene subclass, Frame, in order to make the necessary correction.
#include <QPixmap>
#include <QGraphicsPixmapItem>
#include <QGraphicsTextItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <qfont.h>
#include "Graphics_view_zoom.h"
class Frame : public QGraphicsScene {
Q_OBJECT
public:
QGraphicsTextItem * coords;
QPointF pic_tl;
Frame::Frame(QWidget* parent)
: QGraphicsScene(parent) {
coords = new QGraphicsTextItem();
coords->setZValue(1);
coords->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
addItem(coords);
}
void Frame::tl(QPointF p) {
pic_tl = p;
}
protected:
void Frame::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
QPointF pos = event->scenePos();
coords->setPlainText("(" + QString("%1").arg(int(pos.x() - pic_tl.x())) + ", "
+ QString("%1").arg(int(pos.y() - pic_tl.y())) + ")");
coords->setPos(pos);
coords->adjustSize();
}
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QMainWindow* main = new QMainWindow();
QGraphicsView* GraphicsView = new QGraphicsView(main);
Graphics_view_zoom* view_wrapper = new Graphics_view_zoom(GraphicsView);
Frame* frame = new Frame(main);
QGraphicsPixmapItem* pixmapItem = new QGraphicsPixmapItem();
frame->addItem(pixmapItem);
GraphicsView->setScene(frame);
// Loads a 497x326 pixel test image
pixmapItem->setPixmap(QPixmap(":/StackOverflow/test"));
// small offset to ensure it works for pictures which are not at
// (0,0). Larger offsets produce the same result but require manual
// adjustments of the view, I have neglected those for brevity as
// they are not in the scope of the question.
pixmapItem->setPos(-20, 20);
frame->tl(pixmapItem->pos());
GraphicsView->fitInView(QRectF(0, 0, 640, 320), Qt::KeepAspectRatio);
GraphicsView->centerOn(pixmapItem->pos());
main->resize(1920, 1080);
main->show();
GraphicsView->resize(main->width(), main->height());
return a.exec();
}
This will display the image coordinates of the pixel under the mouse relative to (0,0) at the top left corner.
The mouse is not visible in these screenshots but in the first it is in exactly the upper left corner and the second it is in exactly the lower right corner. If these coordinates are needed inside the Graphics_view_zoom object then you simply have to scope the Frame instance appropriately, or pass the value as needed.
Note - the exact coordinates displayed may not precisely represent the position of the mouse in this example since they are cast to ints for demonstration, but the floating point values can be easily accessed since QGraphicsSceneMoveEvent::scenePos() returns a QPointF. Additionally, note that in running this demonstration there may be some (hopefully very small) variation on where the mouse appears to be relative to it's 'actual' position - I recommend using Qt::CrossCursor to allay this. For example on my system the default cursor is off by about a pixel for certain areas on my smaller display, this is also affected by the zoom level - higher zoom will produce more accurate results, less zoom will be less accurate.
I'm trying to animate a line around a fixed point in Qt. I assume I need to use the QPropertyAnimation class to do this but cannot figure out which property to use.
For clarity, here's what I'm trying to do.
| (5, 10)
| /
| /
| /
| / (10, 5)
| / .
| /
| /
|/
|--------------------------
^
|---(0,0)
Given (x1, y1) = (0, 0) & (x2, y2) = (5, 10), this would be the first frame of the animation. I'd like to then do a smooth animation from (x1, y1), (x2, y2), (with (x1, y1) being one end of the line and (x2, y2) being the other end) to (x1, y1), (x3, y3), with (x3, y3) = (10, 5). Similar to how a clock hand is animated. And before someone posts the analog clock example it uses a rotating pixmap which is not what I need.
I haven't found a whole lot of information on Qt animations, just a lot of basic GUI tutorials.
I have tried doing the following
QPropertyAnimation *anim = new QPropertyAnimation(widget, "geometry")
and the problem with this method is that in this technique the widget is moved between 2 points based on (0, 0) of the widget using the ->setStartValue(startX, startY, ...) and does not allow me to keep one of my lines at a fixed point.
and
QPropertyAnimation *anim = new QPropertyAnimation(widget, "rotation")
The problem with this method being similar to geometry in that it rotates said widget along a (0, 0) point.
Can someone tell me how to achieve the desired effect?
Thanks.
QGraphicsXXXItem do not support q-properties so they can not be used with QPropertyAnimation directly. So the solution is to create a class that inherits QObject and QGraphicsLineItem, plus we must add a q-property that handles the p2 position of the QLineF associated with the line as shown below:
lineitem.h
#ifndef LINEITEM_H
#define LINEITEM_H
#include <QGraphicsLineItem>
#include <QObject>
class LineItem: public QObject, public QGraphicsLineItem {
Q_OBJECT
Q_PROPERTY(QPointF p1 READ p1 WRITE setP1)
Q_PROPERTY(QPointF p2 READ p2 WRITE setP2)
public:
using QGraphicsLineItem::QGraphicsLineItem;
QPointF p1() const {
return line().p1();
}
void setP1(const QPointF & p){
QLineF l = line();
l.setP1(p);
setLine(l);
}
QPointF p2() const {
return line().p2();
}
void setP2(const QPointF & p){
QLineF l = line();
l.setP2(p);
setLine(l);
}
};
#endif // LINEITEM_H
main.cpp
#include "lineitem.h"
#include <QApplication>
#include <QGraphicsView>
#include <QPropertyAnimation>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene(-100, -100, 200, 200);
QGraphicsView view(&scene);
QGraphicsLineItem *item = scene.addLine(QLine(0, 100, 100, 0));
item->setPen(QPen(Qt::red, 5));
LineItem *lineItem = new LineItem(QLineF(QPointF(0, 0), QPointF(0, 100)));
scene.addItem(lineItem);
lineItem->setPen(QPen(Qt::green, 2));
QPropertyAnimation *anim = new QPropertyAnimation(lineItem, "p2");
anim->setStartValue(QPointF(0, 100));
anim->setEndValue(QPointF(100, 0));
anim->setDuration(2000);
anim->start();
view.resize(640, 480);
view.show();
return a.exec();
}
Another way is to use QVariantAnimation:
#include <QApplication>
#include <QGraphicsView>
#include <QPropertyAnimation>
#include <QGraphicsLineItem>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene(-100, -100, 200, 200);
QGraphicsView view(&scene);
scene.addLine(QLine(0, 100, 100, 0), QPen(Qt::green));
QGraphicsLineItem *item = scene.addLine(QLine(0, 0, 0, 100));
item->setPen(QPen(Qt::red, 5));
QVariantAnimation * anim = new QVariantAnimation(&scene);
anim->setStartValue(QPointF(0, 100));
anim->setEndValue(QPointF(100, 0));
anim->setDuration(2000);
anim->start();
QObject::connect(anim, &QVariantAnimation::valueChanged, [item](const QVariant & val){
QLineF l = item->line();
l.setP2(val.toPointF());
item->setLine(l);
});
view.resize(640, 480);
view.show();
return a.exec();
}
I am trying to design something like a timeline view for my video player. I decided to use QTableWidget as the timeline since it suits my purpose. My widget looks like this:
I want the green line to run through the widget when i click on play. Here is my MVCE example:
//View.cpp
View::View(QWidget* parent) : QGraphicsView(parent)
{
QGraphicsScene* scene = new QGraphicsScene(this);
TableWidget* wgt = new TableWidget;
scene->addWidget(wgt);
QGraphicsLineItem* item = new QGraphicsLineItem(30, 12, 30, wgt->height() - 9);
item->setPen(QPen(QBrush(Qt::green), 3));
item->setFlags(QGraphicsItem::ItemIsMovable);
scene->addItem(item);
setScene(scene);
}
Here is TableWidget
TableWidget::TableWidget(QWidget* parent) : QTableWidget(parent)
{
setColumnCount(10);
setRowCount(10);
//Hides the numbers on the left side of the table
verticalHeader()->hide();
//Prevents top header from highlighting on selection
horizontalHeader()->setHighlightSections(false);
//Makes the cells un-editable
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
}
Problem:
Moving the line item reflects changes to the scene it has been added to i.e. when i drag the line using mouse, the line moves in the scene but not inside the TableWidget.
What do i want
I want the green bar to act like a horizontal slider. It should go through the TableWidget horizontally making the widget scroll along with it showing the current position of the frame indicated by the numbers shown on the header.
Something like as shown below (notice the Red line):
I know this might not be the best way to implement a timeline but i would appreciate any other ideas to implement.
A possible solution is to overwrite the itemChange method to restrict movement as shown below:
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QTableWidget>
#include <QHeaderView>
#include <QGraphicsProxyWidget>
class SeekBarItem: public QGraphicsRectItem{
public:
SeekBarItem(QRectF rect, QGraphicsItem *parent=nullptr)
: QGraphicsRectItem(rect, parent)
{
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setBrush(Qt::red);
}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value){
if(change == QGraphicsItem::ItemPositionChange){
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().bottom()- boundingRect().bottom();
qreal min = parentItem()->boundingRect().top()-boundingRect().top();
if(p.y() > max) p.setY(max);
else if (p.y() < min) p.setY(min);
p.setX(pos().x());
return p;
}
return QGraphicsRectItem::itemChange(change, value);
}
};
class TableWidget: public QTableWidget
{
public:
TableWidget(QWidget* parent=nullptr) : QTableWidget(10, 10, parent)
{
verticalHeader()->hide();
horizontalHeader()->setHighlightSections(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene *scene = new QGraphicsScene;
view.setScene(scene);
QGraphicsProxyWidget *proxy = scene->addWidget(new TableWidget);
QGraphicsRectItem *it = new QGraphicsRectItem(QRectF(0, 0, 10, proxy->boundingRect().height()), proxy);
it->setBrush(Qt::green);
SeekBarItem *seekBarItem = new SeekBarItem(QRectF(-5, 0, 20, 50));
seekBarItem->setParentItem(it);
view.resize(640, 480);
view.show();
return a.exec();
}
Update:
#include <QApplication>
#include <QGraphicsRectItem>
#include <QGraphicsView>
#include <QTableWidget>
#include <QHeaderView>
#include <QGraphicsProxyWidget>
#include <QScrollBar>
class TableWidget: public QTableWidget
{
public:
TableWidget(QWidget* parent=nullptr) : QTableWidget(10, 10, parent)
{
verticalHeader()->hide();
horizontalHeader()->setHighlightSections(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setSelectionMode(QAbstractItemView::MultiSelection);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
}
};
class SeekBarItem: public QGraphicsRectItem{
public:
SeekBarItem(int width, QAbstractItemView *view, QGraphicsScene *scene)
: QGraphicsRectItem(nullptr),
proxy(new QGraphicsProxyWidget()),
m_view(view)
{
proxy->setWidget(m_view);
scene->addItem(proxy);
setParentItem(proxy);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setBrush(Qt::red);
setRect(0, 0, width, m_view->height());
scrollbar = m_view->horizontalScrollBar();
}
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value){
if(change == QGraphicsItem::ItemPositionChange){
QPointF p = value.toPointF();
qreal max = parentItem()->boundingRect().right()- boundingRect().right();
qreal min = parentItem()->boundingRect().left()-boundingRect().left();
if(p.x() > max) p.setX(max);
else if (p.x() < min) p.setX(min);
p.setY(pos().y());
float percentage = (p.x()-min)*1.0/(max-min);
int value = scrollbar->minimum() + percentage*(scrollbar->maximum() - scrollbar->minimum());
scrollbar->setValue(value);
return p;
}
return QGraphicsRectItem::itemChange(change, value);
}
private:
QGraphicsProxyWidget *proxy;
QAbstractItemView *m_view;
QScrollBar *scrollbar;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsView view;
QGraphicsScene *scene = new QGraphicsScene;
view.setScene(scene);
TableWidget *table = new TableWidget;
SeekBarItem *seekBarItem = new SeekBarItem(15, table, scene);
view.resize(640, 480);
view.show();
return a.exec();
}
I'm quite a n00b with Qt, but I have seen a lot of posts where people can't get a QGraphicsView to update as expected. I am trying to make very simple widget that will display two arrays of data as images overlapped with some alpha blending. From what I gather the best way to do this is to create a QImage to hold the image data, and each time I want to update the overlay display, you convert the QImage into a QPixmap, and then use that to update the pixmap of a QGraphicsPixmapItem (which is in a QGraphicsScene, which is in a QGraphicsView).
As a minimum working example, I have it setup to generate a random red image and random green image, and with random blending between them. It's setup on a timer to generate new data and, ideally, update the view. No matter how many updates/repaints I drop around, I can't seem to get it to update properly. The timer seems to be working, and the random data generation seems to be working as well, but the scene only updates if I physically change the size of the window.
Here is my code, in a few blocks. First DisplayWidget.h
#include <QtWidgets/QWidget>
#include "ui_displaywidget.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QPushButton>
#include "math.h"
#include <QVBoxLayout>
#include <qsize.h>
#include <qtimer.h>
#define FULLSCALE 255
#define IM_X_MIN -5.0
#define IM_X_MAX 5.0
#define IM_Z_MIN 0.0
#define IM_Z_MAX 15.0
#define IM_PIXEL_WIDTH 200
#define IM_PIXEL_HEIGHT IM_PIXEL_WIDTH * (IM_Z_MAX-IM_Z_MIN)/(IM_X_MAX - IM_X_MIN)
#define BORDER_WIDTH 10
#define RAND_SEED 7
class DisplayWidget : public QWidget
{
Q_OBJECT
public:
DisplayWidget(int width, int height, QWidget *parent = 0);
~DisplayWidget();
void SetData(float * data, float minVal, float maxVal);
void SetTransparency(float * alpha, float minVal, float maxVal);
private:
//Ui::DisplayWidgetClass ui;
QGraphicsView * view;
QGraphicsScene * scene;
QGraphicsPixmapItem * bModeItem;
QGraphicsPixmapItem * dModeItem;
QImage * bImage;
QImage * dImage;
QTimer * frameGrab;
void CreateWidgets();
void SetupGui();
int w, h;
public slots:
void GenerateNewData();
};
And here's the relevant parts of my .cpp. GenerateNewData() is the function that doesn't seem to produce updates to the scene. This is where I have tried view/scene/item updates/repaints.
DisplayWidget::DisplayWidget(int width, int height, QWidget *parent): QWidget(parent)
{
w = width;
h = height;
CreateWidgets();
SetupGui();
// seed the random number generator
srand(RAND_SEED);
GenerateNewData();
}
void DisplayWidget::CreateWidgets()
{
view = new QGraphicsView(this);
scene = new QGraphicsScene(this);
bModeItem = scene->addPixmap(QPixmap());
dModeItem = scene->addPixmap(QPixmap());
bImage = new QImage(w, h, QImage::Format_ARGB32);
dImage = new QImage(w, h, QImage::Format_ARGB32);
frameGrab = new QTimer(this);
}
void DisplayWidget::SetupGui()
{
QVBoxLayout * layout = new QVBoxLayout(this);
layout->addWidget(view);
setLayout(layout);
scene->setSceneRect(0, 0, IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT);
view->setGeometry(0, 0, IM_PIXEL_WIDTH + 2*BORDER_WIDTH, IM_PIXEL_HEIGHT + 2*BORDER_WIDTH);
view->setScene(scene);
bModeItem->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dModeItem->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
connect(frameGrab, SIGNAL(timeout()),this, SLOT(GenerateNewData()));
frameGrab->start(500);
}
void DisplayWidget::GenerateNewData()
{
QRgb * bImageData = (QRgb *)bImage->scanLine(0);
QRgb * dImageData = (QRgb *)dImage->scanLine(0);
for (int i; i < w * h; i++)
{
bImageData[i] = qRgba(rand() % FULLSCALE, 0, 0, FULLSCALE);
dImageData[i] = qRgba(0, 255,0, rand() % FULLSCALE);
}
bModeItem->setPixmap(QPixmap::fromImage(*bImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
dModeItem->setPixmap(QPixmap::fromImage(*dImage).scaled(QSize(IM_PIXEL_WIDTH, IM_PIXEL_HEIGHT)));
}
And here's my main.
#include "displaywidget.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DisplayWidget w(25,25);
w.show();
return a.exec();
}
I would appreciate any help!
i want to draw some rhombuses with random colors in a Qwidget. The widget should be repainted only when the window is resized .The problem is that when the widget was obscured and has now been uncovered , it is repainted. How can i avoid calling paintEvent() in this case? Thanks in advance.
void Dialog::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
QRect background(0,0,this->geometry().width(),this->geometry().height());
painter.setBrush( QBrush( Qt::white ) );
painter.setPen( Qt::NoPen );
// QBrush bbrush(Qt::black,Qt::SolidPattern);
painter.drawRect(background);
int width = this->geometry().width();
int height = this->geometry().height();
int rec_size=64;
int rows=floor((double)height/(double)rec_size);
int cols=floor((double)width/(double)rec_size);
QPointF points[4];
for (int i=0;i<floor(rows);i++)
{
for (int j=0;j<floor(cols);j++)
{
painter.setBrush( QBrush( colors[rand() % color_size] ) );
points[0] = QPointF(rec_size*(j),rec_size*(i+0.5));
points[1] = QPointF(rec_size*(j+0.5),rec_size*(i));
points[2] = QPointF(rec_size*(j+1),rec_size*(i+0.5));
points[3] = QPointF(rec_size*(j+0.5),rec_size*(i+1));
painter.drawPolygon(points, 4);
}
}
}
You are presupposing a solution, where in fact you should be focusing on the problem instead. Your problem isn't about when paintEvent is called. Qt's semantics are such that the paintEvent can be called at any time in the main thread. You must cope with it.
What you need to do, instead, is store the randomized colors in a container, to re-use them when painting. In the example below, the colors are generated on demand, and are stored in a dynamically growing list indexed by row and column. This way when you resize the widget, the colors of the existing items don't have to change. You can then regenerate the colors at any time by simply clearing the container and forcing an update.
The example below allows you to select whether to preserve the colors during resizing.
The code uses Qt 5 and C++11. Note that the use of rand() % range is discouraged in modern code - it doesn't preserve the uniformity of the distribution.
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QCheckBox>
#include <QGridLayout>
#include <QPainter>
#include <random>
std::default_random_engine rng;
class Dialog : public QWidget {
Q_OBJECT
Q_PROPERTY(bool recolorOnResize READ recolorOnResize WRITE setRecolorOnResize)
QList<QColor> m_palette;
QList<QList<QColor>> m_chosenColors;
bool m_recolorOnResize;
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.fillRect(rect(), Qt::white);
p.setRenderHint(QPainter::Antialiasing);
int rec_size=64;
int rows=height()/rec_size;
int cols=width()/rec_size;
std::uniform_int_distribution<int> dist(0, m_palette.size()-1);
while (m_chosenColors.size() < rows) m_chosenColors << QList<QColor>();
for (QList<QColor> & colors : m_chosenColors)
while (colors.size() < cols)
colors << m_palette.at(dist(rng));
QPointF points[4];
for (int i=0; i<rows; i++) {
for (int j=0; j<cols; j++) {
points[0] = QPointF(rec_size*(j),rec_size*(i+0.5));
points[1] = QPointF(rec_size*(j+0.5),rec_size*(i));
points[2] = QPointF(rec_size*(j+1),rec_size*(i+0.5));
points[3] = QPointF(rec_size*(j+0.5),rec_size*(i+1));
p.setBrush(m_chosenColors[i][j]);
p.drawPolygon(points, 4);
}
}
}
void resizeEvent(QResizeEvent *) {
if (m_recolorOnResize) m_chosenColors.clear();
}
public:
Dialog(QWidget * parent = 0) : QWidget(parent), m_recolorOnResize(false) {
m_palette << "#E2C42D" << "#E5D796" << "#BEDA2C" << "#D1DD91" << "#E2992D" << "#E5C596";
setAttribute(Qt::WA_OpaquePaintEvent);
}
Q_SLOT void randomize() {
m_chosenColors.clear();
update();
}
bool recolorOnResize() const { return m_recolorOnResize; }
void setRecolorOnResize(bool recolor) {
m_recolorOnResize = recolor;
setAttribute(Qt::WA_StaticContents, !m_recolorOnResize);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget w;
QGridLayout l(&w);
Dialog d;
QCheckBox recolor("Recolor on Resize");
QPushButton update("Repaint"), randomize("Randomize");
d.setMinimumSize(256, 128);
l.addWidget(&d, 0, 0, 1, 2);
l.addWidget(&recolor, 1, 0, 1, 2);
l.addWidget(&update, 2, 0);
l.addWidget(&randomize, 2, 1);
recolor.setChecked(d.recolorOnResize());
QObject::connect(&recolor, &QAbstractButton::toggled, [&d](bool checked){
d.setRecolorOnResize(checked);}
);
QObject::connect(&update, &QAbstractButton::clicked, &d, static_cast<void(QWidget::*)()>(&QWidget::update));
QObject::connect(&randomize, &QAbstractButton::clicked, &d, &Dialog::randomize);
w.show();
return a.exec();
}
#include "main.moc"