I have this dialog window class in Qt:
class Board : public QDialog
{
Q_OBJECT
public:
explicit Board(QWidget *parent = 0);
~Board();
private:
Ui::Board *ui;
void mousePressEvent(QMouseEvent *mouseEvent);
};
I want to dislay a png image on user given coordinates in function mousePressEvent, which is called every time user clicks somewhere on the dialog window. So I need something like displayImage("path/to/image.png", coordX, coordY);. How can I do it?
The new code:
class Board : public QDialog
{
public:
Board(QWidget *parent = 0) :
QDialog(parent),
ui(new Ui::Board),
view(&scene)
{
// Set background image
/**************/
ui->setupUi(this);
QPixmap pix("path/background.png");
ui->label_board->setPixmap(pix);
/**************/
/ Set layout for displaying other images on the background
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(&view);
//or set the layout and the view in the designer if using Qt Creator
}
protected:
virtual void mousePressEvent(QMouseEvent *mouseEvent) override
{
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(QPixmap("path/to/image.png"));
scene.addItem(item);
item->setPos(coordX, coordY);
}
private:
Ui::Board *ui;
QGraphicsScene scene;
QGraphicsView view;
};
The label_board is a label 500x500 set to some position with Qt Designer.
You will need to use QGraphicsView (docs) and QGraphicsScene (docs):
class Board : public QDialog
{
public:
Board(QWidget *parent = 0) :
QDialog(parent),
ui(new Ui::Board),
view(&scene)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(&view);
//or set the layout and the view in the designer if using Qt Creator
//EDIT: add background like this first
QGraphicsPixmapItem *background = QGraphicsPixmapItem(QPixmap("path/to/background.png"));
scene.addItem(background);
background.setPos(0, 0); //position it to cover all the scene so at 0,0 which is the origin point
background.setScale(2.0); //scale the image to the scene rectangle to fill it
background.setZValue(-0.1); //to ensure it is always at the back
}
protected:
virtual void mousePressEvent(QMouseEvent *mouseEvent) override
{
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(QPixmap("path/to/image.png"));
scene.addItem(item);
item->setPos(coordX, coordY);
}
private:
Ui::Board *ui;
QGraphicsScene scene;
QGraphicsView view;
};
And that is pretty much it. Also note that the position is that of upper left corner of the item (image). If you would want to center it you would need to adjust the position based on the proportions of the item (image).
Related
We are having an issue with artifacts in an application using multiple QGraphicsScene/QGraphicsViews. Essentially it seems the problem is when when in mousePosChanged event handler for a scene, and call setPos() on an item in a different scene, as well as update a region on the view for the other scene, it leaves artifacts.
I tried to set up a minimal example that hopefully will be easy to spot what is wrong
Essentially I have two scenes (scene 1 and scene 2), each with one ellipse item. When the mouse moves in the first scene, its ellipse item tracks the mouse. It also sends out a signal with mouse position. The second scene is connected to this signal, and updates the position of its ellipse item to the same location. The second graphics view is also connected to the signal, and it updates its viewport in an arbitrary location.
The second graphics view ends up with artifacts as you move the mouse around (see picture below)
This is the code from my minimal example:
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QGraphicsScene *scene, QWidget *parent = 0);
public slots:
void onMouseMoveEvent();
};
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject *parent = 0);
public slots:
void setTrackerPos(const QPointF &pos);
signals:
void mousePosChanged(const QPointF &pos);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
private:
QGraphicsEllipseItem *mCursorTracker;
};
MyScene::MyScene(QObject *parent) :
QGraphicsScene(QRectF(0.,0.,1000.,1000.), parent),
mCursorTracker(new QGraphicsEllipseItem(0., 0., 50., 50.))
{
mCursorTracker->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
mCursorTracker->setBrush(QBrush(Qt::red, Qt::SolidPattern));
addItem(mCursorTracker);
}
void MyScene::setTrackerPos(const QPointF &pos)
{
mCursorTracker->setPos(pos);
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
mCursorTracker->setPos(mouseEvent->scenePos());
emit mousePosChanged(mouseEvent->scenePos());
}
MyView::MyView(QGraphicsScene *scene, QWidget *parent) :
QGraphicsView(scene, parent)
{
setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
setMouseTracking(true);
}
void MyView::onMouseMoveEvent(const QPointF &pos)
{
viewport()->update(QRect(0,0,250,250));
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(w);
MyScene *scene1 = new MyScene(w);
MyScene *scene2 = new MyScene(w);
MyView *view1 = new MyView(scene1, w);
MyView *view2 = new MyView(scene2, w);
layout->addWidget(view1);
layout->addWidget(view2);
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), scene2, SLOT(setTrackerPos(QPointF)));
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), view2, SLOT(onMouseMoveEvent()));
w->show();
return a.exec();
}
I understand that changing to a full update on the view will fix this. However in our real application it is too expensive to repaint the whole scene. The update on the viewport is for a small foreground layer, and only one graphics item position changes (like in this example)
I have a QGraphicsView, which contains rectangle, polyline, text etc. I also have some features to transform the view like zoom in, zoom out, Fit in view, change the color on mouse right click etc. I want to add few more features like Undo and Redo.
Undo means, user should cancel the effect of last command executed and
show previous transformation. So If user did transformation like zoom
in -> zoom in -> fit in -> zoom in and now pressed Undo then view's
fit in position should get displayed.
I have tried Command pattern to implement Undo-Redo feature. But I did it to add/remove rectangle,polyline in design. And it worked.
HCommand.h
#include <QUndoCommand>
#include<QGraphicsItem>
#include<QGraphicsScene>
class myCommand : public QUndoCommand
{
public:
HCommand(QGraphicsItem* mItem, QGraphicsScene* scene);
HCommand(QGraphicsScene* scene, QGraphicsView* mView);// for fitIn
private:
QGraphicsItem* mItem;
QGraphicsScene* mScene;
QGraphicsView* mView;
void undo();
void redo();
};
HCommand.cpp
#include "hcommand.h"
HCommand::HCommand(QGraphicsItem* item, QGraphicsScene* scene):
mItem(item), mScene(scene)
{}
void HCommand::undo()
{
if(mItem)
mScene->removeItem(mItem);
}
void HCommand::redo()
{
if(mItem)
mScene->addItem(mItem);
}
Widget.h
class Widget : public QGraphicsView
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
Rectangle r;
PolyLine p;
QUndoStack* undoStack;
void undo();
void redo();
private:
Ui::Widget *ui;
QGraphicsScene* scene;
QGraphicsView* view;
}
Widget.cpp
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
view = new QGraphicsView(this);
view->setScene(scene);
undoStack = new QUndoStack(this);
ui->verticalLayout_2->addWidget(view);
}
void Widget::undo()
{
undoStack->undo();
}
void Widget::redo()
{
undoStack->redo();
}
void Widget::on_schematicBtn_clicked()
{
QGraphicsRectItem* in = r.createRect(20,160,20,20);
scene->addItem(in);
HCommand* com = new HCommand(in,scene);
undoStack->push(com);
// many rectangles and polyline code, same as above
}
But now I want to do Undo-Redo for Zoom In , Zoom Out, Fit In. But whenever I zoom in/out, I zoomed in/out full view
void Widget::on_zoomIn_clicked()
{
double sFactor = 1.2;
view->scale(sFactor,sFactor);
}
void Widget::on_zoomOut_clicked()
{
double sFactor = 1.2;
view->scale(1/sFactor,1/sFactor);
}
void Widget::on_fitInBtn_clicked()
{
view->resetTransform();
}
So the question is :
In above code I am pushing particular rectangle in stack so for zoom
in/out how to push full view in stack ? So that, later I can pull it
out from stack ? Or this can be implemented differently ?
Any help is appreciated.
Currently in my QGraphicsScene, there are multiple items (like Text, Ellipse, Rectangle, Polyline etc ) I want to change the color of these items by right clicking on them and then choose "Color Me" option. Once clicked on "Color Me" option, color dialog box should get pop up and then, I should change the color of item.
But in my try, on right click, Color Me option is appearing but clicking on it ColorOption slot, is not getting called.
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
, ui(new Ui::Widget)
{
.....
myCustomAction1 = new QAction(tr("Color Me"), this);
connect(myCustomAction1, SIGNAL(triggered()), this, SLOT(ColorOption()));
.....
}
void Widget::ColorOption()
{
QColor color = QColorDialog::getColor(currentColor);
if(color.isValid())
currentColor = color;
.....
}
Widget.h
class Widget : public QGraphicsView
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QGraphicsScene* scene;
QGraphicsView* view;
QAction* myCustomAction1;
QColor currentColor;
};
I would appreciate if anyone could help.
In order for the signal-slot mechanism to work, the slot function must be declared in the class declaration. I.e. the following needs to be in Widget.h:
public slots:
void ColorOption();
I create a chart with QChart & QGraphicsScene & QGraphicsView, but the chart does not auto zoom in or zoom out when window change. How could I do that? I don't there is any signal or slot in QChart, QGraphicsScene, or QGraphicsView class.
I know I could use QChart & QChartView, but I want QChart & QGraphicsScene & QGraphicsView for some purpose. Here is my code for draw a chart:
void MainWindow::on_actionDraw_Sine_Chart_triggered()
{
QSplineSeries *spline = new QSplineSeries;
for (double x = -M_PI; x < M_PI; x += 0.01) {
spline->append(x, sin(x));
}
spline->setName(tr("Sine Curve"));
QChart *chart = new QChart;
chart->addSeries(spline);
chart->createDefaultAxes();
chart->axisX()->setRange(-4, 4);
chart->axisY()->setRange(-1.2, 1.2);
chart->setGeometry(ui->graphicsView->rect());
QGraphicsScene *scene = new QGraphicsScene;
scene->addItem(chart);
ui->graphicsView->setScene(scene);
}
Complete code is available here.
You have to track the size change of the viewport and change the size of the QChart, for that we use eventFilter, but since it is another method you need that chart is an attribute of the class.
In addition to this it is not advisable to create the scene in the one slot, but in the constructor, the same with the QChart, and then only add the series.
*.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
...
bool eventFilter(QObject *watched, QEvent *event); // +++
private:
Ui::MainWindow *ui;
QChart *chart; // +++
QGraphicsScene *scene; // +++
};
*.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
scene = new QGraphicsScene;
chart = new QChart;
scene->addItem(chart);
ui->graphicsView->setScene(scene);
ui->graphicsView->viewport()->installEventFilter(this);
}
void MainWindow::on_actionDraw_Sine_Chart_triggered(){
QSplineSeries *spline = new QSplineSeries;
for (double x = -M_PI; x < M_PI; x += 0.01) {
spline->append(x, sin(x));
}
spline->setName(tr("Sine Curve"));
chart->addSeries(spline);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if(watched == ui->graphicsView->viewport() && event->type() == QEvent::Resize){
if(chart)
chart->resize(ui->graphicsView->viewport()->size());
}
return QMainWindow::eventFilter(watched, event);
}
I want to create a personalized UI with my Qt application. For this reason I want to edit the default window frame that you can see at any type of window application.
The application that contains the default window title and frame:
http://0000.2.img98.net/out.php/i12977_with-default-title-and-frame.jpg
The application that edit the window title and frame:
http://0000.2.img98.net/out.php/i12978_without-defualt-title-andframe.jpg
You need to create a new class derived from QWidget, and pass Qt::FramelessWindowHint argument to QWidget constructor, like this:
class MyWidget : public QWidget {
public:
MyWidget(QWidget* parent) : QWidget(parent, Qt::FramelessWindowHint) {...}
After it you need to reimplement QWidget::paintEvent (QPaintEvent * event) and draw any design you want.
For example, you have main window design as a PNG image.
class MyWidget : public QWidget {
Q_OBJECT
private:
QPushButton* button;
QLabel* label;
QComboBox* combobox;
QPixmap pixmap;
public:
explicit MyWidget(QWidget *parent = 0) : QWidget(parent, Qt::FramelessWindowHint) {
// Create some controls
button = new QPushButton();
label = new QLabel();
combobox = new QComboBox();
QVBoxLayout* l = new QVBoxLayout();
l->addWidget(button);
l->addWidget(label);
l->addWidget(combobox);
setLayout(l);
resize (500, 500);
setAttribute(Qt::WA_TranslucentBackground); // enable translucent background
pixmap = QPixmap("./1.png"); // load design picture
};
protected:
virtual void paintEvent (QPaintEvent* event) {
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(0, 0, 0, 0));
painter.drawRect(this->rect());
painter.drawPixmap(this->rect(), pixmap, pixmap.rect());
};
Since we don't see a titlebar, we need to implement window drag operation:
private:
bool pressed;
QPoint mousePressPoint;
protected:
virtual void mousePressEvent ( QMouseEvent * event ) {
QWidget::mousePressEvent(event);
if (!pressed) {
pressed = true;
mousePressPoint = event->pos();
}
}
virtual void mouseMoveEvent ( QMouseEvent * event ) {
QWidget::mouseMoveEvent(event);
if (pressed) {move(event->globalPos() - mousePressPoint);}
}
virtual void mouseReleaseEvent ( QMouseEvent * event ) {
QWidget::mouseReleaseEvent(event);
if (pressed) {pressed = false;}
}