Privacy policyContactsAbout siteOpinionsGitHubDonate
© EVILEG 2015-2018
Recommend hosting
TIMEWEB

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

QGraphicsScene, Qt, Qt урок, перетаскивание, QGraphicsItem

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

d
  • #
  • June 15, 2017, 3:39 p.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);
}
Иначе движение получается неестественное. Дергается при клике.

Не, не совсем так. Здесь нужно будет учитывать сдвиг позиции курсора относительно координаты (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));
}
M
  • #
  • June 28, 2017, 9:44 a.m.

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

d

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

on_pushButton_clicked()
надо указатель item сохранять. Объявить у класса widget в private-секции например QVector<MoveItem*> v, а потом v.append(item). И обращаться к ним, как к элементам вектора.
M

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

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

M

Спасибо

d

Можно вообще

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 можно даже не переопределять.

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

M

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

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

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

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

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

m

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

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

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

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

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

m

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

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

m

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

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

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

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

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

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

a

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

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

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

a

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

Второй нет. Правда, во втором я не удалял переопределенные функции.
d

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

МТ

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

d
#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);   // Добавляем элемент на графическую сцену
}
МТ

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

        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
Ну да, так еще проще. Класс 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
А если есть желание все же изобрести свой велосипед, то с переопределением методов работает, это уже я багов наделал. Если скачать оригинальный проект из этой статьи и заменить один метод:
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);
}
то все работает.
МТ

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

d
Быть не может, чтобы не работало, если скачать архив с этим же проектом. До замены метода зеленые квадратики двигаются, а после - картинки появляются, но не двигаются?
А что за задача? Игру пишете?
МТ

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
P
Feb. 18, 2019, 3:39 p.m.
Poyar

C++ - Test 001. The first program and data types

  • Result:73points,
  • Rating points1
НБ
Feb. 15, 2019, 1:03 p.m.
Николай Булахтин

C++ - Test 002. Constants

  • Result:25points,
  • Rating points-10
Last comments
V
Feb. 14, 2019, 6:41 p.m.
Vlad15007

Спасибо огромное! Заработало!
А
Feb. 12, 2019, 9:26 a.m.
Александр90

Сам разборался, спасибо.
А
Feb. 12, 2019, 8:19 a.m.
Александр90

День добрый! Можешь выложить форму mainwindow.ui от урока? Заранее спасибо
Feb. 11, 2019, 10:51 a.m.
Евгений Легоцкой

Нет, у меня проблема с жёстким диском случилась, занимался восстановлением ПК, ещё пару вечеров придётся этим заниматься, увы.
Now discuss on the forum
Feb. 17, 2019, 5:28 p.m.
Евгений Легоцкой

Добрый день. Очень извиняюсь за долгий ответ Первое, что нашёл, так это необходимость перерисовать чекбокс. void CheckBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem ...
Feb. 15, 2019, 3:36 p.m.
Евгений Легоцкой

Ну я тут нашёл одно решение, но сам его не проверял. Вам нужно помещать фамилии скорее всего в ячейки заголовка, и потом просто перерисовывать их QHeaderView * header = m_ui->tableWidget...
Feb. 15, 2019, 7:53 a.m.
Евгений Легоцкой

Добрый день! Не работал с remoteobjects, поэтому глянул документацию, чтобы рассмотреть, что это за зверь. После просмотра документации сложилось стойкой впечатление, что это вполне возм...
m
Feb. 14, 2019, 6:28 p.m.
mr_roman

Нашел решение на Java. Удалось интегрировать в проект сервиса на Qt, теперь из Qt запускаю Java-код акселерометра.
Join us in social networks

For registered users on the site there is a minimum amount of advertising