Мне нужно отображать окна MDI, содержащие изображения в моем приложении. Я хотел иметь возможность перетаскивать изображения с помощью правой кнопки мыши, масштабировать их с помощью колеса мыши, а также создавать над ними полигональные маски с интересом. С этой целью я создал собственный класс (ImageView) класса QGraphicsView, который повторяет некоторые события мыши. У меня также есть класс, основанный на 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() в представлении (подключен к кнопке в графическом интерфейсе, который пользователь нажимает). До этого панорамирование прекрасно работает, когда курсор мыши переходит в открытую руку. После того, как я активирую режим AOI, курсор превращается в крест, а поверх изображения, а нажатие и перетаскивание правой кнопки панорамирует вид, но не изменяет курсор, как если бы курсор элемента изображения имел приоритет над курсором. После того, как я отключу режим AOI, перекрестный курсор исчезнет, события наведения больше не срабатывают, но на этот раз, пока кастрюля все еще работает, я застрял с простым стрелочным курсором при перетаскивании.
EDIT: В принципе, похоже, что для QGraphicsItem unsetCursor() не удаляет курсор как таковой, а скорее изменяет его на стандартную стрелку, которая является постоянной и переопределяет родительский вид курсор.
Другая проблема заключается в том, что я не уверен, что вся обработка мыши правильно выложена. Например, я не знаю, должен ли я обрабатывать панорамирование и масштабирование переопределений событий мыши, сцены или элемента изображения. Я понял, поскольку, поскольку они глобальны для всего представления (которое будет содержать другие объекты в будущем), это должно принадлежать верхнему элементу - представлению. Кроме того, я не уверен, когда я должен вызывать accept() и игнорировать() в событиях, что это на самом деле делает, и когда я должен вызывать реализации родительских классов событий мыши. Любое понимание будет с благодарностью.
Я создал аналогичное приложение для обработки изображений несколько месяцев назад: перетаскивание изображения, масштабирование/выведение + различные пользовательские инструменты для рисования (добавления) определенных элементов в QGraphicsView. Я использовал только функцию setCursor(), а не unsetCursor().
Я создал таймер, и я установил фигуру курсора внутри функции timer-event в соответствии с настраиваемым флагом Tool-Type, хранящимся в моем подклассе GraphicsView, и с учетом текущего состояния клавиатуры и мыши. Я думаю, что это более удобно, поскольку:
a) Он позволяет вам установить форму курсора, даже если пользователь не перемещает мышь или не нажимает на определенный объект - например, если выбран "инструмент масштабирования", вы можете установить курсор на (+) [курсор увеличения]. Если пользователь удерживает Ctrl-Key, вы можете установить курсор на (-) [курсор увеличения]
b) Нет необходимости хранить какие-либо состояния курсора: вы просто устанавливаете правильную форму курсора в соответствии с выбранным в данный момент инструментом и состоянием мыши/клавиатуры.
c) Это самый простой способ заставить вашу курсорную форму быстро реагировать на все события клавиатуры/мыши/смены инструментов
Я надеюсь, что эта идея таймера может помочь вам преодолеть все проблемы, связанные с курсором. Таймер с интервалом 30 мс будет очень хорошим.
Похоже, вы попали в эту ошибку:
https://bugreports.qt-project.org/browse/QTBUG-4190
Обходной путь - установить курсор на представление, а не на элемент