- 1. Project Structure
- 2. widget.ui
- 3. widget.h
- 4. widget.cpp
- 5. moveitem.h
- 6. moveitem.cpp
- 7. Result
- 8. Video
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
Лучше где-то в классе MoveItem объявить
А потомИначе движение получается неестественное. Дергается при клике.
Не, не совсем так. Здесь нужно будет учитывать сдвиг позиции курсора относительно координаты (0, 0) в координатной системе MoveItem.
Получается тогда так:
Координаты сдвига объявляем в private секции MoveItem
А в методах учитываем этот сдвиг следующим образом:Подскажите пожалуйста как я могу обратиться к одному из сформированных элементов (в данном случае квадратов). Нужно ли сначала присвоить ему какой либо индекс???
Надо просто где-то хранить указатели на графические элементы, в списке, векторе или массиве. В widget.cpp в методе
надо указатель item сохранять. Объявить у класса widget в private-секции например QVector<MoveItem*> v, а потом v.append(item). И обращаться к ним, как к элементам вектора.Попробую, спасибо))
Если перед обращением графические объекты выбирались мышью, то можно использовать метод selectedItems() у QGraphicsScene. Этот метод можно использовать в том случае, когда у графических объектов устанавливается флаг ItemIsSelectable. Тогда, если вам необходимо что-то делать с выделенными объектами, то может и не понадобиться хранить графические объекты в отдельном векторе.
Спасибо
Можно вообще
Одним методом setFlag решить всю задачу. И тогда события mouseMoveEvent, mousePressEvent и mouseReleaseEvent можно даже не переопределять.Да, это стандартный вариант для перемещения элементов в Qt. Но установка флага не всегда помогает для создания перемещаемого элемента. Например, переопределение методов mouseMoveEvent, mousePressEvent и mouseReleaseEvent позволит сделать кастомный интерфейс самого приложения, как например здесь .
Подскажите пожалуйста как в данном проекте по перетаскиванию организовать удаление объекта со scene методом delete item, допустим при щелчке ПКМ по объекту QGraphicsScene. Мои попытки оказались тщетными (маны пока маловато наверное). Заранее спасибо.
Ну например так можете сделать.
Не забудьте только подключить QApplication в файле через #include <QApplication>
И используйте deleteLater(), тогда объект будет удалён тогда, когда он не будет использоваться.
Добрый день. У меня возник такой вопрос: Каким образом в данном примере можно организовать выделение объекта? Просто setSelected метод QGraphicsScene, а мы переопределяли методы для работы с мышью в QGraphicsItem. Так как правильно это реализовать(варианты реализации которые я вижу): 1 вариант - переопределить методы мышки для QGraphicsScene, 2 вариант -эмитировать сигнал в методе мышки QGraphicsItem, который запустит slotSelected в QGraphicsScene, 3 вариант - передать указатель на QGraphicsScene в QGraphicsItem. Или может можно сделать все проще?
Смотрите, какой здесь момент...Эта статья писалась с расчётом на альтернативный вариант того, как можно сделать перемещение объектов на графической сцене. Фактически, если использовать флаги ItemIsMovable , ItemIsSelectable в самом QGraphicsItem, то не понадобится никаких переопределений методов. Просто в некоторых ситуациях, когда не хватает функционала Qt, приходится делать переопределение методов и писать логику всех этих перемещений и выделений.
Пробую реализовать на вашем примере, установил флаги, даже убрал реализацию методов мышки из класса, все равно ItemIsSelectable не срабатывает, при том что ItemIsMovable работает. Так же попробовал дописать в методы мышки вызов методов базового класса QGraphicsItem::mousePress(Move, Release)Event(event); - все равно не срабатывает. Поэтому возник еще один вопрос, правильно ли я понимаю, что при выделении должен контур штрихом перерисовываться или визуально никаких отображений видно не будет?
Вообще, если просто удалить все переопределённые методы мыши из графических объектов, то выделение должно работать. А границы будут перерисовываться пунктирной линией. Так что, Вы на правильном пути.
Реализовал с помощью setSelected в обработке клика и добавления условия в paint. Но попутно возник вопрос если у меня например на сцене два объекта, один из них выделен, я щелкаю по второму, чтобы выделить его, как снять выделение с 1? 1 вариант - сохранять указатель на пред. элемент и выставлять значение setSelected в false. 2 вариант - получать выделенный при помощи selectedItems() сцены, и все остальные элементы отмечать как false.
я как-то писал небольшой векторный редактор в качестве тестового задания, и насколько помню, хранил в векторе указатели на выделяемые объекты (не просто один указатель, а век тор указателей). Так что предложенный вам вариант вполне адекватен на мой взгляд. Плюс имеется возможность контролировать варианты выделения с использованием клавиш CTRL или SHIFT, чтобы например добавлять объекты к выделенным.
Добрый вечер. Продолжаю переделывать ваш пример. Возникло 2 вопроса:
Добрый вечер!
Еще не большой вопрос к предыдущему. (Про форум ваше замечание видел, и следующий вопрос, если такой найдется, задам там, а этот чтобы не терять нить разговора напишу здесь)
Переопределять методы мыши одновременно для сцены и итема допускается?
Да. Допускается.
Подскажите, пожалуйста, как сделать чтобы при нажатие на квадрат и переносе квадрата курсор фиксировался в месте нажатия квадрата? Точнее - центр квадрата не смещался в точку нажатия.
А вообще есть намного проще способ, как сделать то же самое. Просто воспользоваться методом setFlag , например, в конструкторе и переопределить boundingRect и paint. Если нужны просто движущиеся объекты, например, в 2D игре или анимации какой-то, то скорее всего, это то что Вам нужно.
спасибо за наводку, изучаю.
Заработал первый вариант.
У меня оба работали. Но я во втором способе удалил (закомментировал) переопределенные функции и походу в этом дело.
Добрый день. Подскажите, пожалуйста, как быть, если вместо зеленых квадратов мне нужно перемещать изображение QPixmap? Где его определить и как добавить на сцену? если делаю просто scene->addPixmap(myPixmap); то ничего не работает - фото не перемещается. Заранее спасибо!
Сделала вчера немного иначе уже. Собственно пока этого достаточно, но большое спасибо - попробую потом так, как вы сказали.
Пробовала как вы показали, только без флага, с переопределением методов, ничего не вышло, почему-то в области graphicsView не срабатывали события
Срабатывают только простые
Вот как раз так я и делала - меняла только этот метод, у меня не сработало. Видимо где-то еще я набедокурила. Спасибо Вам!)
нет, пишу мини фото-редактор, чтобы фото можно было зуммировать и перемещать в окне, когда оно все не влазит (большой масштаб)
Добрый день. Передо мной стоит схожая с данным примером задача - мне надо реализовать возможность добавлять сплайн-аппроксимацию на загруженном изображении и я хочу, чтобы пользователь мог корректировать полученную кривую вручную. Кривая формируется из точек и соединяющих эти точки кривых(QGraphicsPathItsm в моем случае). Для описания точек у меня есть отдельный класс, который так же, как и в примере, унаследован от QGraphicsItem и QObject. Меня устраивает поведение этих точек, если добавить их на сцену. Второй класс содержит QList этих точек, там все точки хранятся. В этом классе так же находится QList кривых, соединяющих эти точки. Моя задача - изменять положение криввых когда я передвигаю точки (чтобы кривая оставалась связной). И здесь у меня возникли сложности. Сложность в том, что я не знаю, как обратиться к элементу, который я передвигаю. Например, было бы здорово знать индекс этого элемента в списке. Как я могу это сделать? Если для ответа необходимы фрагменты моего кода - я добавлю его в вопрос. Заранее благодарю за любую помощь!
Можно классу, который описывает точку, добавить сигнал, который подавать (emit), когда точка перемещается (переопределить mouseMoveEvent или mouseReleaseEvent). Так вот эти сигналы у каждой из точек (например, при добавлении в QList) привязать к слоту какого-нибудь другого объекта. В этом слоте вызывать методы, которые перерисовывают нужные кривые. Статический метод QObject::sender() вернет указатель на точку, которая послала сигнал, т.е. которую переместили. Только нужно привести тип этого указателя.
Спасибо за ваш ответ, у меня получилось реализовать это. Тем не менее появилась другая проблема, поэтому опять надеюсь на вашу помощь. Скажем, я уже выставил точки и они соеденены. Когда я начинаю перемещать точку, первым делом она оказывается в левом верхнем углу QGraphicsView и только после этого начинает двигаться, при чем с отличной от скорости движения курсора скоростью. Я более подробно описал проблему вот тут . Я читал о схожей проблеме на том же ресурсе и решение состояло в том, чтобы испольозвать в конструкторе метод
Тогда предмет будет выставлен в ту точку, в которую указано. Проблема в том, что у меня предметы уже выставляются туда, куда нужно, и если использовать тот метод в конструкотре, то предмет будет выставлен совершенно в другое место, хотя метод я вызываю с теми же координатами, что и передаются в конструктор. То есть можно сделать вывод, что я получаю какие-то не те координаты, когда кликаю мышкой. Мне было посоветованно использовать функции mapToScene и mapFromScene, но они не помогли - координаты так же отличаются от координат клика мыши.
Если мой вопрос нужно дополнить кодом для большей понятности - скажите, я дополню. Заранее благодарю за ваше время
Добрый день.
Как можно узнать координату на графической сцене при отпускании клавиши мыши?
Вот так
Подскажите пожалуйста,
К graphicsView я подключил обработку контекстного меню:
сonnect(ui->graphicsView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotCustomMenuRequested(QPoint)));
А теперь хочу получать координаты сцены, как мне это лучше сделать?
А без переопределения qgraphicsScene это сделать возможно?
Есть же коорината нажатия кнопки мыши slotCustomMenuRequested(QPoint)
В этом слоте ваам нужно будет правильно смаппить координату. У QGraphicsView есть методы mapToScene, mapFromScene. Попробуйте использовать их.
Большое вам спасибо!
Вот выбор/радактирование/изменение положения одного объекта item- это понятно.
А как реализовать выбор нескольких item'ов?
Например мышкой выделяю область- те, объекты которые туда попали- выделяются.
У меня пока только такой вариант:
-Создать подкласс QGraphicsScene
-Переопределить mousePress/Move/Release event-ы
У меня здесь есть одна старая статья с примером векторного редактора. Там есть ответы на ваши вопросы. Поизучайте
Qt/C++ - Урок 072. Пример векторного редактора на Qt QGraphicsItem, QGraphicsScene, QGraphicsView, Vector Editor, QGraphicsObject
А вот уже и на python...
Добрый день.
Разрешите вопрос, вожусь с перетаскиванием мышью. Сделал свой класс Plugs, в котором отрисовывается объект на сцене, и есть private: void mouseMoveEvent(QGraphicsSceneMouseEvent *event); Но я его не наследую.
Если я пытаюсь описать его в cpp файле , то в строчке this->setPos(mapToScene(event->pos())); то компилятор ругается - ошибка: no member named 'setCursor' in 'Plugs'. Вроде логично, так как setCursor я не реализовывал (вначале подумал, что этот метод работает везде).
Переписал класс, с наследованием(начало заголовка):
Так ошибки понятно исчезают, но появляется другая.
Когда я в mainwindow.cpp создаю экземпляр Plugs plug01 = new Plugs();, компилятор мне пишет, что это абстрактный класс и создание его объектов невозможно. Но вот у вас же работает это:
Хотя вы тоже наследуете MoveItem , подскажите почему это у вас сработало ?
Добрый день.
setCursor должен работать, этот метод есть в QGraphicsItem
Да, прошу прощения отредактировал, не заметил что там есть такая кнопка.
Перетаскивание я реализовал, через флаг
это работает, а вот отслеживание отпускания кнопки мыши сделать и не получается.
Кнопки (QPushButton) у меня вообще нет, все добавляется кодом (вот тут и появляется проблема) в mouseMoveEvent.cpp.
А вот ваш код работает без проблем.
Курсор работает (компилятор не ругается) в том случа если я делаю наследование класса от public QObject, public QGraphicsItem, но в этом случае невозможно создать обьект класса.
К слову, решение через флаг тоже правильное, и даже более правильное для большинства случаев. Но такое решение без использование флага тоже рабочее по той причине, что иногда перетаскивание может конфликтовать с какой-нибудь логикой в коде, и вот тогда всё перепиливается вручную. Но обычно по необходимости.
Вообще... я думаю, что проблема в том, что вам нужно переопределить метод paint, поскольку он является чисто виртуальным. Но если вам нужно таскать картинку, вместо кастомной отрисовки, то замените QGraphicsItem на QGraphicsPixmapItem. Тогда вам не придётся переопределять метод paint
Спасибо, вы оказались правы. Замена наследования на QGraphicsPixmapItem, сделало код рабочим.
Хотя теперь, хоть все и компилируется, но по какой-то причине не срабатывают события - курсор не меняется. Так что буду копать дальше.
Еще раз , презнателен.
думаю, что всё-таки нужно переопределить эти методы
и переопределить их так
Извитине, не сработало. Да не страшно, наверно какую-то мелочь упустил. Сейчас еще раз код проверю.
Добрый день, очень помог ваш код и видео. Я пытаюсь сделать так ,чтобы при нажати разных кнопок рисовались разные фигуру, и столкнулась с тем , что не могу сделать проверку нажата ли кнопка в moveitem.cpp
Подскажите, пожалуйста, как осуществить можно?
делала вот так
Не совсем понятен вопрос. Если проверить на то, нажата ли конкретная кнопа, то в дизайнере нажмите правой кнопкой мыши на кнопу ->перейти к слото -> и там помойму clicked(bool).И в классе MainWindow создаете объект moveitem и с ним играете.
решила проблему разбив на файлы, спасибо,за попытку помочь
Подскажите, пожалуйста, почему функция рандома определена только в спп файле и объявлена при этом статической?
Пережиток plain C, ограничение видимости. По идее можно и .c, .cpp файлы подключать через директиву include. Для компилятора разницы особой нет, какое расширение будет.
здравствуйте! Подскажите, пожалуйста, как можно на Вашем примере реализовать вывод контекстного меню при нажатии на квадрат правой кнопкой мыши с целью решения задач: изменить объект и удалить? Как я поняла, мне может помочь QGraphicsSceneContextMenuEvent.
А есть возможность передавать координаты классу, что-бы он рисвал, допустим линию?