Evgenii Legotckoi
Evgenii LegotckoiOct. 7, 2015, 11:26 a.m.

Qt/C++ - Lesson 023. Moving QGraphicsItem on QGraphicsScene with mouse help

Let's say that you make an application that will be displayed the elements of the network infrastructure on the graphic scene . The question is how to make the movement of these elements with the mouse, you have to implement drag and drop elements. In other words, take the item with the mouse and exercise his moving to another place graphic scene.

Project Structure

To illustrate an example of creating a new project and add the new class, inherited from QGraphicsItem .

  • MoveGraphicsItem.pro - the profile of the project;
  • main.cpp - launch files;
  • widget.h - header of the main window;
  • widget.cpp - file source of the main window;
  • moveitem.h - header graphic element;
  • moveitem.cpp - file source graphic;
  • widget.ui - form the main window.

widget.ui

The form of the main window put the two objects:

  • QGraphicsView - It will contain the graphic scene;
  • QPushButton - It will create new graphics objects.

Main Window

widget.h

The header of the main window only declare a graphical scene and a slot for the button.

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QGraphicsScene>

#include <moveitem.h>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QGraphicsScene *scene;
};

#endif // WIDGET_H

widget.cpp

In this file, we provide a cosmetic adjustment application - this applies to the class constructor. And we make the creation of graphic objects that will drag on the graphic scene. Thus when creating objects arranged at random.

#include "widget.h"
#include "ui_widget.h"

/* Function to get a random number in the range from minimum to maximum
 * */
static int randomBetween(int low, int high)
{
    return (qrand() % ((high + 1) - low) + low);
}

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    this->resize(640,640);         
    this->setFixedSize(640,640);

    scene = new QGraphicsScene(this);   // Init graphics scene
    scene->setItemIndexMethod(QGraphicsScene::NoIndex); 

    ui->graphicsView->resize(600,600); 
    ui->graphicsView->setScene(scene);  
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);   
    ui->graphicsView->setCacheMode(QGraphicsView::CacheBackground); 
    ui->graphicsView->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);

    scene->setSceneRect(0,0,500,500); 
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    MoveItem *item = new MoveItem();       
    item->setPos(randomBetween(30, 470),    
                 randomBetween(30,470));
    scene->addItem(item);   
}

moveitem.h

To a beautiful drag graphical objects we need to use mouseMoveEvent function, mousePressEvent and mouseReleaseEvent. The function will be mouseMoveEvent directly drag the graphic object, and the other two will be a change of appearance of the mouse cursor, which will signal that we take and release a graphic object.

#ifndef MOVEITEM_H
#define MOVEITEM_H

#include <QObject>
#include <QGraphicsItem>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QDebug>
#include <QCursor>

class MoveItem : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit MoveItem(QObject *parent = 0);
    ~MoveItem();

signals:

private:
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);

public slots:
};

#endif // MOVEITEM_H

moveitem.cpp

The graphic will be a normal green square.

#include "moveitem.h"

MoveItem::MoveItem(QObject *parent) :
    QObject(parent), QGraphicsItem()
{

}

MoveItem::~MoveItem()
{

}

QRectF MoveItem::boundingRect() const
{
    return QRectF (-30,-30,60,60);
}

void MoveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setPen(Qt::black);
    painter->setBrush(Qt::green);
    painter->drawRect(-30,-30,60,60);
    Q_UNUSED(option);
    Q_UNUSED(widget);
}

void MoveItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    /* Set the position of the graphical element in the graphic scene, 
     * translate coordinates of the cursor within the graphic element 
     * in the coordinate system of the graphic scenes
     * */
    this->setPos(mapToScene(event->pos()));
}

void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    this->setCursor(QCursor(Qt::ClosedHandCursor));
    Q_UNUSED(event);
}

void MoveItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    this->setCursor(QCursor(Qt::ArrowCursor));
    Q_UNUSED(event);
}

Result

As a result, you should have an application where at the touch of a button dynamically generated green square in a random place graphic scene. And you will be able to carry out its dragging with the mouse.

Link to the project download in zip-archive: MoveGraphicsItem

Video

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

d
  • June 15, 2017, 11:39 a.m.

Лучше где-то в классе MoveItem объявить

QPointF mouseCoords;
А потом
void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    mouseCoords = event->pos();
}

void MoveItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
  QPointF coords = event->pos();
  setPos(mapToScene(coords) - mouseCoords);
}
Иначе движение получается неестественное. Дергается при клике.
Evgenii Legotckoi
  • June 16, 2017, 2:15 a.m.

Не, не совсем так. Здесь нужно будет учитывать сдвиг позиции курсора относительно координаты (0, 0) в координатной системе MoveItem.

Получается тогда так:

Координаты сдвига объявляем в private секции MoveItem

QPointF m_shiftMouseCoords;
А в методах учитываем этот сдвиг следующим образом:
void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    m_shiftMouseCoords = this->pos() - mapToScene(event->pos());
    this->setCursor(QCursor(Qt::ClosedHandCursor));
    Q_UNUSED(event);
}

void MoveItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    this->setPos(mapToScene(event->pos() + m_shiftMouseCoords));
}
F
  • June 28, 2017, 5:44 a.m.

Подскажите пожалуйста как я могу обратиться к одному из сформированных элементов (в данном случае квадратов). Нужно ли сначала присвоить ему какой либо индекс???

d
  • June 29, 2017, 4:39 a.m.

Надо просто где-то хранить указатели на графические элементы, в списке, векторе или массиве. В widget.cpp в методе

on_pushButton_clicked()
надо указатель item сохранять. Объявить у класса widget в private-секции например QVector<MoveItem*> v, а потом v.append(item). И обращаться к ним, как к элементам вектора.
F
  • June 29, 2017, 7:31 a.m.

Попробую, спасибо))

Evgenii Legotckoi
  • June 30, 2017, 12:13 p.m.

Если перед обращением графические объекты выбирались мышью, то можно использовать метод selectedItems() у QGraphicsScene. Этот метод можно использовать в том случае, когда у графических объектов устанавливается флаг ItemIsSelectable. Тогда, если вам необходимо что-то делать с выделенными объектами, то может и не понадобиться хранить графические объекты в отдельном векторе.

F
  • July 3, 2017, 2:32 p.m.

Спасибо

d
  • July 5, 2017, 12:17 p.m.

Можно вообще

void Widget::on_pushButton_clicked()
{
    MoveItem *item = new MoveItem();        // Создаём графический элемент
    item->setPos(randomBetween(30, 470),    // Устанавливаем случайную позицию элемента
                 randomBetween(30, 470));

    item->setFlag(QGraphicsItem::ItemIsMovable); // делаем элемент перемещаемым

    scene->addItem(item);   // Добавляем элемент на графическую сцену
}
Одним методом setFlag решить всю задачу. И тогда события mouseMoveEvent, mousePressEvent и mouseReleaseEvent можно даже не переопределять.
Evgenii Legotckoi
  • July 5, 2017, 1:07 p.m.

Да, это стандартный вариант для перемещения элементов в Qt. Но установка флага не всегда помогает для создания перемещаемого элемента. Например, переопределение методов mouseMoveEvent, mousePressEvent и mouseReleaseEvent позволит сделать кастомный интерфейс самого приложения, как например здесь .

F
  • July 12, 2017, 3:26 p.m.

Подскажите пожалуйста как в данном проекте по перетаскиванию организовать удаление объекта со scene методом delete item, допустим при щелчке ПКМ по объекту QGraphicsScene. Мои попытки оказались тщетными (маны пока маловато наверное). Заранее спасибо.

Evgenii Legotckoi
  • July 12, 2017, 4:12 p.m.

Ну например так можете сделать.

void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (QApplication::mouseButtons() == Qt::RightButton)
    {
        this->deleteLater();
    }
}

Не забудьте только подключить QApplication в файле через #include <QApplication>

И используйте deleteLater(), тогда объект будет удалён тогда, когда он не будет использоваться.

m
  • Oct. 8, 2017, 6:24 a.m.

Добрый день. У меня возник такой вопрос: Каким образом в данном примере можно организовать выделение объекта? Просто setSelected метод QGraphicsScene, а мы переопределяли  методы для работы с мышью в QGraphicsItem.  Так как правильно это реализовать(варианты реализации которые я вижу): 1 вариант -  переопределить методы мышки для QGraphicsScene, 2 вариант -эмитировать сигнал в методе мышки QGraphicsItem, который запустит slotSelected в QGraphicsScene, 3 вариант - передать указатель на QGraphicsScene в QGraphicsItem. Или может можно сделать все проще?

Evgenii Legotckoi
  • Oct. 8, 2017, 6:58 a.m.

Смотрите, какой здесь момент...Эта статья писалась с расчётом на альтернативный вариант того, как можно сделать перемещение объектов на графической сцене. Фактически, если использовать флаги ItemIsMovable , ItemIsSelectable в самом QGraphicsItem, то не понадобится никаких переопределений методов. Просто в некоторых ситуациях, когда не хватает функционала Qt, приходится делать переопределение методов и писать логику всех этих перемещений и выделений.

Так что попробуйте для начала в конструкторе классов, наследованных от QGraphicsItem просто установить необходимые флаги.
setFlags(GraphicsItem::ItemIsMovable | GraphicsItem::ItemIsSelectable)
m
  • Oct. 8, 2017, 8:32 a.m.

Пробую реализовать на вашем примере, установил флаги, даже убрал реализацию методов мышки из класса, все равно ItemIsSelectable не срабатывает, при том что ItemIsMovable работает. Так же попробовал дописать в методы мышки вызов методов базового класса QGraphicsItem::mousePress(Move, Release)Event(event); - все равно не срабатывает. Поэтому возник еще один вопрос, правильно ли я понимаю, что при выделении должен контур штрихом перерисовываться или визуально никаких отображений видно не будет?

Evgenii Legotckoi
  • Oct. 8, 2017, 8:48 a.m.

Вообще, если просто удалить все переопределённые методы мыши из графических объектов, то выделение должно работать. А границы будут перерисовываться пунктирной линией. Так что, Вы на правильном пути.

m
  • Oct. 8, 2017, 9:28 a.m.

Реализовал с помощью setSelected в обработке клика и добавления условия в paint. Но попутно возник вопрос если у меня например на сцене два объекта, один из них выделен, я щелкаю по второму, чтобы выделить его, как снять выделение с 1? 1 вариант - сохранять указатель на пред. элемент и выставлять значение setSelected в false. 2 вариант - получать выделенный при помощи selectedItems() сцены, и все остальные элементы отмечать как false.

Evgenii Legotckoi
  • Oct. 8, 2017, 9:47 a.m.

я как-то писал небольшой векторный редактор в качестве тестового задания, и насколько помню, хранил в векторе указатели на выделяемые объекты (не просто один указатель, а век тор указателей). Так что предложенный вам вариант вполне адекватен на мой взгляд. Плюс имеется возможность контролировать варианты выделения с использованием клавиш CTRL или SHIFT, чтобы например добавлять объекты к выделенным.

m
  • Oct. 9, 2017, 12:10 p.m.

Добрый вечер. Продолжаю переделывать ваш пример. Возникло 2 вопроса:

1. Хочу сделать кнопку триггером (если зажат, то рисуем квадрат по щелку мыши в сцене и кнопка отжимается). Я так понимаю при такой реализации как в примере, мы должны с эмитировать сигнал добавления элемента (который объявим в итеме) из нажатия кнопки мыши и связать со слотом добавления элемента (который объявим в сцене). Либо, как вариант, перенести реализацию методов мыши в сцену. Собственно вопрос, какой из этих вариантов предпочтительней? (Еще заметил, что вы в своих проектах, чаще используете реализацию методов мыши непосредствено в Итемах. А вот в офиц. документации и примерах других авторов, чаще встречаю вариант с реализацией методов мыши в Сцене. Возникает вопрос, это различие зависит от подхода программиста или есть какие либо правила, когда использовать лучше то, а не другое).
2. Хочу давать имена квадратам (при щелчке мышью для от рисовки квадрата будет появляться диалог для ввода имени, после успешного выполнения диалога имя должно появиться слева от квадрата, а при перемещении имя должно автоматом перемещаться за квадратом). Как это лучше реализовать?  Сделать с помощью shape() произвольную форму для отрисовки и вместе с рисованием квадарата добавлять текст или сделать отдельным итемом текст и неким образом связать с квадратом, сам больше склоняюсь ко второму способу, а что вы можете посоветовать?
Evgenii Legotckoi
  • Oct. 9, 2017, 4:47 p.m.

Добрый вечер!

1. Состояние зажатой кнопки можно инициировать через метод setDown(true), тогда сигналы clicked() и pressed() не будут испускаться. Да, нужно будет испускать сигнал либо от Item`a, либо от графической сцены. По поводу переопределения методов мыши в Item, либо в графической сцене я так скажу, это всё зависит от архитектуры приложения. Если очень много графических объектов будет, например делаете графический редактор типа Paint, то если всё реализовывать в графической сцене, то получите огромную неясную портянку кода, которая станет быстро неудобной в управлении. Поэтому лучше реализовывать индивидуальную логику внутри графическиъ объектов, а в графической сцене реализовывать лишь необходимый минимальный функционал, который будет лишь помогать работе графических объектов.
2. shape() лишь указывает форму объекта. Имя отрисовывать можно в методе paint в графическом объекте. Что касается имени, то можно вызывать диалоговое окно через сигнал от графического объекта. Для поддержки сигналов и слотов нужно наследоваться от QGraphicsObject . В принципе можно добавлять текст и отдельным item`ом, смотрите по удобству реализации.
 
P/S/ По возможности, задавайте вопросы на форуме сайта , чтобы были отдельные темы. Это будет полезнее для развития ресурса. Спасибо.
m
  • Oct. 10, 2017, 11:10 a.m.

Еще не большой вопрос к предыдущему. (Про форум ваше замечание видел, и следующий вопрос, если такой найдется, задам там, а этот чтобы не терять нить разговора напишу здесь)
Переопределять методы мыши одновременно для сцены и итема допускается?

Evgenii Legotckoi
  • Oct. 10, 2017, 11:19 a.m.

Да. Допускается.

a
  • May 11, 2018, 6:10 a.m.

Подскажите, пожалуйста, как сделать чтобы при нажатие на квадрат и переносе квадрата курсор фиксировался в месте нажатия квадрата? Точнее - центр квадрата не смещался в точку нажатия.

d
  • May 11, 2018, 7:20 a.m.
Вот так. Сам столкнулся с этой проблемой, когда впервые попробовал запустить коды из этого примера. Но это как улучшить этот способ.
А вообще есть намного проще способ, как сделать то же самое. Просто воспользоваться методом setFlag , например, в конструкторе и переопределить boundingRect и paint. Если нужны просто движущиеся объекты, например, в 2D игре или анимации какой-то, то скорее всего, это то что Вам нужно.
a
  • May 11, 2018, 8 a.m.

спасибо за наводку, изучаю.

a
  • May 11, 2018, 8:07 a.m.

Заработал первый вариант.

Второй нет. Правда, во втором я не удалял переопределенные функции.
d
  • May 11, 2018, 8:37 a.m.

У меня оба работали. Но я во втором способе удалил (закомментировал) переопределенные функции и походу в этом дело.

МТ
  • Aug. 30, 2018, 1:36 a.m.

Добрый день. Подскажите, пожалуйста, как быть, если вместо зеленых квадратов мне нужно перемещать изображение QPixmap? Где его определить и как добавить на сцену? если делаю просто scene->addPixmap(myPixmap); то ничего не работает - фото не перемещается. Заранее спасибо!

d
  • Aug. 30, 2018, 7:35 a.m.
#include "moveitem.h"

MoveItem::MoveItem(QObject *parent) :
    QObject(parent), QGraphicsItem()
{

}

MoveItem::~MoveItem()
{

}

QRectF MoveItem::boundingRect() const
{
    return QRectF (-30,-30,60,60);
}

void MoveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPointF point(-30, -30);
    QRectF source(0, 0, 60, 60);
    QPixmap myPixmap("file.png");
    painter->drawPixmap(point, myPixmap, source);
}

Вот так поправить moveitem.cpp. Перемещать можно только что-то отнаследованное от QGraphicsItem. Рекомендую прочитать про все методы drawPixmap, там им разные аргументы можно передавать. Ну и код добавления на сцену поправить так:
void Widget::on_pushButton_clicked()
{
    MoveItem *item = new MoveItem();        // Создаём графический элемент
    item->setPos(randomBetween(30, 470),    // Устанавливаем случайную позицию элемента
                 randomBetween(30, 470));

    item->setFlag(QGraphicsItem::ItemIsMovable); // делаем элемент перемещаемым

    scene->addItem(item);   // Добавляем элемент на графическую сцену
}
МТ
  • Aug. 30, 2018, 10:02 p.m.

Сделала вчера немного иначе уже. Собственно пока этого достаточно, но большое спасибо - попробую потом так, как вы сказали.

        scene = new QGraphicsScene(this);
        scene->setItemIndexMethod(QGraphicsScene::NoIndex);
        ui->graphicsView->setScene(scene);
        ui->graphicsView->setRenderHint(QPainter::Antialiasing);
        ui->progressBar_control->setValue(0);
        scene->setSceneRect(0,0,640,480);
        QGraphicsPixmapItem *item = new QGraphicsPixmapItem();
        item->setFlag(QGraphicsItem::ItemIsMovable, true);
        QPixmap myPixmap("D:/1.jpg");
        item->setPixmap(myPixmap);
        scene->addItem(item);

Пробовала как вы показали, только без флага, с переопределением методов, ничего не вышло, почему-то в области graphicsView не срабатывали события
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);

Срабатывают только простые
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
d
  • Aug. 31, 2018, 7:48 a.m.
Ну да, так еще проще. Класс QGraphicsPixmapItem я и пытался создать, но его уже создали, он тоже отнаследован от QGraphicsItem . Для вашей задачи вообще не нужно создавать свой класс. Только в строчке
item->setFlag(QGraphicsItem::ItemIsMovable, true);
лишнее, оно там и так по умолчанию, и pixmap можно передать в конструктор:
void Widget::on_pushButton_clicked()
{
    QPixmap myPixmap("file.png"); // создаем картинку
    QGraphicsPixmapItem *item = new QGraphicsPixmapItem(myPixmap); // Создаём графический элемент
    item->setPos(randomBetween(30, 470),    // Устанавливаем случайную позицию элемента
                 randomBetween(30, 470));

    item->setFlag(QGraphicsItem::ItemIsMovable); // делаем элемент перемещаемым

    scene->addItem(item);   // Добавляем элемент на графическую сцену
}

d
  • Sept. 1, 2018, 5:04 a.m.
А если есть желание все же изобрести свой велосипед, то с переопределением методов работает, это уже я багов наделал. Если скачать оригинальный проект из этой статьи и заменить один метод:
void MoveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPointF point(-30, -30);
    QRectF source(0, 0, 60, 60);
    QPixmap myPixmap("file.png");
    painter->drawPixmap(point, myPixmap, source);

    Q_UNUSED(option);
    Q_UNUSED(widget);
}
то все работает.
МТ
  • Sept. 3, 2018, 10:03 p.m.

Вот как раз так я и делала - меняла только этот метод, у меня не сработало. Видимо где-то еще я набедокурила. Спасибо Вам!)

d
  • Sept. 4, 2018, 10:56 a.m.
Быть не может, чтобы не работало, если скачать архив с этим же проектом. До замены метода зеленые квадратики двигаются, а после - картинки появляются, но не двигаются?
А что за задача? Игру пишете?
МТ
  • Sept. 11, 2018, 1:32 a.m.

нет, пишу мини фото-редактор, чтобы фото можно было  зуммировать и перемещать в окне, когда оно все не влазит (большой масштаб)

EF
  • June 13, 2019, 3:24 a.m.

Добрый день. Передо мной стоит схожая с данным примером задача - мне надо реализовать возможность добавлять сплайн-аппроксимацию на загруженном изображении и я хочу, чтобы пользователь мог корректировать полученную кривую вручную. Кривая формируется из точек и соединяющих эти точки кривых(QGraphicsPathItsm в моем случае). Для описания точек у меня есть отдельный класс, который так же, как и в примере, унаследован от QGraphicsItem и QObject. Меня устраивает поведение этих точек, если добавить их на сцену. Второй класс содержит QList этих точек, там все точки хранятся. В этом классе так же находится QList кривых, соединяющих эти точки. Моя задача - изменять положение криввых когда я передвигаю точки (чтобы кривая оставалась связной). И здесь у меня возникли сложности. Сложность в том, что я не знаю, как обратиться к элементу, который я передвигаю. Например, было бы здорово знать индекс этого элемента в списке. Как я могу это сделать? Если для ответа необходимы фрагменты моего кода - я добавлю его в вопрос. Заранее благодарю за любую помощь!

d
  • June 13, 2019, 10:47 a.m.

Можно классу, который описывает точку, добавить сигнал, который подавать (emit), когда точка перемещается (переопределить mouseMoveEvent или mouseReleaseEvent). Так вот эти сигналы у каждой из точек (например, при добавлении в QList) привязать к слоту какого-нибудь другого объекта. В этом слоте вызывать методы, которые перерисовывают нужные кривые. Статический метод QObject::sender() вернет указатель на точку, которая послала сигнал, т.е. которую переместили. Только нужно привести тип этого указателя.

EF
  • June 14, 2019, 9:56 a.m.

Спасибо за ваш ответ, у меня получилось реализовать это. Тем не менее появилась другая проблема, поэтому опять надеюсь на вашу помощь. Скажем, я уже выставил точки и они соеденены. Когда я начинаю перемещать точку, первым делом она оказывается в левом верхнем углу QGraphicsView и только после этого начинает двигаться, при чем с отличной от скорости движения курсора скоростью. Я более подробно описал проблему вот тут . Я читал о схожей проблеме на том же ресурсе и решение состояло в том, чтобы испольозвать в конструкторе метод

QGraphicsItem::setPos()

Тогда предмет будет выставлен в ту точку, в которую указано. Проблема в том, что у меня предметы уже выставляются туда, куда нужно, и если использовать тот метод в конструкотре, то предмет будет выставлен совершенно в другое место, хотя метод я вызываю с теми же координатами, что и передаются в конструктор. То есть можно сделать вывод, что я получаю какие-то не те координаты, когда кликаю мышкой. Мне было посоветованно использовать функции mapToScene и mapFromScene, но они не помогли - координаты так же отличаются от координат клика мыши.
Если мой вопрос нужно дополнить кодом для большей понятности - скажите, я дополню. Заранее благодарю за ваше время

Михаиллл
  • July 20, 2019, 8:49 a.m.

Добрый день.
Как можно узнать координату на графической сцене при отпускании клавиши мыши?

Михаиллл
  • July 20, 2019, 9:31 a.m.

Вот так

qDebug()<<"position:"<<event->scenePos();

Подскажите пожалуйста,
К graphicsView я подключил обработку контекстного меню:
сonnect(ui->graphicsView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotCustomMenuRequested(QPoint)));
А теперь хочу получать координаты сцены, как мне это лучше сделать?

Михаиллл
  • Dec. 5, 2019, 8:11 a.m.
    //qDebug()<<"position:"<<event->pos();
    //qDebug()<<"position:"<<event->screenPos();
    qDebug()<<"position:"<<event->scenePos();

А без переопределения qgraphicsScene это сделать возможно?
Есть же коорината нажатия кнопки мыши slotCustomMenuRequested(QPoint)

В этом слоте ваам нужно будет правильно смаппить координату. У QGraphicsView есть методы mapToScene, mapFromScene. Попробуйте использовать их.

Большое вам спасибо!

Вот выбор/радактирование/изменение положения одного объекта item- это понятно.
А как реализовать выбор нескольких item'ов?
Например мышкой выделяю область- те, объекты которые туда попали- выделяются.

У меня пока только такой вариант:
-Создать подкласс QGraphicsScene
-Переопределить mousePress/Move/Release event-ы

Evgenii Legotckoi
  • Dec. 8, 2019, 7:23 a.m.
  • (edited)

У меня здесь есть одна старая статья с примером векторного редактора. Там есть ответы на ваши вопросы. Поизучайте

Qt/C++ - Урок 072. Пример векторного редактора на Qt QGraphicsItem, QGraphicsScene, QGraphicsView, Vector Editor, QGraphicsObject

K
  • April 7, 2020, 8:55 a.m.
  • (edited)

А вот уже и на python...

#!/usr/bin/env python
# -'''- coding: utf-8 -'''-

import sys
from PySide2.QtWidgets import *
from PySide2.QtQuick import *
from PySide2.QtCore import *
from PySide2.QtGui import *

class MoveItem(QGraphicsItem):
    def __init__(self):
        super().__init__()   

    def boundingRect(self):
        return QRectF(-30,-30,60,60)

    def paint(self, painter, option, widget):
        painter.setPen(Qt.black);
        painter.setBrush(Qt.green);
        painter.drawRect(-30,-30,60,60);

    def mouseMoveEvent(self, event):
        self.setPos(self.mapToScene(event.pos()))

    def mousePressEvent(self, event):
        self.setCursor(QCursor(Qt.ClosedHandCursor))

    def mouseReleaseEvent(self, event):
        self.setCursor(QCursor(Qt.ArrowCursor))

def randomBetween(low, high):
    return (qrand() % ((high + 1) - low) + low)

class Widget(QWidget):
    def __init__(self):
        super().__init__()

        layout = QVBoxLayout()
        self.setLayout(layout)

        pb = QPushButton('Добавить квадрат')
        layout.addWidget(pb)
        pb.clicked.connect(self.button_click)

        self.resize(640,640);         
        self.setFixedSize(640,640)

        scene = QGraphicsScene()
        self.scene = scene
        scene.setItemIndexMethod(QGraphicsScene.NoIndex); 

        view = QGraphicsView()
        layout.addWidget(view)

        view.resize(600,600)
        view.setScene(scene)
        view.setRenderHint(QPainter.Antialiasing)  
        view.setCacheMode(QGraphicsView.CacheBackground)
        view.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)

        scene.setSceneRect(0,0,500,500); 

    def button_click(self):
        item = MoveItem()
        item.setPos(randomBetween(30, 470), randomBetween(30,470))        
        self.scene.addItem(item);   

if __name__ == '__main__':
    # Create the Qt Application
    app = QApplication(sys.argv)

    w = Widget()
    w.show()

    # Run the main Qt loop
    sys.exit(app.exec_())
ИВ
  • April 29, 2020, 6:40 a.m.
  • (edited)

Добрый день.
Разрешите вопрос, вожусь с перетаскиванием мышью. Сделал свой класс Plugs, в котором отрисовывается объект на сцене, и есть private: void mouseMoveEvent(QGraphicsSceneMouseEvent *event); Но я его не наследую.
Если я пытаюсь описать его в cpp файле , то в строчке this->setPos(mapToScene(event->pos())); то компилятор ругается - ошибка: no member named 'setCursor' in 'Plugs'. Вроде логично, так как setCursor я не реализовывал (вначале подумал, что этот метод работает везде).
Переписал класс, с наследованием(начало заголовка):

class Plugs : public QObject, public QGraphicsItem
{
    Q_OBJECT
public:
    explicit Plugs(QObject *parent = 0);
    ~Plugs();

public:
    //Plugs();
    int x,y;

Реализация:

Plugs::Plugs(QObject *parent) :
    QObject(parent), QGraphicsItem()
{
}

Plugs::~Plugs()
{
}

Так ошибки понятно исчезают, но появляется другая.
Когда я в mainwindow.cpp создаю экземпляр Plugs plug01 = new Plugs();, компилятор мне пишет, что это абстрактный класс и создание его объектов невозможно. Но вот у вас же работает это:

void Widget::on_pushButton_clicked()
{
    MoveItem *item = new MoveItem();        // Создаём графический элемента

Хотя вы тоже наследуете MoveItem , подскажите почему это у вас сработало ?

Evgenii Legotckoi
  • April 29, 2020, 6:54 a.m.
  • (edited)

Добрый день.

  1. отредактируйте, пожалуйста, комментарий, чтобы была разметка кода, в тулбаре редактора комментариев есть кнопка для вставки кода, это позволит разметить код так, чтобы он правильно рендерился.
  2. У вас перетаскивание работать и не будет, если не будет сделана правильная реализация метода mouseMoveEvent
  3. setCursor должен работать, перезапустите qmake. Этот метод есть в QGraphicsItem
  4. это void Widget::on_pushButton_clicked(), который был добавлен через графический дизайнер. Qt при сборке генерирует ui файлы, которые разруливают подключение сигналов и слотов в таком формате. У вас очевидно это не сделано.
Evgenii Legotckoi
  • April 29, 2020, 7 a.m.

setCursor должен работать, этот метод есть в QGraphicsItem

ИВ
  • April 29, 2020, 7:05 a.m.
  • (edited)

Да, прошу прощения отредактировал, не заметил что там есть такая кнопка.
Перетаскивание я реализовал, через флаг

pic->setPixmap(QPixmap(":/new/prefix1/Images/iconfinder_Monster.png"));
    pic->setFlags(QGraphicsItem::ItemIsMovable);

это работает, а вот отслеживание отпускания кнопки мыши сделать и не получается.
Кнопки (QPushButton) у меня вообще нет, все добавляется кодом (вот тут и появляется проблема) в mouseMoveEvent.cpp.
А вот ваш код работает без проблем.

Курсор работает (компилятор не ругается) в том случа если я делаю наследование класса от public QObject, public QGraphicsItem, но в этом случае невозможно создать обьект класса.

Evgenii Legotckoi
  • April 29, 2020, 7:17 a.m.
  • (edited)

К слову, решение через флаг тоже правильное, и даже более правильное для большинства случаев. Но такое решение без использование флага тоже рабочее по той причине, что иногда перетаскивание может конфликтовать с какой-нибудь логикой в коде, и вот тогда всё перепиливается вручную. Но обычно по необходимости.

Вообще... я думаю, что проблема в том, что вам нужно переопределить метод paint, поскольку он является чисто виртуальным. Но если вам нужно таскать картинку, вместо кастомной отрисовки, то замените QGraphicsItem на QGraphicsPixmapItem. Тогда вам не придётся переопределять метод paint

ИВ
  • April 29, 2020, 8:20 a.m.

Спасибо, вы оказались правы. Замена наследования на QGraphicsPixmapItem, сделало код рабочим.
Хотя теперь, хоть все и компилируется, но по какой-то причине не срабатывают события - курсор не меняется. Так что буду копать дальше.
Еще раз , презнателен.

Evgenii Legotckoi
  • April 29, 2020, 8:22 a.m.

думаю, что всё-таки нужно переопределить эти методы

void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);

и переопределить их так

void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsPixmapItem::mousePressEvent(event);
    this->setCursor(QCursor(Qt::ClosedHandCursor));
}

void MoveItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsPixmapItem::mouseReleaseEvent(event);
    this->setCursor(QCursor(Qt::ArrowCursor));
}
ИВ
  • April 29, 2020, 9:08 a.m.

Извитине, не сработало. Да не страшно, наверно какую-то мелочь упустил. Сейчас еще раз код проверю.

CF
  • May 28, 2020, 9:18 a.m.
  • (edited)

Добрый день, очень помог ваш код и видео. Я пытаюсь сделать так ,чтобы при нажати разных кнопок рисовались разные фигуру, и столкнулась с тем , что не могу сделать проверку нажата ли кнопка в moveitem.cpp
Подскажите, пожалуйста, как осуществить можно?
делала вот так

//mainwindow.cpp
void MainWindow::on_pushButton_clicked()
{   ui->pushButton->setCheckable(true);
    ui->pushButton->setChecked(true);
    MoveItem *item = new MoveItem();        // Создаём графический элемента
    item->setPos(randomBetween(30, 470),    // Устанавливаем случайную позицию элемента
                 randomBetween(30,470));
    scene->addItem(item);   // Добавляем элемент на графическую сцену
}

void MainWindow::on_pushButton_2_clicked()
{ui->pushButton_2->setCheckable(true);
    MoveItem *item = new MoveItem();        // Создаём графический элемента
    item->setPos(randomBetween(30, 470),    // Устанавливаем случайную позицию элемента
                 randomBetween(30,470));
    scene->addItem(item);   // Добавляем элемент на графическую сцену
}

void MainWindow::on_pushButton_3_clicked()
{ui->pushButton_3->setCheckable(true);
    MoveItem *item = new MoveItem();        // Создаём графический элемента
    item->setPos(randomBetween(30, 470),    // Устанавливаем случайную позицию элемента
                 randomBetween(30,470));
    scene->addItem(item);   // Добавляем элемент на графическую сцену
}

//moveitem.cpp
void MoveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{ MainWindow *m =new MainWindow();

    painter->setPen(Qt::black);
    painter->setBrush(Qt::green);

 if (m->ui->pushButton->isChecked())
            painter->drawEllipse(-30,-30,90,60);
     if (m->ui->pushButton_2->isChecked())
            painter->drawLine(-30,-30,90,60);
     if (m->ui->pushButton_3->isChecked())
            painter->drawLine(-50,-50,90,60);
    Q_UNUSED(option);
    Q_UNUSED(widget);
}

Михаиллл
  • May 28, 2020, 9:31 a.m.

Не совсем понятен вопрос. Если проверить на то, нажата ли конкретная кнопа, то в дизайнере нажмите правой кнопкой мыши на кнопу ->перейти к слото -> и там помойму clicked(bool).И в классе MainWindow создаете объект moveitem и с ним играете.

CF
  • May 28, 2020, 9:50 a.m.
  • (edited)

решила проблему разбив на файлы, спасибо,за попытку помочь

R
  • July 13, 2020, 9:16 a.m.

Подскажите, пожалуйста, почему функция рандома определена только в спп файле и объявлена при этом статической?

Evgenii Legotckoi
  • July 13, 2020, 9:49 a.m.

Пережиток plain C, ограничение видимости. По идее можно и .c, .cpp файлы подключать через директиву include. Для компилятора разницы особой нет, какое расширение будет.

здравствуйте! Подскажите, пожалуйста, как можно на Вашем примере реализовать вывод контекстного меню при нажатии на квадрат правой кнопкой мыши с целью решения задач: изменить объект и удалить? Как я поняла, мне может помочь QGraphicsSceneContextMenuEvent.

AO
  • Dec. 25, 2021, 10:09 a.m.

А есть возможность передавать координаты классу, что-бы он рисвал, допустим линию?

Comments

Only authorized users can post comments.
Please, Log in or Sign up
AD

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:20points,
  • Rating points-10
Last comments
Evgenii Legotckoi
Evgenii LegotckoiNov. 1, 2024, 12:37 a.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 6:19 p.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 5:51 p.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 9:02 p.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssrFeb. 9, 2024, 5:43 a.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 25, 2024, 1:11 a.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 5:04 p.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 1:49 p.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 7:10 p.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks