[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
留言分享你的想法!
avatar-img
螃蟹_crab的沙龍
148會員
255內容數
本業是影像辨識軟體開發,閒暇時間進修AI相關內容,將學習到的內容寫成文章分享。
螃蟹_crab的沙龍的其他內容
2025/02/10
在 PyQt 中,信號與槽(Signal & Slot)機制是用來實現物件間通信的核心機制。 當信號被發射時,槽函數(Slot)根據預先連接的規則被調用。這一過程有時候會呈現出「排隊」的現象,即信號並非立即執行,而是先放入事件隊列,等待事件循環(Event Loop)逐一處理。 本文將介紹其原理
Thumbnail
2025/02/10
在 PyQt 中,信號與槽(Signal & Slot)機制是用來實現物件間通信的核心機制。 當信號被發射時,槽函數(Slot)根據預先連接的規則被調用。這一過程有時候會呈現出「排隊」的現象,即信號並非立即執行,而是先放入事件隊列,等待事件循環(Event Loop)逐一處理。 本文將介紹其原理
Thumbnail
2025/01/19
PyQt 中的 pyqtSignal 和 pyqtSlot 教學 在使用 PyQt5 開發 GUI 程式時,信號 (Signal) 和 槽 (Slot) 是重要的機制,用於元件之間的通訊。 PyQt 提供了 pyqtSignal 和 pyqtSlot 來自定義信號和槽,進一步實現更靈活的功能。
Thumbnail
2025/01/19
PyQt 中的 pyqtSignal 和 pyqtSlot 教學 在使用 PyQt5 開發 GUI 程式時,信號 (Signal) 和 槽 (Slot) 是重要的機制,用於元件之間的通訊。 PyQt 提供了 pyqtSignal 和 pyqtSlot 來自定義信號和槽,進一步實現更靈活的功能。
Thumbnail
2024/11/16
本篇文章將帶你一步步建立一個簡單的 PyQt5 GUI 應用程式,通過 yt-dlp 來下載 YT 視頻。你可以在這個應用中輸入視頻的 URL,並即時看到下載進度。 GUI介面 下載到開啟的資料夾路徑 前置條件 在開始之前,請確保你已經安裝了以下軟體和庫: 安裝 Python 確保你
Thumbnail
2024/11/16
本篇文章將帶你一步步建立一個簡單的 PyQt5 GUI 應用程式,通過 yt-dlp 來下載 YT 視頻。你可以在這個應用中輸入視頻的 URL,並即時看到下載進度。 GUI介面 下載到開啟的資料夾路徑 前置條件 在開始之前,請確保你已經安裝了以下軟體和庫: 安裝 Python 確保你
Thumbnail
看更多
你可能也想看
Thumbnail
TOMICA第一波推出吉伊卡哇聯名小車車的時候馬上就被搶購一空,一直很扼腕當時沒有趕緊入手。前陣子閒來無事逛蝦皮,突然發現幾家商場都又開始重新上架,價格也都回到正常水準,估計是官方又再補了一批貨,想都沒想就立刻下單! 同文也跟大家分享近期蝦皮購物紀錄、好用推薦、蝦皮分潤計畫的聯盟行銷!
Thumbnail
TOMICA第一波推出吉伊卡哇聯名小車車的時候馬上就被搶購一空,一直很扼腕當時沒有趕緊入手。前陣子閒來無事逛蝦皮,突然發現幾家商場都又開始重新上架,價格也都回到正常水準,估計是官方又再補了一批貨,想都沒想就立刻下單! 同文也跟大家分享近期蝦皮購物紀錄、好用推薦、蝦皮分潤計畫的聯盟行銷!
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
在 PyQt 的應用程式中,我們經常需要追蹤滑鼠位置,尤其是在建立繪圖工具或處理繪圖邊界的情況下。以下是如何檢測滑鼠在畫布內外狀態的教學,並包含滑鼠事件處理及邊界判斷的細節。 目標 監測滑鼠進入與離開畫布的狀態,當滑鼠進入畫布範圍內時啟動繪製,而當滑鼠超出範圍時記錄最後一個有效位置。 實現邊
Thumbnail
在 PyQt 的應用程式中,我們經常需要追蹤滑鼠位置,尤其是在建立繪圖工具或處理繪圖邊界的情況下。以下是如何檢測滑鼠在畫布內外狀態的教學,並包含滑鼠事件處理及邊界判斷的細節。 目標 監測滑鼠進入與離開畫布的狀態,當滑鼠進入畫布範圍內時啟動繪製,而當滑鼠超出範圍時記錄最後一個有效位置。 實現邊
Thumbnail
本文介紹如何在Windows命令行及VS Code中確認Python版本,並提供編寫第一個Python程式的步驟。重點在於註解的使用方式及其對程式可讀性的重要性。此外,還簡單介紹了turtle繪圖模組的應用,透過範例繪製佩佩豬,為Python學習者提供實用的參考與指導。
Thumbnail
本文介紹如何在Windows命令行及VS Code中確認Python版本,並提供編寫第一個Python程式的步驟。重點在於註解的使用方式及其對程式可讀性的重要性。此外,還簡單介紹了turtle繪圖模組的應用,透過範例繪製佩佩豬,為Python學習者提供實用的參考與指導。
Thumbnail
先前文章有使用連通域分析來印出物件的位置及高寬面積及達成物件定位等功能。 [OpenCV應用][Python]利用連通域分析達成物件定位 [OpenCV基礎][Python]connectedComponent連通域分析 [OpenCV][Python]印出圖像中OCR面積及位置 這次我們將
Thumbnail
先前文章有使用連通域分析來印出物件的位置及高寬面積及達成物件定位等功能。 [OpenCV應用][Python]利用連通域分析達成物件定位 [OpenCV基礎][Python]connectedComponent連通域分析 [OpenCV][Python]印出圖像中OCR面積及位置 這次我們將
Thumbnail
我, 手握著一支筆, 像拳頭握住那樣, 也許是一把刀,
Thumbnail
我, 手握著一支筆, 像拳頭握住那樣, 也許是一把刀,
Thumbnail
前言 上篇把定位講完,不過實務上很少真的用手刻,大多用錄製或者軟體輔助,先講XPATH主要是讓大家有個底,就像最近的AI風一樣,好玩是一回事,做出來的東西還是要人看得懂知道哪裡可能有問題。 這篇就會著重介紹如何錄製腳本並轉換成可以執行的程式。
Thumbnail
前言 上篇把定位講完,不過實務上很少真的用手刻,大多用錄製或者軟體輔助,先講XPATH主要是讓大家有個底,就像最近的AI風一樣,好玩是一回事,做出來的東西還是要人看得懂知道哪裡可能有問題。 這篇就會著重介紹如何錄製腳本並轉換成可以執行的程式。
Thumbnail
或許就如官網文件中所說的,lambda function就只是syntactic sugar而已,所以也就沒特別在意,直到在設計Game of Life的輸入介面時,因為需要用到,兜兜轉轉,費了好些功夫和時間,總算對它的用途和用法有比較完整的認識。
Thumbnail
或許就如官網文件中所說的,lambda function就只是syntactic sugar而已,所以也就沒特別在意,直到在設計Game of Life的輸入介面時,因為需要用到,兜兜轉轉,費了好些功夫和時間,總算對它的用途和用法有比較完整的認識。
Thumbnail
因應科技的快速成長 台灣孩子教育也在改革 電腦科技的程式課就有點像20年前的台灣英文教育剛開始一樣 英文能力變成企業的門檻標準 直到現在英文還是一樣重要 身為父母我們不一定能夠熟悉孩子的學業科目 但此時2022我們身處在資訊便利的時代 我們獲取相關學習內容不用特別的到圖書館或認識專家
Thumbnail
因應科技的快速成長 台灣孩子教育也在改革 電腦科技的程式課就有點像20年前的台灣英文教育剛開始一樣 英文能力變成企業的門檻標準 直到現在英文還是一樣重要 身為父母我們不一定能夠熟悉孩子的學業科目 但此時2022我們身處在資訊便利的時代 我們獲取相關學習內容不用特別的到圖書館或認識專家
Thumbnail
Youtube上逛著逛著看到techwithtim的線上教學,這是一個pygame的模組練習,只\是我想了解深一點的是物件導向的寫法應用。影片大約兩小時,實際邊動手coding,一邊看著影片的講解,結果花在這上面的時間遠遠超過我的預期。
Thumbnail
Youtube上逛著逛著看到techwithtim的線上教學,這是一個pygame的模組練習,只\是我想了解深一點的是物件導向的寫法應用。影片大約兩小時,實際邊動手coding,一邊看著影片的講解,結果花在這上面的時間遠遠超過我的預期。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News