前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Qt官方示例-拖放机器人

Qt官方示例-拖放机器人

作者头像
Qt君
发布2020-02-24 09:17:27
4.7K1
发布2020-02-24 09:17:27
举报
文章被收录于专栏:跟Qt君学编程跟Qt君学编程

❝拖放机器人示例演示如何在QGraphicsItem子类中实现拖放,以及如何使用Qt的Animation Framework动画化项目。❞

  Graphics View提供了QGraphicsScene类,用于管理从QGraphicsItem类派生的大量定制2D图形项目,并与之交互;还提供了QGraphicsView小部件,用于可视化项目,并支持缩放和旋转。

  该示例是由一个Robot类,一个ColorItem类和一个主要功能组成:Robot该类描述了一个由多个RobotPart派生肢体组成的简单机器人,包括RobotHead和RobotLimb,ColorItem类提供了可拖动的彩色椭圆。

  我们将首先看Robot类,以了解如何组装不同的部分,以便可以使用QPropertyAnimation分别旋转和动画化各个部分,然后我们将看ColorItem类,以演示如何在项目之间实现拖放。最后,我们将看main()函数,以了解如何将所有部分放在一起以形成最终应用程序。

Robot类定义

  该机器人包括三个主要的类:RobotHead,RobotTorso和RobotLimb,它被用于上下臂和腿。他们都继承于RobotPart类,该类又继承了QGraphicsObject。所述的Robot类本身不具有视觉外观和仅充当用于机器人根节点。

  让我们从RobotPart类声明开始。

代码语言:javascript
复制
class RobotPart : public QGraphicsObject
{
public:
    RobotPart(QGraphicsItem *parent = nullptr);

protected:
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
    void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override;
    void dropEvent(QGraphicsSceneDragDropEvent *event) override;

    QColor color = Qt::lightGray;
    bool dragOver = false;
};

  该基类继承QGraphicsObject。QGraphicsObject通过继承QObject提供信号和槽,它还可使用Q_PROPERTY声明QGraphicsItem的属性,这使该属性可用于QPropertyAnimation。

  RobotPart还实现了三个最重要的事件处理程序以接收放置事件:dragEnterEvent(),dragLeaveEvent()和dropEvent()。

  颜色与变量一起存储为成员变量,dragOver稍后将使用该变量在视觉上指示肢体可以接受拖动到的颜色。

代码语言:javascript
复制
RobotPart::RobotPart(QGraphicsItem *parent)
    : QGraphicsObject(parent), color(Qt::lightGray)
{
    setAcceptDrops(true);
}

  RobotPart的构造函数初始化dragOver成员并将颜色设置为Qt::lightGray。在构造函数主体中,我们通过调用setAcceptDrops(true)来支持接受放置事件。

代码语言:javascript
复制
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(),以帮助向用户提供积极的视觉反馈;否则,事件将被忽略,从而使事件传播到父元素。

代码语言:javascript
复制
void RobotPart::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
{
    Q_UNUSED(event);
    dragOver = false;
    update();
}

  对于dragLeaveEvent(),当拖放元素从机器人部分的面积拖走后将自动调用。我们的实现只需将dragOver重置为false并调用update()即可帮助提供视觉反馈,说明拖动已离开了此项。

代码语言:javascript
复制
void RobotPart::dropEvent(QGraphicsSceneDragDropEvent *event)
{
    dragOver = false;
    if (event->mimeData()->hasColor())
        color = qvariant_cast<QColor>(event->mimeData()->colorData());
    update();
}

  对于dropEvent(),当拖放元素被拖放到一个项目上时,dropEvent()将被自动调用。(当鼠标在拖动项目时释放鼠标按钮时)。 我们将其重置dragOver为false,分配机器人部件的新颜色,然后调用update()。

  RobotHead,RobotTorso以及RobotLimb的定义和实现几乎相同。我们这里只详细介绍RobotHead。

代码语言:javascript
复制
class RobotHead : public RobotPart
{
public:
    RobotHead(QGraphicsItem *parent = nullptr);

    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;

protected:
    void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override;
    void dropEvent(QGraphicsSceneDragDropEvent *event) override;

private:
    QPixmap pixmap;
};

  RobotHead类继承于RobotPart,并提供必要的实现boundingRect()和paint() 。它还重新实现dragEnterEvent()和dropEvent()以提供图像放置的特殊处理。   该类还包含一个私有的pixmap成员,我们可以用来实现对接受图像放置的支持。

代码语言:javascript
复制
RobotHead::RobotHead(QGraphicsItem *parent)
    : RobotPart(parent)
{
}

  RobotHead有一个非常简单的构造函数,可以简单地转发到RobotPart的构造函数。

代码语言:javascript
复制
QRectF RobotHead::boundingRect() const
{
    return QRectF(-15, -50, 30, 50);
}

  boundingRect()重新实现机器人的头部的范围。因为我们希望旋转中心为项目的底部中心,所以我们选择了一个以(-15,-50)开始并延伸到30个单位宽和50个单位高的边界矩形。旋转头部时,"颈部"将保持静止,同时头部的顶部从一侧向另一侧倾斜。

代码语言:javascript
复制
void RobotHead::paint(QPainter *painter,
           const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);
    if (pixmap.isNull()) {
        painter->setBrush(dragOver ? color.lighter(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()中,我们绘制实际的头部。该实现分为两个部分:如果将图像放置在头部上,则绘制图像,否则将绘制带有简单矢量图形的圆形矩形机器人头部。   出于性能方面的考虑,取决于所绘制内容的复杂性,将头部绘制为图像通常比使用一系列矢量操作更快。

代码语言:javascript
复制
void RobotHead::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
    if (event->mimeData()->hasImage()) {
        event->setAccepted(true);
        dragOver = true;
        update();
    } else {
        RobotPart::dragEnterEvent(event);
    }
}

  机器人头部可以接受图像源。为了支持此操作,对其dragEnterEvent()的重新实现将检查拖动对象是否包含图像数据,如果包含,则接受该事件。否则,我们将退回到基本RobotPart实现。

代码语言:javascript
复制
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类。

代码语言:javascript
复制
class Robot : public RobotPart
{
public:
    Robot(QGraphicsItem *parent = nullptr);

    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
};  

  Robot类也继承于RobotPart,并像其他部分也实现了boundingRect()和paint()方法。但是,它提供了一个相当特殊的实现:

代码语言:javascript
复制
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()函数则不执行任何操作。

代码语言:javascript
复制
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的构造函数。这将使头部成为躯干的"孩子";如果旋转躯干,头部将跟随。相同的模式适用于其余四肢。

代码语言:javascript
复制
    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);

  每个机器人零件都经过仔细定位。例如,左上臂精确地移动到躯干的左上区域,右上臂移动到右上区域。

代码语言:javascript
复制
    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实例仅设置对象,属性以及各自的开始和结束值。   所有动画均由一个顶级并行动画组控制。比例和旋转动画已添加到该组中。其余动画以类似方式定义。

代码语言:javascript
复制
    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类表示可被按下以拖动颜色到机器人零件的圆形项。

代码语言:javascript
复制
class ColorItem : public QGraphicsItem
{
public:
    ColorItem();

    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;

private:
    QColor color;
};

  这个ColorItem类很简单。它不使用动画,不需要属性,也不需要信号和插槽,因此为了节省资源,最自然的是它继承了QGraphicsItem(与QGraphicsObject相对)。   它声明了必需的boundingRect()和paint()函数,并重新实现了mousePressEvent(),mouseMoveEvent()和mouseReleaseEvent()。它包含一个私人颜色成员。

  让我们看一下它的实现。

代码语言:javascript
复制
ColorItem::ColorItem()
    : color(QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(256), QRandomGenerator::global()->bounded(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的构造函数通过使用QRandomGenerator向其颜色成员分配不透明的随机颜色。为了提高可用性,它分配了一个工具提示,向用户提供有用的提示,并且还设置了合适的光标。这样可以确保当鼠标指针悬停在项目上时,光标将有机会进入Qt::OpenHandCursor状态。 最后,我们调用setAcceptedMouseButtons()以确保该项目只能处理Qt::LeftButton。这可以大大简化鼠标事件处理程序,因为我们始终可以假定仅按下并释放了鼠标左键。

代码语言:javascript
复制
QRectF ColorItem::boundingRect() const
{
    return QRectF(-15.5, -15.5, 34, 34);
}

  项的边界矩形是固定的30x30单位,以该项的原点(0,0)为中心,并在各个方向上调整0.5单位,以允许可伸缩的笔绘制其轮廓。为了获得最终的视觉效果,边界还向右下方补偿了几个单位,从而为简单的阴影提供了空间。

代码语言:javascript
复制
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个单位像素的黑色轮廓,一个普通的颜色填充,和深灰色阴影效果的椭圆。

代码语言:javascript
复制
void ColorItem::mousePressEvent(QGraphicsSceneMouseEvent *)
{
    setCursor(Qt::ClosedHandCursor);
}

  当你按下该项目的区域内的鼠标按键时自动调用mousePressEvent()。我们的实现只是将光标设置为Qt::ClosedHandCursor。

代码语言:javascript
复制
void ColorItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *)
{
    setCursor(Qt::OpenHandCursor);
}

  当你释放已经按了一个项目的区域内后自动调用mouseReleaseEvent()。我们的实现将光标设置回Qt::OpenHandCursor。鼠标按下和释放事件处理程序共同为用户提供有用的视觉反馈:将鼠标指针移到上时CircleItem,光标将变为张开的手。按下该项目将显示一个闭合的手形光标。释放将再次恢复为打开的手形光标。

代码语言:javascript
复制
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对象,将事件窗口小部件(即QGraphicsView)传递给其构造函数。Qt将确保在正确的时间删除该对象。我们还创建了一个QMimeData实例,该实例可以包含我们的颜色或图像数据,并将其分配给拖动对象。

代码语言:javascript
复制
    static int n = 0;
    if (n++ > 2 && QRandomGenerator::global()->bounded(3) == 0) {
        QImage image(":/images/head.png");
        mime->setImageData(image);

        drag->setPixmap(QPixmap::fromImage(image).scaled(30, 40));
        drag->setHotSpot(QPoint(15, 30));

  该代码段具有某种随机结果:有时,会将特殊图像分配给拖动对象的mime数据。像素图也被辅助为拖动对象的像素图。这将确保您可以在鼠标光标下看到被拖动为像素图的图像。

代码语言:javascript
复制
    } 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, nullptr, nullptr);
        painter.end();

        pixmap.setMask(pixmap.createHeuristicMask());

        drag->setPixmap(pixmap);
        drag->setHotSpot(QPoint(15, 20));
    }

  否则,这是最常见的结果,将简单的颜色分配给拖动对象的mime数据。我们将此渲染ColorItem为新的像素图,以向用户提供颜色正在"拖拉"的视觉反馈。

代码语言:javascript
复制
    drag->exec();
    setCursor(Qt::OpenHandCursor);
}

  最后,我们执QDrag::exec()将重新进入事件循环,并且只有在拖动被删除或取消的情况下才退出。无论如何,我们都将光标重置为Qt::OpenHandCursor。

main函数

  现在Robot和ColorItem类已经完成,我们可以将这些模块放到main函数中。

代码语言:javascript
复制
int main(int argc, char **argv)
{
    QApplication app(argc, argv);

  我们首先构建QApplication,然后初始化随机数生成器。这样可以确保每次启动应用程序时颜色项具有不同的颜色。

代码语言:javascript
复制
    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->setTransform(QTransform::fromScale(1.2, 1.2), true);
    robot->setPos(0, -20);
    scene.addItem(robot);

  我们构造一个固定大小的场景,并创建ColorItem一个排列成一个圆圈的10个实例。每个项目都添加到场景中。   在此圆的中心,我们创建一个Robot实例。缩放机器人并将其向上移动几个单元。然后将其添加到场景中。

代码语言:javascript
复制
    GraphicsView view(&scene);
    view.setRenderHint(QPainter::Antialiasing);
    view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
    view.setBackgroundBrush(QColor(230, 200, 167));
    view.setWindowTitle("Drag and Drop Robot");
    view.show();

    return app.exec();
}

  我们创建一个QGraphicsView窗口,并为其分配场景。为了提高视觉质量,我们启用了抗锯齿功能。我们还选择使用边界矩形更新来简化可视更新处理。该视图具有固定的沙色背景和窗口标题。

  最后,我们显示视图。控件进入事件循环后,动画立即开始。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Qt君 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Robot类定义
  • ColorItem类定义
  • main函数
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档