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_())
关键修改点
- 窗口标志调整:将
FramelessWindowHint与WindowStaysOnTopHint组合,确保截图窗口无框且始终在顶层,避免被其他窗口遮挡,同时正确定位到目标屏幕。 - 初始化顺序修正:先设置窗口标志,再设置几何位置,避免属性冲突导致的定位错误。
- 光标恢复:截图完成后恢复系统默认光标,提升用户体验。
- 中文界面适配:将界面文本改为中文,更符合使用习惯。
内容的提问来源于stack exchange,提问作者Vaibhav Vishwakarma
相关产品推荐
相关产品推荐

