更新於 2024/10/27閱讀時間約 17 分鐘

[PyQt5][Python]偵測滑鼠在 PyQt 畫布內外

在 PyQt 的應用程式中,我們經常需要追蹤滑鼠位置,尤其是在建立繪圖工具或處理繪圖邊界的情況下。以下是如何檢測滑鼠在畫布內外狀態的教學,並包含滑鼠事件處理及邊界判斷的細節。


目標

  1. 監測滑鼠進入與離開畫布的狀態,當滑鼠進入畫布範圍內時啟動繪製,而當滑鼠超出範圍時記錄最後一個有效位置。
  2. 實現邊界事件處理,讓滑鼠離開畫布範圍時,自動設置繪圖的終點為邊界內最後的位置,並觸發相應的繪製行為。

關鍵概念與步驟

  1. eventFilter方法:使用 PyQt 的 eventFilter 來攔截滑鼠離開或進入畫布的事件(QEvent.LeaveQEvent.Enter)。
  2. 滑鼠事件處理:重寫 mousePressEvent, mouseMoveEvent, 和 mouseReleaseEvent 來跟蹤滑鼠的位置,並更新畫布內外的狀態。
  3. 邊界檢查與記錄:在滑鼠移動時,確認滑鼠是否在畫布內。若滑鼠移動到邊界外,則記錄最後一個在邊界內的有效位置,並用它作為終點位置。



畫面呈現

假設整個白色區域都是畫布內,超出去就是畫布外,去偵測滑鼠在畫布上的位置並秀出

raw-image


詳細教學步驟

步驟 1:建立 ZoomableGraphicsView 類別並設置滑鼠事件

此類別繼承自 QGraphicsView,並會擴展滑鼠事件處理、滾輪縮放功能,以及滑鼠的邊界檢查邏輯。

class ZoomableGraphicsView(QGraphicsView):
def __init__(self, *args, **kwargs):
super(ZoomableGraphicsView, self).__init__(*args, **kwargs)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setRenderHint(QPainter.Antialiasing)
self.setMouseTracking(True) # 啟用滑鼠追蹤
self.viewport().installEventFilter(self) # 安裝事件過濾器

# 相關變數初始化
self.mousePressCallback = None
self.mouseMoveCallback = None
self.mouseReleaseCallback = None
self.mouseLeaveCallback = None
self.last_in_bounds_pos = None # 記錄最後有效滑鼠位置

def eventFilter(self, source, event):
"""偵測滑鼠是否在畫布內或離開畫布"""
if event.type() == QEvent.Leave:
if self.last_in_bounds_pos is not None:
print(f"滑鼠離開畫布,最後位置為 {self.last_in_bounds_pos}")
if self.mouseLeaveCallback:
self.mouseLeaveCallback(event) # 調用離開事件處理
elif event.type() == QEvent.Enter:
print("滑鼠進入畫布範圍")

return super().eventFilter(source, event)

步驟 2:滑鼠事件的偵測與記錄最後有效位置

ZoomableGraphicsView 中實現 mouseMoveEvent 來檢查滑鼠是否超出畫布邊界範圍。此步驟將確認滑鼠是否在邊界內,若在內則更新 last_in_bounds_pos

def mouseMoveEvent(self, event):
"""當滑鼠在畫布內時更新位置,超出邊界則記錄最後位置"""
if self.rect().contains(event.pos()):
# 滑鼠在邊界內,更新有效位置
self.last_in_bounds_pos = self.mapToScene(event.pos())
if self.mouseMoveCallback:
self.mouseMoveCallback(event)
else:
# 滑鼠超出邊界,可根據需求處理
print("滑鼠超出畫布範圍")

步驟 3:設置終點位置並觸發相應繪製

當滑鼠移出畫布範圍時,將最後一個在畫布邊界內的位置設為繪製的終點,並觸發終點設置的繪製動作。

def set_end_position(self, end_position):
"""將最後有效位置設定為終點"""
self.end_pos = end_position
print(f"設定繪製終點為 {self.end_pos}")

實現完整的應用場景

以下展示了如何將 ZoomableGraphicsView 整合進主窗口應用程式中。此應用程式在 MaskROIEditor 中實現了基於滑鼠事件的繪圖功能。

import sys
from PyQt5.QtCore import QEvent, Qt, QPointF, QRectF
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QMainWindow, QApplication
from PyQt5.QtGui import QPainter, QPen, QColor

class ZoomableGraphicsView(QGraphicsView):
def __init__(self, *args, **kwargs):
super(ZoomableGraphicsView, self).__init__(*args, **kwargs)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setRenderHint(QPainter.Antialiasing)
self.setMouseTracking(True) # 啟用滑鼠追蹤
self.viewport().installEventFilter(self) # 安裝事件過濾器

# 相關變數初始化
self.mousePressCallback = None
self.mouseMoveCallback = None
self.mouseReleaseCallback = None
self.mouseLeaveCallback = None
self.last_in_bounds_pos = None # 記錄最後有效滑鼠位置

def eventFilter(self, source, event):
"""偵測滑鼠是否在畫布內或離開畫布"""
if event.type() == QEvent.Leave:
if self.last_in_bounds_pos is not None:
print(f"滑鼠離開畫布,最後位置為 {self.last_in_bounds_pos}")
if self.mouseLeaveCallback:
self.mouseLeaveCallback(event) # 調用離開事件處理
elif event.type() == QEvent.Enter:
print("滑鼠進入畫布範圍")

return super().eventFilter(source, event)

def mouseMoveEvent(self, event):
"""當滑鼠在畫布內時更新位置,超出邊界則記錄最後位置"""
if self.rect().contains(event.pos()):
# 滑鼠在邊界內,更新有效位置
self.last_in_bounds_pos = self.mapToScene(event.pos())
if self.mouseMoveCallback:
self.mouseMoveCallback(event)
else:
# 滑鼠超出邊界,可根據需求處理
print("滑鼠超出畫布範圍")

def set_end_position(self, end_position):
"""將最後有效位置設定為終點"""
self.end_pos = end_position
print(f"設定繪製終點為 {self.end_pos}")

class MaskROIEditor(QMainWindow):
def __init__(self, img=None):
super().__init__()
# 初始化畫布
self.view = ZoomableGraphicsView()
self.scene = QGraphicsScene(self) # 建立畫布
self.view.setScene(self.scene) # 將畫布設定到視圖中
self.setCentralWidget(self.view)

self.view.mousePressCallback = self.mousePressEvent
self.view.mouseMoveCallback = self.mouseMoveEvent
self.view.mouseReleaseCallback = self.mouseReleaseEvent
self.view.mouseLeaveCallback = self.handle_mouse_leave

# 初始化繪圖變數
self.is_drawing = False
self.start_pos = None
self.end_pos = None

# 設定畫布的大小(可根據需要調整)
self.scene.setSceneRect(QRectF(0, 0, 800, 600))

# 若 img 不為 None,可以在此顯示圖像
if img:
# 此處可將 img 作為背景顯示
pass

def mousePressEvent(self, event):
if event.button() == Qt.LeftButton and self.view.rect().contains(event.pos()):
self.is_drawing = True
self.start_pos = self.view.mapToScene(event.pos())

def mouseMoveEvent(self, event):
if self.is_drawing and self.view.rect().contains(event.pos()):
self.end_pos = self.view.mapToScene(event.pos())
# 動態繪製矩形框作為測試
self.scene.clear() # 每次移動時清除畫布內容
pen = QPen(QColor("blue"), 2)
self.scene.addRect(QRectF(self.start_pos, self.end_pos), pen)

def mouseReleaseEvent(self, event):
if self.is_drawing and self.view.rect().contains(event.pos()):
self.is_drawing = False
self.end_pos = self.view.mapToScene(event.pos())
# 完成繪圖
pen = QPen(QColor("red"), 2)
self.scene.addRect(QRectF(self.start_pos, self.end_pos), pen)

def handle_mouse_leave(self, event):
"""處理滑鼠離開畫布"""
if self.is_drawing and self.view.last_in_bounds_pos:
self.view.set_end_position(self.view.last_in_bounds_pos)
self.is_drawing = False
# 觸發最終繪製行為

if __name__ == "__main__":
app = QApplication(sys.argv)
editor = MaskROIEditor()
editor.show()
sys.exit(app.exec_())


重點回顧

  1. 安裝事件過濾器:使用 installEventFilter 來檢測 QEvent.LeaveQEvent.Enter 事件。
  2. 記錄最後位置:在 mouseMoveEvent 中更新 last_in_bounds_pos
  3. 邊界事件觸發:滑鼠超出畫布範圍時,將最後位置作為終點。

通過這些步驟,你可以有效地在 PyQt 中監控滑鼠的畫布內外狀態,並設置滑鼠移出畫布後的繪製行為。

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.