Note
本文代码实现参考Qt源码实现,记录位置进行移动即可
其他方法:
通过将鼠标右键事件识别为鼠标左键也是可以,但右键菜单不受控制
实现代码
- 视图移动: 通过记录鼠标位置,并通过偏差去计算移动距离,通过scrollbar实现移动
- 菜单响应: 如果在图元上有处理菜单事件的需求(响应复合的事件下,应考虑使用contextmenuevent,因为涉及到图元的重叠等情况),那么在处理鼠标移动完成时,如果鼠标在图元上,那么图元菜单会被响应。在理想状态下鼠标的事件在未发生移动时(即超过曼汉顿距离时),才响应菜单,移动视口后不应响应菜单事件。经过查阅Qt源码,发现在代码中因为contextmenuevent 在处理时是接连传递的,如下图所示,所以我们在处理时只能想办法让事件不响应。
header
#pragma once
#include <QGraphicsView>
class GraphicsView :public QGraphicsView{
Q_OBJECT
public:
GraphicsView(QWidget *parent = nullptr);
~GraphicsView() override ;
private:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
private:
QPoint m_lastPos;
bool m_bRightButtonPressed;
bool m_bRightButtonMoved;
bool m_bAcceptContextMenu = false;
QGraphicsItem* m_pRightButtonPressItem = nullptr;
QTimer* m_pContextMenuCheckTimer = nullptr;
DragMode m_storeDragMode;
};
source
#include "GraphicsView.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <QTimer>
#include <QDebug>
#include <QGraphicsItem>
#include <QMenu>
GraphicsView::GraphicsView(QWidget *parent) : QGraphicsView(parent) {
m_pContextMenuCheckTimer = new QTimer(this);
m_pContextMenuCheckTimer->setSingleShot(true);
connect(m_pContextMenuCheckTimer, &QTimer::timeout, [this](){
if(m_bAcceptContextMenu){
qWarning() << "because of timeout, prevent context menu";
m_bAcceptContextMenu = false;
}
});
}
GraphicsView::~GraphicsView() {
m_pContextMenuCheckTimer->stop();
delete m_pContextMenuCheckTimer;
}
void GraphicsView::mousePressEvent(QMouseEvent *event) {
m_storeDragMode = dragMode();
m_bAcceptContextMenu = false;
m_pRightButtonPressItem = nullptr;
if (event->button() == Qt::RightButton) {
event->accept();
m_bRightButtonPressed = true;
m_bRightButtonMoved = false;
m_lastPos = event->pos();
m_pRightButtonPressItem = itemAt(m_lastPos); // 假设已经设置了flag(QGraphicsItem::ItemIsSelectable)
setDragMode(QGraphicsView::ScrollHandDrag);
} else {
QGraphicsView::mousePressEvent(event);
}
}
void GraphicsView::mouseMoveEvent(QMouseEvent *event) {
if (m_bRightButtonPressed) {
event->accept();
QPoint delta = event->pos() - m_lastPos;
if(!m_bRightButtonMoved && delta.manhattanLength() < QApplication::startDragDistance()){
m_bRightButtonMoved = false;
return;
}
QScrollBar *hBar = horizontalScrollBar();
QScrollBar *vBar = verticalScrollBar();
hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x()));
vBar->setValue(vBar->value() - delta.y());
m_lastPos = event->pos();
m_bRightButtonMoved = true;
} else {
QGraphicsView::mouseMoveEvent(event);
}
}
void GraphicsView::mouseReleaseEvent(QMouseEvent *event) {
m_bAcceptContextMenu = false;
if(m_bRightButtonPressed){
event->accept();
if(m_bRightButtonMoved){
scene()->clearSelection();
}else{
m_bAcceptContextMenu = true;
if(!m_pRightButtonPressItem){
auto selects = scene()->selectedItems();
// 在contextMenuEvent中处理 多选图元下的菜单
}
else{
scene()->clearSelection();
m_pRightButtonPressItem->setSelected(true);
// 此时在默认的QGraphicsView::contextMenuEvent中,会将此图元的菜单弹出,请在图元的contextMenuEvent中处理图元的菜单
}
m_pContextMenuCheckTimer->start(20);
}
}else {
QGraphicsView::mouseReleaseEvent(event);
}
setDragMode(m_storeDragMode);
}
void GraphicsView::contextMenuEvent(QContextMenuEvent *event) {
if(m_bAcceptContextMenu)
{
m_bAcceptContextMenu = false;
if(m_pRightButtonPressItem) {
QGraphicsView::contextMenuEvent(event); // QGraphicsItem will respond to context menu event
}else{
event->accept();
QMenu multiSelectMenu;
multiSelectMenu.addAction("test");
multiSelectMenu.exec(event->globalPos());
}
}else{
event->accept();
}
}