You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用同一鼠标事件实现PyQt5画布的移动与拉伸功能?

实现右键拖动同时支持移动和拉伸PyQt5截图Overlay

需求:基于PyQt5的截图工具中,用右键拖动(b3-move)同时实现两个操作:

  1. 移动overlay/canvas到目标屏幕位置
  2. 拉伸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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 20:30:32