为什么我在 Qt 中实现交互式 QGraphicsView 时无法更改鼠标光标?

发布于 2024-10-21 07:00:12 字数 4248 浏览 3 评论 0原文

我需要在我的应用程序中显示包含图像的 MDI 窗口。我希望能够使用鼠标右键拖动滚动图像,使用鼠标滚轮缩放图像,并在图像上创建多边形感兴趣区域蒙版。为此,我创建了一个自定义的 QGraphicsView 派生类(ImageView),它重新实现了一些鼠标事件。我还有一个 QGraphicsPixmapItem 派生类(ImageItem),用于实现悬停事件,该事件更新应用程序界面中的光标像素位置指示器。这是轮廓(尚未实现遮罩多边形):

class ImageView : public QGraphicsView
{
    Q_OBJECT
public:
    ImageView(QWidget *parent) : QGraphicsView(parent), _pan(false), _panStartX(0), _panStartY(0), 
        _scale(1.2), _scene(NULL), _imgItem(NULL)
    {
        _scene = new QGraphicsScene(this); 
        _scene->setBackgroundBrush(Qt::lightGray);
        setScene(_scene);
        _imgItem = new ImageItem(this);
        _scene->addItem(_imgItem);
    }

    void showImage(const QString &path)
    {
        _imgItem->loadImage(path);
        setSceneRect(0, 0, _imgItem->imageWidth(), _imgItem->imageHeight()); 
    }

    void startAoi(AoiType type)
    {
        _imgItem->setAcceptHoverEvents(true);
        _imgItem->setCursor(Qt::CrossCursor);
        // explicit mouse tracking enable isn't necessary
    }

    void stopAoi()
    {
        _imgItem->unsetCursor();
        _imgItem->setAcceptHoverEvents(false);
    }

public slots:
    void zoomIn() { scale(_scale, _scale); }
    void zoomOut() { scale(1/_scale, 1/_scale); }

protected:
    void ImageView::mousePressEvent(QMouseEvent *event)
    {
        // enter pan mode; set flag, change cursor and store start position
        if (event->button() == Qt::RightButton)
        {
            _pan = true;
            _panStartX = event->x();
            _panStartY = event->y();
            setCursor(Qt::OpenHandCursor);
            // accept the event and skip the default implementation?
            event->accept();
            return;
        }

        // should do event accept here?
        event->ignore();
    }

    void ImageView::mouseReleaseEvent(QMouseEvent *event)
    {
        // leave pan mode on right button release; clear flag and restore cursor
        if (_pan && event->button() == Qt::RightButton)
        {
            _pan = false;
            unsetCursor();
            event->accept();
            return;
        }

        // in the future, left clicks will add vertices to a mask polygon
        event->ignore() // ?
    }

    void ImageView::mouseMoveEvent(QMouseEvent *event)
    {
        // pan-mode move; scroll image by appropriate amount
        if (_pan)
        {
            scrollBy(_panStartX - event->x(), _panStartY - event->y());
            _panStartX = event->x();
            _panStartY = event->y();
            event->accept();
            return;
        }

        // generic mouse move, hover events won't occur otherwise.
        QGraphicsView::mouseMoveEvent(event);
        // need to accept or ignore afterwards?
    }

    void ImageView::wheelEvent(QWheelEvent *event)
    {
        // disallow zooming while panning
        if (_pan)
        {
            event->ignore();
            return;
        }

        // handle mouse wheel zoom
        // perform scaling
        if (event->delta() > 0) zoomIn();
        else zoomOut();
        event->accept();
        return;
    }


    bool _pan;
    int _panStartX, _panStartY;
    const qreal _scale;
    QGraphicsScene *_scene;
    ImageItem *_imgItem;
};

class ImageItem : public QGraphicsPixmapItem
{
public:
    ImageItem(ImageView *view) : QGraphicsPixmapItem()
    {
    }

    void loadImage(const QString &path)
    {
        _pixmap.load(path);
        setPixmap(_pixmap);
    }

protected:
    virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event)
    {
        // update label with position in GUI here
    }

    QPixmap _pixmap;
};

我的第一个问题是,在我激活视图中的 startAoi() 之前一切正常(连接到用户按下的 GUI 中的按钮)。在此之前,平移工作正常,鼠标光标变成张开的手。激活 AOI 模式后,光标在图像上方变为十字形,按下并拖动右键可平移视图,但不会更改光标,就好像图像项的光标优先于视图的光标一样。停用 AOI 模式后,十字光标消失,不再触发悬停事件,但这一次,虽然平移仍然有效,但我在拖动时仍停留在普通箭头光标上。

编辑:基本上,对于 QGraphicsItem 来说, unsetCursor() 本身不会删除光标,而是将其更改为标准箭头,该箭头是持久的并覆盖父视图的光标。

另一个问题是我不确定整个鼠标处理是否正确布置。例如,我不知道是否应该处理视图、场景或图像项的鼠标事件覆盖中的平移和缩放。我想因为它们对于整个视图是全局的(将来将包含其他对象),所以它应该属于顶部项目 - 视图。另外,我不确定何时应该对事件调用accept() 和ignore(),这实际上会做什么,以及何时应该调用父类的鼠标事件实现。任何见解将不胜感激。

I need to display MDI windows containing images in my application. I wanted to be able to drag-scroll the images using the right mouse button, zoom them using the mouse wheel, and also create polygon-shaped area-of-interest masks over them. To this end, I created a custom QGraphicsView-derived class (ImageView) which reimplements some mouse events. I also have a QGraphicsPixmapItem-derived class (ImageItem) for implementing hover events which update a cursor pixel-position indicator in the application's interface. Here's the outline (doesn't yet implement the mask polygons):

class ImageView : public QGraphicsView
{
    Q_OBJECT
public:
    ImageView(QWidget *parent) : QGraphicsView(parent), _pan(false), _panStartX(0), _panStartY(0), 
        _scale(1.2), _scene(NULL), _imgItem(NULL)
    {
        _scene = new QGraphicsScene(this); 
        _scene->setBackgroundBrush(Qt::lightGray);
        setScene(_scene);
        _imgItem = new ImageItem(this);
        _scene->addItem(_imgItem);
    }

    void showImage(const QString &path)
    {
        _imgItem->loadImage(path);
        setSceneRect(0, 0, _imgItem->imageWidth(), _imgItem->imageHeight()); 
    }

    void startAoi(AoiType type)
    {
        _imgItem->setAcceptHoverEvents(true);
        _imgItem->setCursor(Qt::CrossCursor);
        // explicit mouse tracking enable isn't necessary
    }

    void stopAoi()
    {
        _imgItem->unsetCursor();
        _imgItem->setAcceptHoverEvents(false);
    }

public slots:
    void zoomIn() { scale(_scale, _scale); }
    void zoomOut() { scale(1/_scale, 1/_scale); }

protected:
    void ImageView::mousePressEvent(QMouseEvent *event)
    {
        // enter pan mode; set flag, change cursor and store start position
        if (event->button() == Qt::RightButton)
        {
            _pan = true;
            _panStartX = event->x();
            _panStartY = event->y();
            setCursor(Qt::OpenHandCursor);
            // accept the event and skip the default implementation?
            event->accept();
            return;
        }

        // should do event accept here?
        event->ignore();
    }

    void ImageView::mouseReleaseEvent(QMouseEvent *event)
    {
        // leave pan mode on right button release; clear flag and restore cursor
        if (_pan && event->button() == Qt::RightButton)
        {
            _pan = false;
            unsetCursor();
            event->accept();
            return;
        }

        // in the future, left clicks will add vertices to a mask polygon
        event->ignore() // ?
    }

    void ImageView::mouseMoveEvent(QMouseEvent *event)
    {
        // pan-mode move; scroll image by appropriate amount
        if (_pan)
        {
            scrollBy(_panStartX - event->x(), _panStartY - event->y());
            _panStartX = event->x();
            _panStartY = event->y();
            event->accept();
            return;
        }

        // generic mouse move, hover events won't occur otherwise.
        QGraphicsView::mouseMoveEvent(event);
        // need to accept or ignore afterwards?
    }

    void ImageView::wheelEvent(QWheelEvent *event)
    {
        // disallow zooming while panning
        if (_pan)
        {
            event->ignore();
            return;
        }

        // handle mouse wheel zoom
        // perform scaling
        if (event->delta() > 0) zoomIn();
        else zoomOut();
        event->accept();
        return;
    }


    bool _pan;
    int _panStartX, _panStartY;
    const qreal _scale;
    QGraphicsScene *_scene;
    ImageItem *_imgItem;
};

class ImageItem : public QGraphicsPixmapItem
{
public:
    ImageItem(ImageView *view) : QGraphicsPixmapItem()
    {
    }

    void loadImage(const QString &path)
    {
        _pixmap.load(path);
        setPixmap(_pixmap);
    }

protected:
    virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event)
    {
        // update label with position in GUI here
    }

    QPixmap _pixmap;
};

My first problem is that everything works fine before I activate startAoi() in the view (connected to a button in the GUI which the user presses). Before that, panning works fine, with the mouse cursor changing into an open hand. After I activate the AOI mode, the cursor changes into a cross while over the image, and pressing and dragging the right button pans the view, but doesn't change the cursor, as if the image item's cursor takes precedence over the view's cursor. After I deactivate the AOI mode, the cross cursor disappears, hover events are no longer triggered, but this time while pan still works, I'm stuck with the plain arrow cursor while dragging.

EDIT: Basically, it looks like for a QGraphicsItem, unsetCursor() doesn't remove the cursor per se, but rather changes it to the standard arrow, which is persistent and overrides the parent view's cursor.

The other problem is I'm not sure I have the whole mouse processing laid out correctly. For example, I don't know if I should process the panning and zooming in the mouse events overrides of the view, the scene, or the image item. I figured since because they are global to the whole view (which will contain other objects in the future), this should belong in the top item - the view. Also, I'm not sure when I should call accept() and ignore() on the events, what this actually does, and when I should call the parent classes' implementations of mouse events. Any insight will be greatly appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

酒中人 2024-10-28 07:00:12

几个月前,我创建了一个类似的图像处理应用程序:图像拖动、放大/缩小+不同的自定义工具来在 QGraphicsView 上绘制(添加)特定项目。
我只使用了 setCursor() 函数,而不是 unsetCursor()。

我创建了一个计时器,并根据 GraphicsView 子类中存储的自定义“工具类型”标志并考虑键盘和键盘的当前状态,在计时器事件函数内设置了光标形状。老鼠。
我认为这更方便,因为:

a)即使用户不移动鼠标或单击特定对象,它也允许您设置光标形状 - 例如,如果选择“缩放工具”,您可以将光标设置为a (+) [放大光标]。如果用户按住 Ctrl 键,您可以将光标设置为 (-) [缩小光标]

b) 无需存储任何光标状态:您只需根据当前选择设置正确的光标形状工具和鼠标/键盘状态。

c) 这是让你的光标形状快速响应所有键盘/鼠标/工具更改事件的最简单方法

我希望这个计时器的想法可以帮助你克服所有与光标形状相关的问题。
间隔 30 毫秒的计时器就可以了。

I have created a similar image manipulation application some months ago: image drag, zoom-in/out + different custom tools to draw (add) specific items on a QGraphicsView.
I used only the setCursor() function - not the unsetCursor().

I created a timer and I set the cursor-shape inside the timer-event function according to a custom "Tool-Type" flag stored in my GraphicsView subclass and taking into account the current state of keyboard & mouse.
I think this is more convenient as:

a) It allows you to set the cursor-shape even if the user does not move the mouse or click on a specific object - e.g. if a "zoom tool" is selected you can set the cursor to a (+) [zoom-in cursor]. If the user holds-down the Ctrl-Key you can set the cursor to a (-) [zoom-out cursor]

b) There is no need to store any cursor states: You just set the correct cursor-shape according to currently selected tool and the mouse/keyboard state.

c) It's the easiest way to make your cursor-shape to quickly respond to all keyboard/mouse/tool-change events

I hope this timer idea can help you to overcome all cursor-shape related problems.
A timer with a 30 msec interval it will be just fine.

汹涌人海 2024-10-28 07:00:12

看起来您遇到了这个错误:

https://bugreports.qt-project.org/browse/ QTBUG-4190

解决方法是将光标设置在视图上而不是项目上

Looks like you hit this bug:

https://bugreports.qt-project.org/browse/QTBUG-4190

Workaround is to set the cursor on the view rather than the item

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文