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

PyQt5多屏幕截图异常:选屏后截图工具仍显示在主屏

多屏幕截图工具定位问题修复

我正在开发一款实时数据记录器,需要通过类截图工具的操作选择屏幕区域,记录该像素范围内随时间变化的值。目前代码能捕获主屏文本,但即便选择了其他屏幕,截图工具始终在主屏打开。期望代码能展示所有可用屏幕,并在选中的屏幕上打开截图工具。

原代码

import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PIL import ImageGrab
import numpy as np
import cv2
import pytesseract
import mss

class ScreenSelector(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Select Screen')
        self.setGeometry(100, 100, 800, 600)
        self.layout = QtWidgets.QVBoxLayout()
        
        self.label = QtWidgets.QLabel("Select a screen to capture:")
        self.layout.addWidget(self.label)
        
        # Create a frame for displaying screens
        self.screen_frame = QtWidgets.QFrame(self)
        self.screen_frame.setLayout(QtWidgets.QVBoxLayout())
        self.layout.addWidget(self.screen_frame)

        self.setLayout(self.layout)
        
        # Get available screens
        self.sct = mss.mss()
        self.monitors = self.sct.monitors

        # Display each screen's current content
        self.display_screens()

    def display_screens(self):
        """Display the current content of each screen as thumbnails."""
        for i, monitor in enumerate(self.monitors[1:], start=1):  # Skip the first monitor which is a total display
            screen_label = QtWidgets.QLabel(self)
            screen_label.setAlignment(QtCore.Qt.AlignCenter)
            screen_label.setText(f"Screen {i}")

            # Capture the current content of the screen
            img = self.sct.grab(monitor)
            img_np = np.array(img)
            img_rgb = cv2.cvtColor(img_np, cv2.COLOR_BGRA2RGB)
            img_thumb = cv2.resize(img_rgb, (200, 150))  # Resize for thumbnail display

            # Convert to QImage for display
            height, width, channel = img_thumb.shape
            bytes_per_line = 3 * width
            q_img = QtGui.QImage(img_thumb.data, width, height, bytes_per_line, QtGui.QImage.Format_RGB888)

            # Create a label for the thumbnail
            thumbnail_label = QtWidgets.QLabel(self)
            thumbnail_label.setPixmap(QtGui.QPixmap.fromImage(q_img))
            thumbnail_label.setFixedSize(200, 150)
            thumbnail_label.mousePressEvent = lambda event, index=i: self.select_screen(index)  # Connect click to selection

            # Add the screen label and thumbnail to the layout
            self.screen_frame.layout().addWidget(screen_label)
            self.screen_frame.layout().addWidget(thumbnail_label)

    def select_screen(self, index):
        """Proceed to snipping on the selected screen."""
        
        print("SELECTED SCREEN: ", self.monitors[index])
        
        # print(f"Selected screen: {index}")
        self.snipping_tool = SnippingTool(self.monitors[index])
        self.snipping_tool.show()  # Show snipping tool without full screen
        self.close()  # Close the screen selection window


class SnippingTool(QtWidgets.QWidget):
    def __init__(self, monitor):
        super().__init__()
        self.monitor = monitor
        
        print("Selected Monitor: ", monitor)
        
        # Set geometry based on selected monitor
        self.setGeometry(monitor["left"], monitor["top"], monitor["width"], monitor["height"])
        self.begin = QtCore.QPoint()
        self.end = QtCore.QPoint()
        self.setWindowOpacity(0.3)
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

        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 mousePressEvent(self, event):
        self.begin = event.pos()
        self.end = self.begin
        self.update()

    def mouseMoveEvent(self, event):
        self.end = event.pos()
        self.update()

    def mouseReleaseEvent(self, event):
        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())

        # Capture the selected area relative to the monitor coordinates
        img = ImageGrab.grab(bbox=(self.monitor["left"] + x1, self.monitor["top"] + y1,
                                    self.monitor["left"] + x2, self.monitor["top"] + y2))

        # Convert the captured image to OpenCV format
        img_cv = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)

        # Use Tesseract to extract text from the captured image
        extracted_text = pytesseract.image_to_string(img_cv)

        # Print the extracted text
        print("Extracted Text:\n", extracted_text)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    screen_selector = ScreenSelector()
    screen_selector.show()

    sys.exit(app.exec_())

问题分析与修复

问题核心在于SnippingTool的窗口属性设置顺序错误,且缺少置顶标志,导致窗口无法正确定位到选中屏幕。以下是修复后的完整代码:

import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PIL import ImageGrab
import numpy as np
import cv2
import pytesseract
import mss

class ScreenSelector(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('选择屏幕')
        self.setGeometry(100, 100, 800, 600)
        self.layout = QtWidgets.QVBoxLayout()
        
        self.label = QtWidgets.QLabel("选择要捕获的屏幕:")
        self.layout.addWidget(self.label)
        
        # 创建屏幕展示框架
        self.screen_frame = QtWidgets.QFrame(self)
        self.screen_frame.setLayout(QtWidgets.QVBoxLayout())
        self.layout.addWidget(self.screen_frame)

        self.setLayout(self.layout)
        
        # 获取可用屏幕
        self.sct = mss.mss()
        self.monitors = self.sct.monitors

        # 展示每个屏幕的当前内容
        self.display_screens()

    def display_screens(self):
        """展示每个屏幕的当前内容缩略图"""
        for i, monitor in enumerate(self.monitors[1:], start=1):  # 跳过第一个代表总显示区域的监视器
            screen_label = QtWidgets.QLabel(self)
            screen_label.setAlignment(QtCore.Qt.AlignCenter)
            screen_label.setText(f"屏幕 {i}")

            # 捕获屏幕当前内容
            img = self.sct.grab(monitor)
            img_np = np.array(img)
            img_rgb = cv2.cvtColor(img_np, cv2.COLOR_BGRA2RGB)
            img_thumb = cv2.resize(img_rgb, (200, 150))  # 缩放到缩略图尺寸

            # 转换为QImage用于展示
            height, width, channel = img_thumb.shape
            bytes_per_line = 3 * width
            q_img = QtGui.QImage(img_thumb.data, width, height, bytes_per_line, QtGui.QImage.Format_RGB888)

            # 创建缩略图标签
            thumbnail_label = QtWidgets.QLabel(self)
            thumbnail_label.setPixmap(QtGui.QPixmap.fromImage(q_img))
            thumbnail_label.setFixedSize(200, 150)
            thumbnail_label.mousePressEvent = lambda event, index=i: self.select_screen(index)  # 绑定点击选择事件

            # 添加屏幕标签和缩略图到布局
            self.screen_frame.layout().addWidget(screen_label)
            self.screen_frame.layout().addWidget(thumbnail_label)

    def select_screen(self, index):
        """进入选中屏幕的截图模式"""
        print("选中屏幕: ", self.monitors[index])
        self.snipping_tool = SnippingTool(self.monitors[index])
        self.snipping_tool.show()
        self.close()


class SnippingTool(QtWidgets.QWidget):
    def __init__(self, monitor):
        super().__init__()
        self.monitor = monitor
        
        print("选中监视器参数: ", monitor)
        
        # 先设置窗口标志,确保无框且置顶
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
        # 设置窗口位置和大小为选中屏幕的区域
        self.setGeometry(monitor["left"], monitor["top"], monitor["width"], monitor["height"])
        self.begin = QtCore.QPoint()
        self.end = QtCore.QPoint()
        self.setWindowOpacity(0.3)
        QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))

        print('开始捕获屏幕...')

    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 mousePressEvent(self, event):
        self.begin = event.pos()
        self.end = self.begin
        self.update()

    def mouseMoveEvent(self, event):
        self.end = event.pos()
        self.update()

    def mouseReleaseEvent(self, event):
        self.close()
        QtWidgets.QApplication.restoreOverrideCursor()

        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())

        # 计算绝对坐标,确保捕获选中屏幕的对应区域
        bbox = (
            self.monitor["left"] + x1,
            self.monitor["top"] + y1,
            self.monitor["left"] + x2,
            self.monitor["top"] + y2
        )
        img = ImageGrab.grab(bbox=bbox)

        # 转换为OpenCV格式
        img_cv = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB)

        # OCR提取文本
        extracted_text = pytesseract.image_to_string(img_cv)

        print("提取的文本:\n", extracted_text)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    screen_selector = ScreenSelector()
    screen_selector.show()

    sys.exit(app.exec_())

关键修改点

  1. 窗口标志调整:将FramelessWindowHint与WindowStaysOnTopHint组合,确保截图窗口无框且始终在顶层,避免被其他窗口遮挡,同时正确定位到目标屏幕。
  2. 初始化顺序修正:先设置窗口标志,再设置几何位置,避免属性冲突导致的定位错误。
  3. 光标恢复:截图完成后恢复系统默认光标,提升用户体验。
  4. 中文界面适配:将界面文本改为中文,更符合使用习惯。

内容的提问来源于stack exchange,提问作者Vaibhav Vishwakarma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 12:10:57