检测 QGraphicsItemGroup 上的点击

Posted

技术标签:

【中文标题】检测 QGraphicsItemGroup 上的点击【英文标题】:Detecting clicks on a QGraphicsItemGroup 【发布时间】:2014-02-06 13:19:29 【问题描述】:

我一直在组装一个图像滑块,以根据一些按钮为图像设置动画。作为我正在寻找的效果的示例,请参见本页顶部 (http://www.menucool.com/slider/javascript-image-slider-demo4)。

通过继承 QObject/QGraphicsItemGroup(在 imageSlider.h 和 imageSlider.cpp 中编码)并向其添加动画,我在这部分过程中取得了成功。该代码可能对其他尝试类似的人有用。

在此之后,我想添加单击 imageSlider 的功能,这将发出信号 clicked(),从而可以查询选定的图像并根据该选择执行一些任务。听起来很简单。

尝试 1:

我的第一个可能是最明显的尝试是在 imageSlider 中重新实现 mouseReleaseEvent 处理程序以捕获鼠标点击。但是,这个函数永远不会被调用。

尝试 2:

imageSlider 被添加到 QGraphicsScene 中,然后通过 QGraphicsView 查看。这是有道理的,也许 QGraphicsView 正在处理鼠标点击,因此他们根本没有到达 imageSlider。所以我将 QGraphicsView 子类化为一个名为 clickableQGraphicsView 的类,它有一个重新实现的 mouseReleaseEvent。 这确实有效。虽然我可以使用此方法并继续前进,但为了优雅起见,我想将与 imageSlider 交互所需的所有代码封装到 imageSlider 代码本身中。如果我要使用这种方法,它需要两个类。

尝试 5:(尝试 3 和 4 没有成功)

我认为可以将常规 QGraphicsView 检测到的事件传递给 imageSlider 中的事件处理程序。这意味着我可以使用标准的 QGraphicsView 类,并且能够在 imageSlider 中检测自己的鼠标点击。我安装了事件过滤器。 imageSlider 中的此事件处理程序会在 QGraphicsView 中的任何鼠标移动时调用,因此过滤器似乎可以工作。然而奇怪的是,没有检测到鼠标点击事件。

那么,问题来了:

我确定您想知道为什么我不只是闭嘴并使用尝试 2。不过我很好奇,我对自己的教育感兴趣,因为尝试 1 和 5 不使用的原因没用。

最简单的测试方法是从下载项目代码: https://www.dropbox.com/sh/zxlo3n014v7g2n7/riMq3FCB4i pro 文件位于 TestGUI 文件夹中。

可以通过更改 imageSlider.h 顶部的#define 来编译各种尝试。如果所有定义都被注释掉,则不会启用点击检测。可以通过取消注释相应的#define 行来测试每次尝试。

如果愿意,将受影响的代码粘贴到下方。


imageSlider.h代码也如下所示:

    class imageSlider: public QObject, public QGraphicsItemGroup

    Q_OBJECT
    Q_PROPERTY(QPointF pos READ pos WRITE setPos)   /// these property access functions don't have to be reimplemented as they are implemented in QObject. Just declare them.

public:
    imageSlider(QGraphicsItem *parent = 0);
    ~imageSlider();

    virtual void addToGroup(QGraphicsItem *item);   // /virtual function is used so that the function can be reimplemented but using the same signature (name, arguments, etc)
    virtual void removeFromGroup(QGraphicsItem *item);
    void moveImage(int numImages);
    void setTransitionDuration(uint ms);
    void setEasingCurve(uint curveNum); //see the help file for QEasingCurve to see what the integers stand for

private:
    QList <QGraphicsItem*> items;   /// holds the images themselves
    uint transitionDuration; /// The duration of the transition in ms
    int currentImageIndex; /// The index of the currently displayed image
    QMutex animationMutexLock;  /// So that an animation cannot be called while the previous one is ongoing
    QEasingCurve::Type easingCurveType; /// the type of easing curve for the animation.

    int getXPosChange(int numImages);   /// Get the amount the position has to change by to slide by numImages images
    void animate(int xPosChange);   /// Move the images along by the number of pixels in xPosChange

public slots:
    void unlockMutex();

signals:
    void clicked(); /// Parent process can be connected to this. When clicked the getCurrentImageIndex() function can be called and a response carried out (like running a process, opening a link, etc).

    //*******************
    // TEST CODE
    //*******************
#ifdef MOUSECLICKDETECTATTEMPT1
public slots:
    void mouseReleaseEvent(QGraphicsSceneMouseEvent * event);
#endif
#ifdef MOUSECLICKDETECTATTEMPT5
protected:
    bool eventFilter(QObject *target, QEvent *event);
#endif

;

imageSlider.cpp 是:

#include "imageSlider.h"

/**
  * Constructor for image slider.
  */
imageSlider::imageSlider(QGraphicsItem *parent) :
    QObject(0),
    QGraphicsItemGroup(parent),
    transitionDuration(500),
    currentImageIndex(0),
    easingCurveType(QEasingCurve::Linear)



/**
  * Deconstructor for image slider.
  */
imageSlider::~imageSlider()

    if(~items.isEmpty())
        items.clear();


/**
  * Add QGraphicsItems (images from QPixmaps) to the image slider.
  */
void imageSlider::addToGroup(QGraphicsItem *item)
    //change the xpos of the item before adding so that the images line up one after the other
    int xpos = 0;
    for(int i=0;i<items.count();i++)
        xpos += items.at(i)->boundingRect().width();
    item->setX(xpos);
    //add to the items and group
    items << item;
    QGraphicsItemGroup::addToGroup(item);


/**
  * Remove items from the imageSlider.
  */
void imageSlider::removeFromGroup(QGraphicsItem *item)

    items.removeAll(item);
    QGraphicsItemGroup::removeFromGroup(item);


/**
  * Move the imageSlider along by numImages number of images.
  * numImages can be +ve (move images left) or -ve (move images right).
  */
void imageSlider::moveImage(int numImages)

    if(animationMutexLock.tryLock())    //the mutex will be unlocked on receiving the finished() signal from the animation object
    
        int xPosChange = getXPosChange(numImages);
        if(xPosChange==0)   //not going to move, so unlock the mutex here as otherwise you have to wait for a zero move animation to finish before getting the next animation. Not a bug, but not ideal for user fluidity.
            animationMutexLock.unlock();
        else
            //Do the animation
            imageSlider::animate(xPosChange);
    


/**
  * Calculate the distance the QGraphicsItemGroup must slide to show the rqeuired image.
  * A positive numImages move moves the current image to the left.
  * A negative numImages move moves the current image to the right.
  */
int imageSlider::getXPosChange(int numImages)

    //create an incrementer that increments up or down to the requested image
    int incrementer = 1;
    if(numImages<0)
        incrementer = -1;
    int imageToGoTo = currentImageIndex + numImages;
    //check that the requested image is within the bounds of the number of images we have
    if(imageToGoTo<0)
        imageToGoTo = 0;
    if(imageToGoTo>items.count()-1)
        imageToGoTo = items.count()-1;
    //calculate the positional change
    int posChange = 0;
    int i=0;
    for(i=currentImageIndex;i!=imageToGoTo;i+=incrementer)
        posChange += items.at(i)->boundingRect().width();   //add the width of each image to skip over
    //update the current image index to the image that will be shown
    currentImageIndex = imageToGoTo;
    //if we want to move to the right, make the positional change negative
    if(incrementer==1)
        posChange = -posChange;
    return posChange;


/**
  * Carry out the animation from one image to another.
  */
void imageSlider::animate(int xPosChange)

    QPointF currPos = this->pos();
    QPointF endPos = currPos;
    endPos.setX(currPos.x()+xPosChange);

    QPropertyAnimation *animation = new QPropertyAnimation(this,"pos");
    connect(animation,SIGNAL(finished()),SLOT(unlockMutex()));
    animation->setStartValue(currPos);
    animation->setEndValue(endPos);
    animation->setDuration(this->transitionDuration);
    animation->setEasingCurve(this->easingCurveType);
    animation->start(QAbstractAnimation::DeleteWhenStopped);


/**
  * A slot which is called when the animation is completed to unlock the mutex.
  * This mutex stops two animations from occurring at the same time.
  */
void imageSlider::unlockMutex()

    this->animationMutexLock.unlock();


/**
  * set function for the animation transition duration.
  */
void imageSlider::setTransitionDuration(uint ms)

    this->transitionDuration = ms;


/**
  * set functionfor the easing curve enum.
  */
void imageSlider::setEasingCurve(uint curveNum)

    this->easingCurveType = (QEasingCurve::Type)curveNum;


//*******************
// TEST CODE
//*******************
#ifdef MOUSECLICKDETECTATTEMPT1
/**
  * Try reimplementing the mouseReleaseEvent for the imageSlider to catch mouse clicks.
  */
void imageSlider::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)

    qDebug() << "imageSlider mouse release event detected";
    emit clicked();

#endif

#ifdef MOUSECLICKDETECTATTEMPT5
/**
  * Try capturing clicks on the images within the slider using a QObject event filter (imageSlider inherits from QObject and QGraphicsItemGroup.
  */
bool imageSlider::eventFilter(QObject *target, QEvent *event)

    if(event->type()==QEvent::MouseButtonRelease)
    
        qDebug() << "imageSlider mouse release event detected through the eventFilter";
        emit clicked();
        return true;
    
    return false;

#endif

主窗体代码ma​​inWindow.h为:

// 
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QPropertyAnimation>
#include "../imageSlider/imageSlider.h"
#include <QGraphicsView>

namespace Ui 
class MainWindow;


#ifdef MOUSECLICKDETECTATTEMPT2
class clickableQGraphicsView : public QGraphicsView

    Q_OBJECT

public:
    clickableQGraphicsView(QWidget *parent=0);
    ~clickableQGraphicsView();
public slots:
    virtual void mouseReleaseEvent(QMouseEvent *event);
;
#endif

class MainWindow : public QMainWindow

    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_pushButtonRight_clicked();
    void on_pushButtonLeft_clicked();
    void on_listWidgetAnimType_currentTextChanged(const QString &currentText);
    void on_spinBoxMSAnim_valueChanged(int arg1);
    void imageGroupClicked();

private:
    Ui::MainWindow *ui;
    imageSlider *imageGroup;
    QGraphicsScene *GScene;
    void moveImageSlider(int numImages);

    //****************
    // TEST CODE
    //****************
#ifdef MOUSECLICKDETECTATTEMPT2
    clickableQGraphicsView *clickView;
#endif

;

#endif // MAINWINDOW_H

最后,ma​​inWindow.cpp 是:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "qmessagebox.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)

    ui->setupUi(this);

    //*******************
    // Set up options list
    // for animation types
    //*******************
    for(int i=0;i<41;i++)
        ui->listWidgetAnimType->addItem(QString::number(i));

    //****************
    // Graphics Scene
    //****************
    QPixmap pixmap1(":imageSwipe/images/1.png");
    QGraphicsItem *Image1 = new QGraphicsPixmapItem(pixmap1);
    QPixmap pixmap2(":imageSwipe/images/2.png");
    QGraphicsItem *Image2 = new QGraphicsPixmapItem(pixmap2);
    QPixmap pixmap3(":imageSwipe/images/3.png");
    QGraphicsItem *Image3 = new QGraphicsPixmapItem(pixmap3);
    QPixmap pixmap4(":imageSwipe/images/4.png");
    QGraphicsItem *Image4 = new QGraphicsPixmapItem(pixmap4);

    GScene = new QGraphicsScene();
    GScene->setSceneRect(QRectF(0,0,Image1->boundingRect().width(),Image1->boundingRect().height()));

    imageGroup = new imageSlider;
    imageGroup->addToGroup(Image1);
    imageGroup->addToGroup(Image2);
    imageGroup->addToGroup(Image3);
    imageGroup->addToGroup(Image4);

    GScene->addItem(imageGroup);

    ui->graphicsViewGUIInterface->setScene(GScene);
    ui->graphicsViewGUIInterface->setGeometry(0,0,Image1->boundingRect().width(),Image1->boundingRect().height());
    //*******************
    // TEST CODE
    //*******************
    connect(imageGroup,SIGNAL(clicked()),this,SLOT(imageGroupClicked()));

#ifdef MOUSECLICKDETECTATTEMPT2
    clickView = new clickableQGraphicsView(this);
    clickView->setScene(GScene);
    clickView->setGeometry(20,20,Image1->boundingRect().width(),Image1->boundingRect().height());
#endif

#ifdef MOUSECLICKDETECTATTEMPT5
    ui->graphicsViewGUIInterface->installEventFilter(imageGroup);
#endif


MainWindow::~MainWindow()

    if(imageGroup)
    
        disconnect(imageGroup);
        delete imageGroup;
    
    if(GScene)
        delete GScene;
    delete ui;


void MainWindow::on_pushButtonRight_clicked()

    moveImageSlider(-1);


void MainWindow::on_pushButtonLeft_clicked()

    moveImageSlider(1);


void MainWindow::moveImageSlider(int numImages)

    imageGroup->moveImage(numImages);


void MainWindow::on_listWidgetAnimType_currentTextChanged(const QString &currentText)

    imageGroup->setEasingCurve(currentText.toUInt());


void MainWindow::on_spinBoxMSAnim_valueChanged(int arg1)

    imageGroup->setTransitionDuration((uint)arg1);


void MainWindow::imageGroupClicked()

    QMessageBox msgBox;
    msgBox.setText(QString("Received index = 1"));
    msgBox.exec();


//***************
// TEST CODE
//***************
#ifdef MOUSECLICKDETECTATTEMPT2
/**
  * The below functions are an attempt to subclass the QGraphicsView
  * to provide a click response.
  */
clickableQGraphicsView::clickableQGraphicsView(QWidget *parent) :
    QGraphicsView(parent)



clickableQGraphicsView::~clickableQGraphicsView()



void clickableQGraphicsView::mouseReleaseEvent(QMouseEvent *event)

    if(event->type() == QEvent::MouseButtonRelease)
        qDebug() << "QGraphicsView event dectected";


#endif

感谢您的帮助。我希望这对其他人也有用。 斯蒂芬

【问题讨论】:

【参考方案1】:

好吧,确切地说,我对所提到的对象不是很有经验,但也许只需彻底阅读手册并进行一点调试就足够了。让我们来看看:

1) 首先我要指出的是,在 bool 值上使用二进制 not 有点违反直觉,而且不是一个好习惯,我的 VS 甚至给了我警告。我的意思是准确地说:if(~items.isEmpty())

2) 当您在QGraphicsItem::mouseReleaseEvent 上进入 Qt 手册时,您会发现以下几行:

请注意,mousePressEvent() 决定接收鼠标事件的图形项目。有关详细信息,请参阅 mousePressEvent() 描述。

所以我们要去QGraphicsItem::mousePressEvent 找到的是:

鼠标按下事件决定哪个项目应该成为鼠标抓取器(参见 QGraphicsScene::mouseGrabberItem())。如果您不重新实现该函数,则按下事件将传播到该项目下方的任何最顶层项目,并且不会向该项目传递其他鼠标事件。

所以基本上要解决你第一次尝试的问题,我们只需要覆盖mousePressEvent,函数什么都不做,事件将进入mouseReleaseEvent of imageSlider

3) 关于方法 5,这有点难以弄清楚。基本上,由于 QGraphicsView 的特殊性质,这不起作用——它总是将 QEvent::MouseButtonRelease 类型的事件转换为它的特殊 QEvent::GraphicsSceneMouseRelease 类型并将其发送到它的鼠标抓取器(前面提到过),但即使没有这些鼠标抓取器仍然将GraphicsSceneMouseRelease 事件设置为已接受,因此MouseButtonRelease 也被接受,因此最终它永远不会被发送到进一步的eventFilters。顺便说一下,MousePressEvent 不会像这样被吃掉。

实际上,大多数时候使用setEventFilter 有点太笼统了,所以无论如何我想你应该坚持首先提到的方式。

【讨论】:

谢谢。您在上面 2) 中概述的方法已经奏效。正如你所提到的,我所要做的就是实现一个 mousePressEvent 并且 mouseReleaseEvent 开始被调用。也许我应该从文档中挑选出来,但即使在上面再次阅读它,我也不确定它是否非常清楚。至于您上面的第 3) 点,我在 imageSlider::eventFilter 中添加了一个 qDebug() 以查看正在发生的事件,并且在单击鼠标时,GraphicsSceneMousePress 事件正在发生,但不是您在上面预测的释放事件。非常感谢您的帮助。 @stephenf555 根据我的经验,Qt 提供了最清晰、最完整的文档之一,因此仔细阅读它是使用 Qt 进行开发时最有用的技能。此外,在一些罕见的复杂情况下,调试 Qt 源代码可能会为正在发生的事情提供一些线索。不客气。

以上是关于检测 QGraphicsItemGroup 上的点击的主要内容,如果未能解决你的问题,请参考以下文章

画布旋转后如何检测画布上的点

带有 QGraphicsItemGroup 的事件

如何设置 QGraphicsItemGroup 的显示范围?

从 QGraphicsScene/QgraphicsItemGroup/QGraphicsView 中正确删除项目

Qt 视图框架QGraphicsItem

Qt 之 QGraphicsItemGroup