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

閱讀時間約 17 分鐘

在 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 中監控滑鼠的畫布內外狀態,並設置滑鼠移出畫布後的繪製行為。

avatar-img
122會員
204內容數
本業是影像辨識軟體開發,閒暇時間進修AI相關內容,將學習到的內容寫成文章分享。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
螃蟹_crab的沙龍 的其他內容
進一步探討 PyQt5 的一些進階功能,具體包括如何使用更多的控件如 QComboBox(下拉框)、QTableWidget(表格),如何使用 QMainWindow 建立多窗口應用,及如何自訂樣式和設計。 1. 使用 QComboBox(下拉框) QComboBox 是一個下拉框控件,用來顯示
以下是一個關於 PyQt5 基礎教學的簡單入門文,帶你一步步了解如何從零開始建立 PyQt5 應用程序。 1. 安裝 PyQt5 首先,確保你安裝了 PyQt5 庫。打開終端或命令提示符,輸入以下命令進行安裝: pip install pyqt5 2. 建立最簡單的 PyQt5 應用程序
進一步探討 PyQt5 的一些進階功能,具體包括如何使用更多的控件如 QComboBox(下拉框)、QTableWidget(表格),如何使用 QMainWindow 建立多窗口應用,及如何自訂樣式和設計。 1. 使用 QComboBox(下拉框) QComboBox 是一個下拉框控件,用來顯示
以下是一個關於 PyQt5 基礎教學的簡單入門文,帶你一步步了解如何從零開始建立 PyQt5 應用程序。 1. 安裝 PyQt5 首先,確保你安裝了 PyQt5 庫。打開終端或命令提示符,輸入以下命令進行安裝: pip install pyqt5 2. 建立最簡單的 PyQt5 應用程序
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
選擇一款適合自己的繪圖軟體是開始電繪的重要步驟。以下是幾款流行的數位繪圖軟體
Thumbnail
這篇內容,將透過實戰教學,介紹GameMaker中的Camera。包括Camera的簡介、設定Camera的方法、Viewport的介紹。
Thumbnail
在影像處理中,有時候我們只想特別關注某個感興趣的區域時,就是ROI的概念,擷取此範圍的圖像來做處理。 設定超過圖像邊界時就會報錯,本文主要介紹如何擷取影像的同時,避免設定錯誤造成程式崩潰的狀況。 擷取圖像示意圖 ROI程式範例 import cv2 import numpy as np
Thumbnail
在某些特殊情況下,需要將圖片進行黑白反轉,例如Tesseract(OCR辨識引擎)就有建議黑底白字的狀況下辨識率較高。 本文將使用 NumPy 進行影像黑白反轉,並顯示反轉前後的影像。
這個系列的教學會列出基本上所有常見的造型和一些基礎 , 讓各位可以自行搭配造型~在這個第五篇呢 , 我們會來一起了解: 常見的按鈕樣式特性,怎麼使用他們,他們的作用是甚麼。讓我們一起了解如何自訂滑鼠以及使用常見的按鈕樣式特性,學會讓按鈕播放音效,切換滑鼠造型,以及避免圖片透明的地方被點擊到。
Thumbnail
此篇為上一篇文章的延伸,先辦別是螺絲還是螺母才擷取出影像。 [OpenCV應用][Python]利用findContours辨識螺絲還是螺母 因為可能會需要另外處理螺絲與螺母才可以準確地去做量測,所以第一步就是先分割出這兩種的圖像。
Thumbnail
開啟零件環境並選擇任一平面進入草圖模式 (詳Lesson 1),下圖以連續線作說明
Thumbnail
Ae 小技巧:宣紙噪點+抽幀效果 動態後記系列會記錄一些我在製作中的記錄,可能是分解動畫、小技巧、發想、腳本......等等。 每篇都是小短篇,就是補充用的小筆記,沒有前後順序,可跳著閱讀。
Thumbnail
本文將利用OpenCV的findContours函式,從找到的輪廓中來計算物件的面積,周長,邊界框等屬性,從而得到物體的寬度與高度。 一般來說,我們在進行輪廓檢測時,會先進行圖像二值化,將對象轉換為白色,背景為黑色。這樣,在找到輪廓後,輪廓的點就會以白色表示,背景為黑色。 結果圖 從圖中綠色框
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
選擇一款適合自己的繪圖軟體是開始電繪的重要步驟。以下是幾款流行的數位繪圖軟體
Thumbnail
這篇內容,將透過實戰教學,介紹GameMaker中的Camera。包括Camera的簡介、設定Camera的方法、Viewport的介紹。
Thumbnail
在影像處理中,有時候我們只想特別關注某個感興趣的區域時,就是ROI的概念,擷取此範圍的圖像來做處理。 設定超過圖像邊界時就會報錯,本文主要介紹如何擷取影像的同時,避免設定錯誤造成程式崩潰的狀況。 擷取圖像示意圖 ROI程式範例 import cv2 import numpy as np
Thumbnail
在某些特殊情況下,需要將圖片進行黑白反轉,例如Tesseract(OCR辨識引擎)就有建議黑底白字的狀況下辨識率較高。 本文將使用 NumPy 進行影像黑白反轉,並顯示反轉前後的影像。
這個系列的教學會列出基本上所有常見的造型和一些基礎 , 讓各位可以自行搭配造型~在這個第五篇呢 , 我們會來一起了解: 常見的按鈕樣式特性,怎麼使用他們,他們的作用是甚麼。讓我們一起了解如何自訂滑鼠以及使用常見的按鈕樣式特性,學會讓按鈕播放音效,切換滑鼠造型,以及避免圖片透明的地方被點擊到。
Thumbnail
此篇為上一篇文章的延伸,先辦別是螺絲還是螺母才擷取出影像。 [OpenCV應用][Python]利用findContours辨識螺絲還是螺母 因為可能會需要另外處理螺絲與螺母才可以準確地去做量測,所以第一步就是先分割出這兩種的圖像。
Thumbnail
開啟零件環境並選擇任一平面進入草圖模式 (詳Lesson 1),下圖以連續線作說明
Thumbnail
Ae 小技巧:宣紙噪點+抽幀效果 動態後記系列會記錄一些我在製作中的記錄,可能是分解動畫、小技巧、發想、腳本......等等。 每篇都是小短篇,就是補充用的小筆記,沒有前後順序,可跳著閱讀。
Thumbnail
本文將利用OpenCV的findContours函式,從找到的輪廓中來計算物件的面積,周長,邊界框等屬性,從而得到物體的寬度與高度。 一般來說,我們在進行輪廓檢測時,會先進行圖像二值化,將對象轉換為白色,背景為黑色。這樣,在找到輪廓後,輪廓的點就會以白色表示,背景為黑色。 結果圖 從圖中綠色框