QGraphicsView从键盘和鼠标接收输入事件,并将这些事件转换为场景事件(在适当的情况下将使用的坐标转换为场景坐标),然后再将事件发送到可视化场景。
QGraphicsScene的事件传播体系结构将场景事件传递到项目,并管理项目之间的传播。如果场景在特定位置收到鼠标按下事件,则场景会将事件传递给位于该位置的任何项目。
以鼠标点击事件的传播为例:
1、在qgraphicsview.cpp中的鼠标点击事件中,将鼠标事件的数据赋值给新建的场景鼠标事件,并将该事件传递给场景。
void QGraphicsView::mousePressEvent(QMouseEvent *event)
{
Q_D(QGraphicsView);
// 存储此事件,用于重播、计算增量和滚动拖动;
// 即使在非交互模式下,也允许滚动拖动,因此在函数的最开始存储事件。
// Store this event for replaying, finding deltas, and for
// scroll-dragging; even in non-interactive mode, scroll hand dragging is
// allowed, so we store the event at the very top of this function.
d->storeMouseEvent(event);
d->lastMouseEvent.setAccepted(false);
if (d->sceneInteractionAllowed) {
// Store some of the event's button-down data.// 存储一些按钮按下的数据
d->mousePressViewPoint = event->pos();
d->mousePressScenePoint = mapToScene(d->mousePressViewPoint);
d->mousePressScreenPoint = event->globalPos();
d->lastMouseMoveScenePoint = d->mousePressScenePoint;
d->lastMouseMoveScreenPoint = d->mousePressScreenPoint;
d->mousePressButton = event->button();
if (d->scene) {
// Convert and deliver the mouse event to the scene.
// 将鼠标事件转换并传递给场景。
QGraphicsSceneMouseEvent mouseEvent(QEvent::GraphicsSceneMousePress);
mouseEvent.setWidget(viewport());
mouseEvent.setButtonDownScenePos(d->mousePressButton, d->mousePressScenePoint);
mouseEvent.setButtonDownScreenPos(d->mousePressButton, d->mousePressScreenPoint);
mouseEvent.setScenePos(d->mousePressScenePoint);
mouseEvent.setScreenPos(d->mousePressScreenPoint);
mouseEvent.setLastScenePos(d->lastMouseMoveScenePoint);
mouseEvent.setLastScreenPos(d->lastMouseMoveScreenPoint);
mouseEvent.setButtons(event->buttons());
mouseEvent.setButton(event->button());
mouseEvent.setModifiers(event->modifiers());
mouseEvent.setSource(event->source());
mouseEvent.setFlags(event->flags());
mouseEvent.setAccepted(false);
if (event->spontaneous())
qt_sendSpontaneousEvent(d->scene, &mouseEvent);
else
QCoreApplication::sendEvent(d->scene, &mouseEvent);
// 更新原始鼠标事件的接受状态。
// Update the original mouse event accepted state.
bool isAccepted = mouseEvent.isAccepted();
event->setAccepted(isAccepted);
// Update the last mouse event accepted state.
d->lastMouseEvent.setAccepted(isAccepted);
if (isAccepted)
return;
}
}
#if QT_CONFIG(rubberband)
if (d->dragMode == QGraphicsView::RubberBandDrag && !d->rubberBanding) {
if (d->sceneInteractionAllowed) {
// Rubberbanding is only allowed in interactive mode.
event->accept();
d->rubberBanding = true;
d->rubberBandRect = QRect();
if (d->scene) {
bool extendSelection = (event->modifiers() & Qt::ControlModifier) != 0;
if (extendSelection) {
d->rubberBandSelectionOperation = Qt::AddToSelection;
} else {
d->rubberBandSelectionOperation = Qt::ReplaceSelection;
d->scene->clearSelection();
}
}
}
} else
#endif
if (d->dragMode == QGraphicsView::ScrollHandDrag && event->button() == Qt::LeftButton) {
//在滚动拖动模式下,左键按下开始手动滚动。
// Left-button press in scroll hand mode initiates hand scrolling.
event->accept();
d->handScrolling = true;
d->handScrollMotions = 0;
#ifndef QT_NO_CURSOR
viewport()->setCursor(Qt::ClosedHandCursor);
#endif
}
}
2、在qgraphicsscene.cpp中的鼠标点击事件中,
如果鼠标抓取项为空,新建鼠标悬浮事件,分发该事件。
然后在mousePressEventHandler内处理该鼠标点击事件。
void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
Q_D(QGraphicsScene);
if (d->mouseGrabberItems.isEmpty()) {
// Dispatch hover events
QGraphicsSceneHoverEvent hover;
_q_hoverFromMouseEvent(&hover, mouseEvent);
d->dispatchHoverEvent(&hover);
}
d->mousePressEventHandler(mouseEvent);
}
3、场景私有类的鼠标事件处理函数中,
(1)、首先将鼠标事件的状态设置为忽略;
(2)、将事件发送给任何已存在的鼠标抓取项;
(3)、temsAtPosition查找在事件位置的所有项目,存储到cachedItemsUnderMouse;
(4)、更新窗口激活状态;
(5)、setFocusItem依次设置cachedItemsUnderMouse中项目为焦点项;
(6)、检查场景模态性;
(7)、sendMouseEvent将鼠标按下事件逐个发送给所有cachedItemsUnderMouse中项目;
(8)、事件还是被忽略吗?那么鼠标按下事件发送给场景。重置鼠标抓取项,清除选择,清除焦点,并保持事件被忽略以便能够传播到原始视图。
void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mouseEvent)
{
Q_Q(QGraphicsScene);
// Ignore by default, unless we find a mouse grabber that accepts it.
//默认情况下忽略事件,除非我们找到能够接收它的鼠标抓取项。
mouseEvent->ignore();//忽略该事件,往父组件传播
// Deliver to any existing mouse grabber.将事件发送给任何已存在的鼠标抓取项。
if (!mouseGrabberItems.isEmpty()) {
if (mouseGrabberItems.constLast()->isBlockedByModalPanel())
return;
//默认情况下忽略事件,但我们在传递后不考虑事件的接受状态;毕竟鼠标已经被抓取了。
// The event is ignored by default, but we disregard the event's
// accepted state after delivery; the mouse is grabbed, after all.
sendMouseEvent(mouseEvent);
return;
}
// Start by determining the number of items at the current position.首先确定当前位置处的项目数。
// Reuse value from earlier calculations if possible.如果可能,重用之前的计算结果。
if (cachedItemsUnderMouse.isEmpty()) {
cachedItemsUnderMouse = itemsAtPosition(mouseEvent->screenPos(),
mouseEvent->scenePos(),
mouseEvent->widget());
}
// Update window activation.更新窗口激活状态。
QGraphicsItem *topItem = cachedItemsUnderMouse.value(0);
QGraphicsWidget *newActiveWindow = topItem ? topItem->window() : 0;
if (newActiveWindow && newActiveWindow->isBlockedByModalPanel(&topItem)) {
// pass activation to the blocking modal window将激活状态传递给阻塞的模态窗口
newActiveWindow = topItem ? topItem->window() : 0;
}
if (newActiveWindow != q->activeWindow())
q->setActiveWindow(newActiveWindow);
// Set focus on the topmost enabled item that can take focus. 设置焦点到最上层的可接收焦点的项。
bool setFocus = false;
foreach (QGraphicsItem *item, cachedItemsUnderMouse) {
if (item->isBlockedByModalPanel()
|| (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling)) {
// Make sure we don't clear focus.确保不清除焦点。
setFocus = true;
break;
}
if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable))) {
if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) {
setFocus = true;
if (item != q->focusItem() && item->d_ptr->mouseSetsFocus)
q->setFocusItem(item, Qt::MouseFocusReason);
break;
}
}
if (item->isPanel())
break;
if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation)
break;
}
// Check for scene modality. 检查场景模态性。
bool sceneModality = false;
for (int i = 0; i < modalPanels.size(); ++i) {
if (modalPanels.at(i)->panelModality() == QGraphicsItem::SceneModal) {
sceneModality = true;
break;
}
}
// If nobody could take focus, clear it.如果没有任何项能够接收焦点,清除焦点。
if (!stickyFocus && !setFocus && !sceneModality)
q->setFocusItem(0, Qt::MouseFocusReason);
// Any item will do.随便找个项即可。
if (sceneModality && cachedItemsUnderMouse.isEmpty())
cachedItemsUnderMouse << modalPanels.constFirst();
// Find a mouse grabber by sending mouse press events to all mouse grabber
// candidates one at a time, until the event is accepted. It's accepted by
// default, so the receiver has to explicitly ignore it for it to pass
// through.
// 通过将鼠标按下事件逐个发送给所有鼠标抓取项的候选项,找到一个鼠标抓取项。
// 默认情况下事件被接受,因此接收者必须明确地忽略它才能传递。
foreach (QGraphicsItem *item, cachedItemsUnderMouse) {
if (!(item->acceptedMouseButtons() & mouseEvent->button())) {
// Skip items that don't accept the event's mouse button.跳过不接受该事件鼠标按钮的项。
continue;
}
//检查该项是否被模态面板阻塞,并将鼠标事件发送给阻塞的面板而不是该项。
// Check if this item is blocked by a modal panel and deliver the mouse event to the
// blocking panel instead of this item if blocked.
(void) item->isBlockedByModalPanel(&item);
grabMouse(item, /* implicit = */ true);
mouseEvent->accept();
//检查我们要发送事件的项是否被禁用(在发送事件之前)。
// check if the item we are sending to are disabled (before we send the event)
bool disabled = !item->isEnabled();
bool isPanel = item->isPanel();
if (mouseEvent->type() == QEvent::GraphicsSceneMouseDoubleClick
&& item != lastMouseGrabberItem && lastMouseGrabberItem) {
// If this item is different from the item that received the last
// mouse event, and mouseEvent is a doubleclick event, then the
// event is converted to a press. Known limitation:
// Triple-clicking will not generate a doubleclick, though.
// 如果该项与上次接收鼠标事件的项不同,并且鼠标事件是一个双击事件,
// 则将事件转换为按下事件。已知限制:无法生成三击事件的双击事件。
QGraphicsSceneMouseEvent mousePress(QEvent::GraphicsSceneMousePress);
mousePress.spont = mouseEvent->spont;
mousePress.accept();
mousePress.setButton(mouseEvent->button());
mousePress.setButtons(mouseEvent->buttons());
mousePress.setScreenPos(mouseEvent->screenPos());
mousePress.setScenePos(mouseEvent->scenePos());
mousePress.setModifiers(mouseEvent->modifiers());
mousePress.setWidget(mouseEvent->widget());
mousePress.setButtonDownPos(mouseEvent->button(),
mouseEvent->buttonDownPos(mouseEvent->button()));
mousePress.setButtonDownScenePos(mouseEvent->button(),
mouseEvent->buttonDownScenePos(mouseEvent->button()));
mousePress.setButtonDownScreenPos(mouseEvent->button(),
mouseEvent->buttonDownScreenPos(mouseEvent->button()));
sendMouseEvent(&mousePress);
mouseEvent->setAccepted(mousePress.isAccepted());
} else {
sendMouseEvent(mouseEvent);
}
bool dontSendUngrabEvents = mouseGrabberItems.isEmpty() || mouseGrabberItems.constLast() != item;
if (disabled) {
ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents);
break;
}
if (mouseEvent->isAccepted()) {
if (!mouseGrabberItems.isEmpty())
storeMouseButtonsForMouseGrabber(mouseEvent);
lastMouseGrabberItem = item;
return;
}
ungrabMouse(item, /* itemIsDying = */ dontSendUngrabEvents);
// Don't propagate through panels.不要继续传播到面板
if (isPanel)
break;
}
// 事件还是被忽略吗?那么鼠标按下事件发送给场景。
// 重置鼠标抓取项,清除选择,清除焦点,并保持事件被忽略以便能够传播到原始视图。
// Is the event still ignored? Then the mouse press goes to the scene.
// Reset the mouse grabber, clear the selection, clear focus, and leave
// the event ignored so that it can propagate through the originating
// view.
if (!mouseEvent->isAccepted()) {
clearMouseGrabber();
QGraphicsView *view = mouseEvent->widget() ? qobject_cast<QGraphicsView *>(mouseEvent->widget()->parentWidget()) : 0;
bool dontClearSelection = view && view->dragMode() == QGraphicsView::ScrollHandDrag;
bool extendSelection = (mouseEvent->modifiers() & Qt::ControlModifier) != 0;
dontClearSelection |= extendSelection;
if (!dontClearSelection) {
// Clear the selection if the originating view isn't in scroll
// hand drag mode. The view will clear the selection if no drag
// happened.
// 如果原始视图不处于滚动拖动模式,则清除选择。
// 如果没有发生拖动,视图将清除选择。
q->clearSelection();
}
}
}
4、在sendMouseEvent函数中,场景的私有类将事件发送给鼠标抓取项的最后一项
void QGraphicsScenePrivate::sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if (mouseEvent->button() == 0 && mouseEvent->buttons() == 0 && lastMouseGrabberItemHasImplicitMouseGrab) {
// ### This is a temporary fix for until we get proper mouse
// grab events.
clearMouseGrabber();
return;
}
QGraphicsItem *item = mouseGrabberItems.constLast();
if (item->isBlockedByModalPanel())
return;
const QTransform mapFromScene = item->d_ptr->genericMapFromSceneTransform(mouseEvent->widget());
const QPointF itemPos = mapFromScene.map(mouseEvent->scenePos());
for (int i = 0x1; i <= 0x10; i <<= 1) {
Qt::MouseButton button = Qt::MouseButton(i);
mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, itemPos));
mouseEvent->setButtonDownScenePos(button, mouseGrabberButtonDownScenePos.value(button, mouseEvent->scenePos()));
mouseEvent->setButtonDownScreenPos(button, mouseGrabberButtonDownScreenPos.value(button, mouseEvent->screenPos()));
}
mouseEvent->setPos(itemPos);
mouseEvent->setLastPos(mapFromScene.map(mouseEvent->lastScenePos()));
sendEvent(item, mouseEvent);
}
1、DrawView继承于QGraphicsView,主要实现放缩、刻度尺(控件)、文件操作。
2、DrawScene继承于QGraphicsScene,主要实现项目的排列、组合等操作、键盘、鼠标等事件的处理、背景的绘制。
在事件处理函数中调用DrawTool的mouseMoveEvent事件,管理GraphicsItem类对象的新建和管理。
3、DrawTool管理基类,子类SelectTool(选择工具)、RotationTool(旋转工具)、RectTool(矩形工具)、PolygonTool(折线工具)工具实现具体功能。
RectTool类有rectTool、roundRectTool、ellipseTool三个静态对象。
PolygonTool类有lineTool、polygonTool、bezierTool、polylineTool四个静态对象。在静态变量定义时,七个对象添加到DrawTool指针的列表中。
在mainwindow.cpp中,点击不同的新建图形按钮,改变DrawTool的DrawShape类静态变量c_drawShape值。然后在画布上点击鼠标并移动完成绘制的操作。
enum DrawShape
{
selection ,
rotation ,
line ,
rectangle ,
roundrect ,
ellipse ,
bezier,
polygon,
polyline,
};
在场景类的鼠标事件中,通过查找DrawTool指针列表中当前操作对应的管理工具,调用该工具的同名函数进行处理,实现绘制功能。
选择和旋转功能同理。
在RectTool的鼠标事件中新建矩形、椭圆、圆角矩形。
在PolygonTool的鼠标事件中新建线段、多边形、贝塞尔曲线、折线。
这些图形的共同基类是GraphicsItem。
4、GraphicsItem继承于QObject和模板类AbstractShapeType,处理具体到项目内部的事件。
template < typename BaseType = QGraphicsItem >
class AbstractShapeType : public BaseType
GraphicsRectTool继承于GraphicsItem,GraphicsEllipseItem继承于GraphicsRectTool。
GraphicsPolygonItem继承于GraphicsItem,GraphicsLineItem继承于GraphicsPolygonItem,GraphicsBezier继承于GraphicsPolygonItem。
paint函数绘制item,duplicate函数复制item,control函数改变形状,stretch函数改变大小。