Пример Drag and Drop RobotФайлы:
Изображения: Пример "Drag and Drop Robot" показывает, как реализовать перетаскивание в подклассе QGraphicsItem, а также как анимировать элементы, используя Каркас анимации Qt. Графическое представление предоставляет класс QGraphicsScene для управления и взаимодействия с большим числом изготовленных пользователем двухмерных элементов производных от класса QGraphicsItem, а также виджет QGraphicsView для их визуализации с поддержкой масштабирования и вращения. Этот пример состоит из класса Robot, класса ColorItem и функции main: класс Robot описывает простого робота, состоящего из нескольких RobotPart вторичных конечностей, включая RobotHead и RobotLimb, класс ColorItem предоставляет перетаскиваемый цветной эллипс, а функция main() обеспечивает главное окно приложения. Сначала рассмотрим класс Robot чтобы увидеть, как собирать разные части таким образом, чтобы они могли по отдельности вращаться и двигаться используя QPropertyAnimation, а затем рассмотрим класс ColorItem, чтобы продемонстрировать как реализовать перетаскивание между элементами. В заключением, рассмотрим функцию main(), чтобы увидеть, как мы можем собрать всё вместе для получения завершённого приложения. Определение класса RobotРобот состоит из трёх главных классов: RobotHead, RobotTorso и RobotLimb, которые используются для верхних и нижних конечностей. Все части унаследованы от класса RobotPart, который, в свою очередь, унаследован от QGraphicsObject. Класс Robot сам по себе не имеет визуального внешнего вида и служит только в качестве корневого узла для робота. Давайте начнём с объявления класса RobotPart. class RobotPart : public QGraphicsObject { public: RobotPart(QGraphicsItem *parent = 0); protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event); void dragLeaveEvent(QGraphicsSceneDragDropEvent *event); void dropEvent(QGraphicsSceneDragDropEvent *event); QColor color; bool dragOver; }; Этот базовый класс унаследовано от QGraphicsObject. QGraphicsObject предоставляет сигналы и слоты наследуя их от QObject, а также он объявляет свойства QGraphicsItem'а используя макрос Q_PROPERTY, который делает свойства доступными для QPropertyAnimation. RobotPart также реализует три наиболее важный обработчика событий для приёма событий отпускания: dragEnterEvent(), dragLeaveEvent() и dropEvent(). Цвет сохраняется как переменная-член, наряду с переменной dragOver, которую будем использовать позднее для визуального указания того, что конечность может принять цвета, которые перетаскиваются на неё. RobotPart::RobotPart(QGraphicsItem *parent) : QGraphicsObject(parent), color(Qt::lightGray), dragOver(false) { setAcceptDrops(true); } Конструктор RobotPart'а инициализирует член dragOver и устанавливает цвет в значение Qt::lightGray. В теле конструктора включаем поддержку принятия событий отпускания вызывая setAcceptDrops(true). Оставшаяся часть реализации класса - поддержка перетаскивания. void RobotPart::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasColor()) { event->setAccepted(true); dragOver = true; update(); } else { event->setAccepted(false); } } Обработчик dragEnterEvent() вызывается, когда элемент перетаскивается в область детали робота. Реализация обработчика определяет, действительно ли этот элемент как целое может принять mime-данные, связанные с входящим перетаскиваемым объектом. RobotPart обеспечивает базовое поведение всех частей, принимающих отпускаемый цвет. Поэтому если входящий перетаскиваемый объект содержит цвет, событие принимается, то мы устанавливаем dragOver в значение true и вызываем update(), чтобы помочь предоставить пользователю положительную визуальную обратную связь; в противном случае, событие игнорируется, которое в свою очередь позволяет событию распространяться по родительским элементам. void RobotPart::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { Q_UNUSED(event); dragOver = false; update(); } Обработчик dragLeaveEvent() вызывается, когда элемент перетаскивается за пределы области детали робота. Наша реализация просто сбрасывает dragOver в значение false и вызывает update(), чтобы помочь предоставить визуальную обратную связь, которую имеет элемент при покидании. void RobotPart::dropEvent(QGraphicsSceneDragDropEvent *event) { dragOver = false; if (event->mimeData()->hasColor()) color = qvariant_cast<QColor>(event->mimeData()->colorData()); update(); } Обработчик dropEvent() вызывается, когда элемент перетаскиваемый отпускается на элементе (т.е., когда во время перетаскивания кнопку мыши отпускают над элементом). Сбрасываем dragOver в false, присваиваем новый цвет элемента и вызываем update(). Объявление и реализация классов RobotHead, RobotTorso и RobotLimb практически идентична. Рассмотрим класс RobotHead подробно, так как этот класс имеет одно незначительное отличие, и оставим остальные классы читателю в качестве упражнения. class RobotHead : public RobotPart { public: RobotHead(QGraphicsItem *parent = 0); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); protected: void dragEnterEvent(QGraphicsSceneDragDropEvent *event); void dropEvent(QGraphicsSceneDragDropEvent *event); private: QPixmap pixmap; }; Класс RobotHead унаследован от RobotPart и предоставляет необходимые реализации boundingRect() и paint(). Также в нём переопределены dragEnterEvent() и dropEvent(), чтобы предоставить особую обработку отпускания изображений. Класс содержит закрытый член pixmap, который можно использовать для реализации поддержки приёма отпускаемого изображения. RobotHead::RobotHead(QGraphicsItem *parent) : RobotPart(parent) { } RobotHead имеет довольно простой конструктор, который просто перенаправляет в конструктор RobotPart'а. QRectF RobotHead::boundingRect() const { return QRectF(-15, -50, 30, 50); } Переопределённая функция boundingRect() возвратит пространство для головы. Поскольку мы хотим поместить центр вращения в центр нижней части элемента, то выбирает ограничивающий прямоугольник, который начинается в (-15, -50) и простирается на 30 единиц в ширину и 50 единиц в высоту. При вращении головы "шея" должна оставаться неподвижной в то время, как верхушка головы наклоняется из стороны в сторону. void RobotHead::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); if (pixmap.isNull()) { painter->setBrush(dragOver ? color.light(130) : color); painter->drawRoundedRect(-10, -30, 20, 30, 25, 25, Qt::RelativeSize); painter->setBrush(Qt::white); painter->drawEllipse(-7, -3 - 20, 7, 7); painter->drawEllipse(0, -3 - 20, 7, 7); painter->setBrush(Qt::black); painter->drawEllipse(-5, -1 - 20, 2, 2); painter->drawEllipse(2, -1 - 20, 2, 2); painter->setPen(QPen(Qt::black, 2)); painter->setBrush(Qt::NoBrush); painter->drawArc(-6, -2 - 20, 12, 15, 190 * 16, 160 * 16); } else { painter->scale(.2272, .2824); painter->drawPixmap(QPointF(-15 * 4.4, -50 * 3.54), pixmap); } } В функции paint() рисуем реальное положение головы. Реализация делится на две части; если изображение было отпущено на голову, то рисуем изображение, в противном случае рисуем скруглённую прямоугольную голову робота с простой векторной графикой. Из соображений производительности, в зависимости от сложности отрисовываемого, часто бывает быстрее отрисовать голову как изображение, чем используя последовательность векторных операций. void RobotHead::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasImage()) { event->setAccepted(true); dragOver = true; update(); } else { RobotPart::dragEnterEvent(event); } } Голова робота может принимать отпускание изображения. Для того, чтобы поддерживать это его переопределённая функция dragEnterEvent() проверяет, если перетаскиваемый объект содержит изображения и выполняется отпускание, тогда событие принимается. В противном случае возвращаемся к базовой реализации RobotPart. void RobotHead::dropEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->hasImage()) { dragOver = false; pixmap = qvariant_cast<QPixmap>(event->mimeData()->imageData()); update(); } else { RobotPart::dropEvent(event); } } Чтобы завершить с поддержкой изображений, нужно также реализовать dropEvent(). Проверяем, содержит ли перетаскиваемый объект изображение, и если содержит, то сохраняем изображение в качестве члена pixmap и вызываем update(). Это растровое изображение используется внутри реализации paint(), которую мы рассмотрели ранее. RobotTorso и RobotLimb аналогичны RobotHead, поэтому давайте перейдём сразу к классу Robot. class Robot : public RobotPart { public: Robot(QGraphicsItem *parent = 0); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); }; Класс Robot также унаследован от RobotPart, и как остальные части он также реализует boundingRect() и paint(). Точнее, он предоставляет специальную реализацию: QRectF Robot::boundingRect() const { return QRectF(); } void Robot::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(painter); Q_UNUSED(option); Q_UNUSED(widget); } Поскольку класс Robot используется только в качестве базового узла для остального робота, то он не имеет визуального представления. По этой причине его реализация boundingRect() может вернуть нулевой QRectF, а его функция paint() ничего не делает. Robot::Robot(QGraphicsItem *parent) : RobotPart(parent) { setFlag(ItemHasNoContents); QGraphicsObject *torsoItem = new RobotTorso(this); QGraphicsObject *headItem = new RobotHead(torsoItem); QGraphicsObject *upperLeftArmItem = new RobotLimb(torsoItem); QGraphicsObject *lowerLeftArmItem = new RobotLimb(upperLeftArmItem); QGraphicsObject *upperRightArmItem = new RobotLimb(torsoItem); QGraphicsObject *lowerRightArmItem = new RobotLimb(upperRightArmItem); QGraphicsObject *upperRightLegItem = new RobotLimb(torsoItem); QGraphicsObject *lowerRightLegItem = new RobotLimb(upperRightLegItem); QGraphicsObject *upperLeftLegItem = new RobotLimb(torsoItem); QGraphicsObject *lowerLeftLegItem = new RobotLimb(upperLeftLegItem); Конструктор начинается с установки флага ItemHasNoContents, отвечающего за незначительную оптимизация элементов, которые не имеют визуального представления. Затем создаём все части робота (голову, торс и верхние/нижние конечности - руки и ноги). Порядок расположения очень важен и мы используем иерархию родители-дети чтобы гарантировать правильное вращение и перемещение элементов. Сначала создаём торс в качестве корневого элемента. Затем создаём голову и передаём торс в конструктор HeadItem'а. Этим делаем голову дочерним элементом торса; если вы вращаете торс, то голова будет следовать за ним. Такой же шаблон применяется к остальным конечностям. headItem->setPos(0, -18); upperLeftArmItem->setPos(-15, -10); lowerLeftArmItem->setPos(30, 0); upperRightArmItem->setPos(15, -10); lowerRightArmItem->setPos(30, 0); upperRightLegItem->setPos(10, 32); lowerRightLegItem->setPos(30, 0); upperLeftLegItem->setPos(-10, 32); lowerLeftLegItem->setPos(30, 0); Каждая часть робота тщательно расположена. Например, верхняя левая рука перемещается точно в верхнюю левую область торса, а верхняя правая рука перемещается в верхнюю правую область. QParallelAnimationGroup *animation = new QParallelAnimationGroup(this); QPropertyAnimation *headAnimation = new QPropertyAnimation(headItem, "rotation"); headAnimation->setStartValue(20); headAnimation->setEndValue(-20); QPropertyAnimation *headScaleAnimation = new QPropertyAnimation(headItem, "scale"); headScaleAnimation->setEndValue(1.1); animation->addAnimation(headAnimation); animation->addAnimation(headScaleAnimation); В следующем разделе создаются все анимационные объекты. Это фрагмент кода показывает две анимации, которые работают с масштабом и вращением головы. Два экземпляра класса QPropertyAnimation просто устанавливают объект, свойство и соответственно начальное и конечное значения. Все анимации управляются одной параллельной группой анимации верхнего уровня. Анимации масштабирования и вращения добавляются в эту группу. Остальные анимации объявляются таким же способом. for (int i = 0; i < animation->animationCount(); ++i) { QPropertyAnimation *anim = qobject_cast<QPropertyAnimation *>(animation->animationAt(i)); anim->setEasingCurve(QEasingCurve::SineCurve); anim->setDuration(2000); } animation->setLoopCount(-1); animation->start(); В заключение установим сглаживание кривых и продолжительность всех анимаций, обеспечив бесконечный цикл группы анимации верхнего уровня, и запустим анимацию верхнего уровня. Определение класса ColorItemКласс ColorItem представляет круглый элемент, на который можно нажимать для перетаскивания цветов на части робота. class ColorItem : public QGraphicsItem { public: ColorItem(); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); protected: void mousePressEvent(QGraphicsSceneMouseEvent *event); void mouseMoveEvent(QGraphicsSceneMouseEvent *event); void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); private: QColor color; }; Это очень простой класс. Он не использует анимацию и ему не нужны ни свойства, ни сигналы и слоты, поэтому чтобы сэкономить ресурсы наиболее естественным будет унаследовать его от QGraphicsItem (в отличие от QGraphicsObject). Объявляет обязательные функции boundingRect() и paint(), и добавляет переопределённые функции mousePressEvent(), mouseMoveEvent() и mouseReleaseEvent(). Содержит единственный закрытый член color. Давайте изучим его реализацию. ColorItem::ColorItem() : color(qrand() % 256, qrand() % 256, qrand() % 256) { setToolTip(QString("QColor(%1, %2, %3)\n%4") .arg(color.red()).arg(color.green()).arg(color.blue()) .arg("Click and drag this color onto the robot!")); setCursor(Qt::OpenHandCursor); setAcceptedMouseButtons(Qt::LeftButton); } Конструктор ColorItem'а присваивает своему члену color, непрозрачный случайный цвет используя qrand(). Чтобы улучшить удобство работы, он присваивает всплывающую подсказку, которая предоставляет пользователю полезную подсказку, а также устанавливает подходящий курсор. Этим обеспечивается, чтобы курсор изменялся на Qt::OpenHandCursor когда указатель мыши расположится над элементом. В заключение, вызываем setAcceptedMouseButtons() для гарантии того, что этот элемент сможет обрабатывать только Qt::LeftButton. Это значительно упрощает обработчики событий мыши, поскольку мы всегда можем принять, что нажималась и отпускалась только левая кнопка мыши. QRectF ColorItem::boundingRect() const { return QRectF(-15.5, -15.5, 34, 34); } Ограничивающий прямоугольник элемента - с размерами 30x30 единиц зафиксирован вокруг исходной точки элемента (0, 0), и корректируется на 0.5 единицы по всем направлениям, чтобы позволить масштабируемому перу рисовать его границу. Для заключительного визуального штриха границы также компенсируются несколькими единицами вниз и вправо, чтобы увеличить пространство для простой тени. void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->setPen(Qt::NoPen); painter->setBrush(Qt::darkGray); painter->drawEllipse(-12, -12, 30, 30); painter->setPen(QPen(Qt::black, 1)); painter->setBrush(QBrush(color)); painter->drawEllipse(-15, -15, 30, 30); } Реализация paint() отрисовывает эллипс с чёрной границей толщиной в 1 единицу, простой заливкой цветом и тёмно-серой тенью. void ColorItem::mousePressEvent(QGraphicsSceneMouseEvent *) { setCursor(Qt::ClosedHandCursor); } Обработчик mousePressEvent() вызывается, когда вы нажимаете на кнопку мыши внутри области элемента. Наша реализация просто устанавливает курсор Qt::ClosedHandCursor. void ColorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *) { setCursor(Qt::OpenHandCursor); } Обработчик mouseReleaseEvent() вызывается, когда вы отпускаете кнопку мыши после нажатия на неё внутри области элемента. Наша реализация устанавливает курсор обратно в Qt::OpenHandCursor. Обработчики событий нажатия и отпускания кнопки мыши вместе предоставляют удобную визуальную обратную связь с пользователем: когда вы передвигаете указатель мыши над CircleItem, курсор изменяется и принимает вид раскрытой ладони. Нажатие на элементе приведёт к показу курсора в виде сжатой ладони. Отпускание снова восстановит курсор в виде раскрытой ладони. void ColorItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton)) .length() < QApplication::startDragDistance()) { return; } QDrag *drag = new QDrag(event->widget()); QMimeData *mime = new QMimeData; drag->setMimeData(mime); Обработчик mouseMoveEvent() вызывается, когда вы перемещаете мышь после нажатия на кнопку внутри области ColorItem'а. Эта реализация предоставляет наиболее важную часть логики для CircleItem: код, который начинает и управляет перетаскиванием. Реализация начинается с проверки перетаскивает ли мышь достаточно далеко для устранения дрожания. Мы хотим начать перетаскивание только если перетаскиваем мышью значительно дальше, чем начальное расстояние перетаскивания. Продолжая, создаём объект QDrag, передаём событие widget (т.е., область просмотра QGraphicsView) в его конструктор. Qt позаботится о том, чтобы этот объект был удалён в нужное время. Также создаём экземпляр класса QMimeData, содержащий цвет или изображение, и присваиваем его перетаскиваемому объекту. static int n = 0; if (n++ > 2 && (qrand() % 3) == 0) { QImage image(":/images/head.png"); mime->setImageData(image); drag->setPixmap(QPixmap::fromImage(image).scaled(30, 40)); drag->setHotSpot(QPoint(15, 30)); Данный фрагмент кода имеет кое-что случайное в результате: один раз в цикле, специальное изображение присваивается mime-данным перетаскиваемого объекта. Растровое изображение также присваивается растровому изображению перетаскиваемого объекта. Этим обеспечивается то, что вы можете видеть перетаскиваемое изображение в виде растровой картинки под указателем мыши. } else { mime->setColorData(color); mime->setText(QString("#%1%2%3") .arg(color.red(), 2, 16, QLatin1Char('0')) .arg(color.green(), 2, 16, QLatin1Char('0')) .arg(color.blue(), 2, 16, QLatin1Char('0'))); QPixmap pixmap(34, 34); pixmap.fill(Qt::white); QPainter painter(&pixmap); painter.translate(15, 15); painter.setRenderHint(QPainter::Antialiasing); paint(&painter, 0, 0); painter.end(); pixmap.setMask(pixmap.createHeuristicMask()); drag->setPixmap(pixmap); drag->setHotSpot(QPoint(15, 20)); } В противном случае, и это наиболее общий результат, mime-данным перетаскиваемого объекта присваивается простой цвет. Визуализируем этот ColorItem в новом растровом изображении, чтобы дать пользователю визуальную обратную связь "перетаскивания" цвета. drag->exec(); setCursor(Qt::OpenHandCursor); } В заключение выполняем перетаскивание. QDrag::exec() выполнит повторный вход в цикл обработки событий только если перетаскивание было завершено отпусканием или было отменено. В любом случае сбрасываем курсор к Qt::OpenHandCursor. Функция main()Теперь классы Robot и ColorItem завершены, можно разместить все куски внутри функции main(). int main(int argc, char **argv) { QApplication app(argc, argv); qsrand(QTime(0,0,0).secsTo(QTime::currentTime())); Начинаем с создания QApplication и инициализации генератора случайных чисел. Этим обеспечиваем разные цвета у цветных элементов при каждом запуске приложения. QGraphicsScene scene(-200, -200, 400, 400); for (int i = 0; i < 10; ++i) { ColorItem *item = new ColorItem; item->setPos(::sin((i * 6.28) / 10.0) * 150, ::cos((i * 6.28) / 10.0) * 150); scene.addItem(item); } Robot *robot = new Robot; robot->scale(1.2, 1.2); robot->setPos(0, -20); scene.addItem(robot); Создаём сцену фиксированного размера, а также создаём 10 экземпляров класса ColorItem расположенных по окружности. Каждый элемент добавляем на сцену. В центре этой окружности создаём один экземпляр класса Robot. Робот масштабируется и перемещается на несколько единиц. Затем добавляется на сцену. GraphicsView view(&scene); view.setRenderHint(QPainter::Antialiasing); view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); view.setBackgroundBrush(QColor(230, 200, 167)); view.setWindowTitle("Drag and Drop Robot"); #if defined(Q_OS_SYMBIAN) view.showMaximized(); #else view.show(); #endif return app.exec(); } В заключение создаём окно QGraphicsView и присваиваем ему сцену. Для увеличения качества изображения включаем сглаживание. Также выбираем для использования обновления ограничивающего прямоугольника для упрощения обработки визуального обновления. В области просмотра задан фиксированный фон песочного цвета и заголовок окна. Затем показываем область просмотра. После управляемого входа в цикл обработки событий немедленно запускаются анимации. |
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |