如何用同一鼠标事件实现PyQt5画布的移动与拉伸功能?
实现右键拖动同时支持移动和拉伸PyQt5截图Overlay
需求:基于PyQt5的截图工具中,用右键拖动(b3-move)同时实现两个操作:
- 移动overlay/canvas到目标屏幕位置
- 拉伸overlay/canvas以匹配图片/PDF区域
当前问题:需手动修改坐标或setGeometry参数来匹配区域,效率低,希望通过鼠标操作自动化调整。
要实现同一右键拖动事件对应两种功能,核心是通过鼠标点击位置区分操作模式:点击窗口内部非边缘区域时触发移动,点击窗口边缘(或角落)时触发拉伸。以下是修改后的完整代码:
import sys from PyQt5 import QtWidgets, QtCore, QtGui import tkinter as tk from PIL import ImageGrab import numpy as np import cv2 # 补充原代码缺失的初始尺寸定义,可根据需求调整 dim = (800, 600) class MyWidget(QtWidgets.QWidget): def __init__(self): super().__init__() root = tk.Tk() screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() x = screen_width/2 - dim[0]/2 y = screen_height/2 - dim[1]/2 self.setGeometry(x, y, dim[0], dim[1]) self.setWindowTitle('Snipping') self.begin = QtCore.QPoint() self.end = QtCore.QPoint() self.setWindowOpacity(0.8) QtWidgets.QApplication.setOverrideCursor( QtGui.QCursor(QtCore.Qt.CrossCursor)) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) # 新增操作状态变量 self.is_moving = False self.is_resizing = False self.resize_edge = None # 记录拉伸方向:left/right/top/bottom等 self.init_geo = QtCore.QRect() # 窗口初始几何信息 self.init_mouse_pos = QtCore.QPoint() # 鼠标初始位置 print('Capture the screen...') self.show() def paintEvent(self, event): qp = QtGui.QPainter(self) qp.setPen(QtGui.QPen(QtGui.QColor('black'), 3)) qp.setBrush(QtGui.QColor(128, 128, 255, 128)) qp.drawRect(QtCore.QRect(self.begin, self.end)) def get_resize_edge(self, pos): """判断鼠标点击位置是否在窗口边缘,返回对应的拉伸方向""" edge_threshold = 10 # 边缘检测阈值,可根据需求调整 rect = self.rect() # 判断四个角落 if pos.x() <= edge_threshold and pos.y() <= edge_threshold: return 'top-left' elif pos.x() >= rect.width() - edge_threshold and pos.y() <= edge_threshold: return 'top-right' elif pos.x() <= edge_threshold and pos.y() >= rect.height() - edge_threshold: return 'bottom-left' elif pos.x() >= rect.width() - edge_threshold and pos.y() >= rect.height() - edge_threshold: return 'bottom-right' # 判断四条边 elif pos.x() <= edge_threshold: return 'left' elif pos.x() >= rect.width() - edge_threshold: return 'right' elif pos.y() <= edge_threshold: return 'top' elif pos.y() >= rect.height() - edge_threshold: return 'bottom' # 非边缘区域 else: return None def mousePressEvent(self, event): if event.button() == QtCore.Qt.RightButton: # 右键处理移动/拉伸逻辑 self.init_mouse_pos = event.globalPos() self.init_geo = self.geometry() self.resize_edge = self.get_resize_edge(event.pos()) if self.resize_edge: self.is_resizing = True # 切换对应拉伸光标 cursor_map = { 'left': QtCore.Qt.SizeHorCursor, 'right': QtCore.Qt.SizeHorCursor, 'top': QtCore.Qt.SizeVerCursor, 'bottom': QtCore.Qt.SizeVerCursor, 'top-left': QtCore.Qt.SizeFDiagCursor, 'top-right': QtCore.Qt.SizeBDiagCursor, 'bottom-left': QtCore.Qt.SizeBDiagCursor, 'bottom-right': QtCore.Qt.SizeFDiagCursor } QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(cursor_map[self.resize_edge])) else: self.is_moving = True QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.OpenHandCursor)) else: # 保留原左键截图框选逻辑 self.begin = event.pos() self.end = self.begin self.update() def mouseMoveEvent(self, event): if self.is_moving: # 移动窗口:计算鼠标偏移量,更新窗口位置 delta = event.globalPos() - self.init_mouse_pos self.setGeometry(self.init_geo.x() + delta.x(), self.init_geo.y() + delta.y(), self.init_geo.width(), self.init_geo.height()) elif self.is_resizing: # 拉伸窗口:根据边缘方向调整大小和位置 delta = event.globalPos() - self.init_mouse_pos new_x = self.init_geo.x() new_y = self.init_geo.y() new_width = self.init_geo.width() new_height = self.init_geo.height() if self.resize_edge in ['left', 'top-left', 'bottom-left']: new_x += delta.x() new_width -= delta.x() if self.resize_edge in ['right', 'top-right', 'bottom-right']: new_width += delta.x() if self.resize_edge in ['top', 'top-left', 'top-right']: new_y += delta.y() new_height -= delta.y() if self.resize_edge in ['bottom', 'bottom-left', 'bottom-right']: new_height += delta.y() # 限制窗口最小尺寸,避免异常 min_size = 100 new_width = max(new_width, min_size) new_height = max(new_height, min_size) self.setGeometry(new_x, new_y, new_width, new_height) else: # 保留原左键拖动截图区域逻辑 self.end = event.pos() self.update() def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton: # 重置右键操作状态和光标 self.is_moving = False self.is_resizing = False QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor)) else: # 保留原左键截图完成逻辑,修正截图坐标为屏幕绝对坐标 self.close() x1 = min(self.begin.x(), self.end.x()) y1 = min(self.begin.y(), self.end.y()) x2 = max(self.begin.x(), self.end.x()) y2 = max(self.begin.y(), self.end.y()) img = ImageGrab.grab(bbox=(x1 + self.geometry().x(), y1 + self.geometry().y(), x2 + self.geometry().x(), y2 + self.geometry().y())) img = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB) cv2.imshow('Captured Image', img) cv2.waitKey(0) cv2.destroyAllWindows() global s s = f"{x1 + self.geometry().x()},{dim[1] - y1 + self.geometry().y()},{x2 + self.geometry().x()},{dim[1] - y2 + self.geometry().y()}" print(s) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) window = MyWidget() window.show() app.aboutToQuit.connect(app.deleteLater) sys.exit(app.exec_())
关键功能说明:
- 边缘检测:通过
get_resize_edge方法判断鼠标是否在窗口边缘(阈值可调整),区分移动和拉伸模式 - 移动逻辑:右键点击窗口内部时,记录初始窗口位置和鼠标位置,拖动时根据偏移量更新窗口坐标
- 拉伸逻辑:右键点击边缘时,根据边缘方向调整窗口的位置和大小,同时限制最小尺寸避免异常
- 光标切换:不同操作模式下切换对应光标,提升用户交互体验
- 截图坐标修正:原代码截图坐标基于窗口内部,修改后加上窗口的屏幕坐标,确保截图区域与视觉区域一致
内容的提问来源于stack exchange,提问作者user13347865
相关产品推荐
相关产品推荐

