Evgenii Legotckoi
Evgenii Legotckoi07 жовтня 2015 р. 11:26

Qt/C++ - Урок 023. Переміщення QGraphicsItem на QGraphicsScene за допомогою миші

Припустимо, що ви робите програму, яка відображатиметься на графічній сцені елементи мережної інфраструктури. Питання, як зробити переміщення цих елементів мишею, тобто здійснювати перетягування елементів. Іншими словами брати елемент мишею та здійснювати його переміщення в інше місце графічної сцени.

Давайте розберемося, як це зробити.

Структура проекта

Для демонстрації прикладу створюємо новий проект і додаємо до нього новий клас, успадкований від QGraphicsItem .

  • MoveGraphicsItem.pro - профайл проекту;
  • main.cpp - файл, що запускає;
  • widget.h - заголовний файл головного вікна;
  • widget.cpp - файл вихідних кодів головного вікна;
  • moveitem.h - заголовний файл графічного елемента;
  • moveitem.cpp - файл вихідних кодів графічного елемента.
  • widget.ui - форма головного вікна.

widget.ui

У форму головного вікна поміщаємо два об'єкти:

  • QGraphicsView - міститиме графічну сцену;
  • QPushButton - створюватиме нові графічні об'єкти.

Головне вікно

widget.h

У заголовному файлі головного вікна лише оголошуємо графічну сцену та слот для кнопки.

#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

У даному файлі здійснюємо косметичну настройку програми - це стосується конструктора класу. І робимо створення графічних об'єктів, які перетягуватимемо на графічній сцені. У цьому об'єкти під час створення розташовуються у довільному порядку.

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

/* Функция для получения рандомного числа
 * в диапазоне от минимального до максимального
 * */
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);   // Инициализируем графическую сцену
    scene->setItemIndexMethod(QGraphicsScene::NoIndex); // настраиваем индексацию элементов

    ui->graphicsView->resize(600,600);  // Устанавливаем размер graphicsView
    ui->graphicsView->setScene(scene);  // Устанавливаем графическую сцену в graphicsView
    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

Для здійснення красивого перетягування графічних об'єктів Нам потрібно використовувати функції mouseMoveEvent , mousePressEvent та mouseReleaseEvent . У функції mouseMoveEvent буде проводитися безпосереднє перетягування графічного об'єкта, а в двох інших буде проводитись зміна зовнішнього вигляду курсору миші, які сигналізуватиме про те, що ми беремо та відпускаємо графічний об'єкт.

#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

Графічний об'єкт буде звичайним зеленим квадратом.

#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)
{
    /* Устанавливаем позицию графического элемента
     * в графической сцене, транслировав координаты
     * курсора внутри графического элемента
     * в координатную систему графической сцены
     * */
    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);
}

Підсумок - здійснюємо перетягування

В результаті у Вас має вийти програма, в якій за натисканням кнопки динамічно створюватиметься зелений квадрат у випадковому місці графічної сцени. І Ви зможете здійснювати його перетягування за допомогою миші.

Демонстрацію роботи програми Ви можете побачити у відеоуроку.

Посилання на завантаження проекту в zip-архіві: MoveGraphicsItem

Відеоурок

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

d
  • 15 червня 2017 р. 11:39

Лучше где-то в классе 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
  • 16 червня 2017 р. 02:15

Не, не совсем так. Здесь нужно будет учитывать сдвиг позиции курсора относительно координаты (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
  • 28 червня 2017 р. 05:44

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

d
  • 29 червня 2017 р. 04:39

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

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

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

Evgenii Legotckoi
  • 30 червня 2017 р. 12:13

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

F
  • 03 липня 2017 р. 14:32

Спасибо

d
  • 05 липня 2017 р. 12:17

Можно вообще

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
  • 05 липня 2017 р. 13:07

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

F
  • 12 липня 2017 р. 15:26

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

Evgenii Legotckoi
  • 12 липня 2017 р. 16:12

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

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

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

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

m
  • 08 жовтня 2017 р. 06:24

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

Evgenii Legotckoi
  • 08 жовтня 2017 р. 06:58

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

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

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

Evgenii Legotckoi
  • 08 жовтня 2017 р. 08:48

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

m
  • 08 жовтня 2017 р. 09:28

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

Evgenii Legotckoi
  • 08 жовтня 2017 р. 09:47

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

m
  • 09 жовтня 2017 р. 12:10

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

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

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

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

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

Evgenii Legotckoi
  • 10 жовтня 2017 р. 11:19

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

a
  • 11 травня 2018 р. 06:10

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

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

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

a
  • 11 травня 2018 р. 08:07

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

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

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

МТ
  • 30 серпня 2018 р. 01:36

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

d
  • 30 серпня 2018 р. 07:35
#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);   // Добавляем элемент на графическую сцену
}
МТ
  • 30 серпня 2018 р. 22:02

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

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

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

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

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

EF
  • 13 червня 2019 р. 03:24

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

d
  • 13 червня 2019 р. 10:47

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

EF
  • 14 червня 2019 р. 09:56

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

QGraphicsItem::setPos()

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

Михаиллл
  • 20 липня 2019 р. 08:49

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

Михаиллл
  • 20 липня 2019 р. 09:31

Вот так

qDebug()<<"position:"<<event->scenePos();
ЛП
  • 05 грудня 2019 р. 08:09

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

Михаиллл
  • 05 грудня 2019 р. 08:11
    //qDebug()<<"position:"<<event->pos();
    //qDebug()<<"position:"<<event->screenPos();
    qDebug()<<"position:"<<event->scenePos();
ЛП
  • 05 грудня 2019 р. 08:30

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

Evgenii Legotckoi
  • 05 грудня 2019 р. 16:15

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

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

ЛП
  • 08 грудня 2019 р. 04:12

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

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

Evgenii Legotckoi
  • 08 грудня 2019 р. 07:23
  • (відредаговано)

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

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

K
  • 07 квітня 2020 р. 08:55
  • (відредаговано)

А вот уже и на 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_())
ИВ
  • 29 квітня 2020 р. 06:40
  • (відредаговано)

Добрый день.
Разрешите вопрос, вожусь с перетаскиванием мышью. Сделал свой класс 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
  • 29 квітня 2020 р. 06:54
  • (відредаговано)

Добрый день.

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

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

ИВ
  • 29 квітня 2020 р. 07:05
  • (відредаговано)

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

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

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

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

Evgenii Legotckoi
  • 29 квітня 2020 р. 07:17
  • (відредаговано)

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

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

ИВ
  • 29 квітня 2020 р. 08:20

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

Evgenii Legotckoi
  • 29 квітня 2020 р. 08:22

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

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));
}
ИВ
  • 29 квітня 2020 р. 09:08

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

CF
  • 28 травня 2020 р. 09:18
  • (відредаговано)

Добрый день, очень помог ваш код и видео. Я пытаюсь сделать так ,чтобы при нажати разных кнопок рисовались разные фигуру, и столкнулась с тем , что не могу сделать проверку нажата ли кнопка в 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);
}

Михаиллл
  • 28 травня 2020 р. 09:31

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

CF
  • 28 травня 2020 р. 09:50
  • (відредаговано)

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

R
  • 13 липня 2020 р. 09:16

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

Evgenii Legotckoi
  • 13 липня 2020 р. 09:49

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

КС
  • 26 серпня 2021 р. 06:29

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

AO
  • 25 грудня 2021 р. 10:09

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

Коментарі

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

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:30бали,
  • Рейтинг балів-10
ПЩ

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
d

C++ - Тест 005. Структуры и Классы

  • Результат:50бали,
  • Рейтинг балів-4
Останні коментарі
k
kmssr09 лютого 2024 р. 02:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 09:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 18:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 16:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 05:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
P
Pisych27 лютого 2023 р. 12:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 січня 2024 р. 19:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
BlinCT
BlinCT27 грудня 2023 р. 16:57
Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
Дмитрий
Дмитрий10 січня 2024 р. 12:18
Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
Evgenii Legotckoi
Evgenii Legotckoi12 грудня 2023 р. 14:48
Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

Слідкуйте за нами в соціальних мережах