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

如何用PySide6实现透明但可响应鼠标点击的无框窗口?

PySide6 无框透明截图窗口修复(MacOS)

问题根源

MacOS会对完全透明(alpha=0)的窗口自动启用点击穿透,导致无法触发鼠标事件。必须让窗口拥有视觉上接近透明但实际存在极小不透明度的背景区域,才能让系统识别为可交互窗口。

修正方案

  1. 使用#01000000作为窗口背景色(alpha通道为1,几乎完全透明但系统判定为非穿透区域)。
  2. 确保窗口置顶,避免被其他窗口遮挡。
  3. 修复工具类中方法的静态装饰器问题。

修正后完整代码

from PySide6 import QtWidgets, QtCore, QtGui
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtGui import QScreen, QPixmap
import sys

using_debug_mode = None

class DraggingPanel(QWidget):
    def __init__(self, callback=None, cancel_callback=None):
        super().__init__()
        self.callback = callback
        self.cancel_callback = cancel_callback
        self.start_x = None
        self.start_y = None
        self.rect:QtCore.QRect = None
        
        window_tools.set_frameless(self)
        # 核心修改:设置几乎透明但可点击的背景
        window_tools.set_bg_to_almost_transparent(self)
        # 确保窗口置顶,避免被其他窗口遮挡
        window_tools.set_always_on_top(self)

        height = screen.height()
        width = screen.width()
        self.setGeometry(0, 0, width, height)
        self.setMouseTracking(True)

    def paintEvent(self, event):
        if self.rect:
            painter = QtGui.QPainter(self)
            painter.setPen(QtGui.QPen(QtGui.Qt.white, 2))
            painter.drawRect(self.rect)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.start_x = event.globalPosition().x()
            self.start_y = event.globalPosition().y()
            self.rect = QtCore.QRect(self.start_x, self.start_y, 0, 0)

    def mouseMoveEvent(self, event):
        if event.buttons() & QtCore.Qt.LeftButton:
            self.rect.setBottomRight(event.globalPosition().toPoint())
            self.update()

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.capture_screen()

    def capture_screen(self):
        xs = [self.rect.x(), self.rect.x() + self.rect.width()]
        ys = [self.rect.y(), self.rect.y() + self.rect.height()]
        xs.sort()
        ys.sort()
        x1, x2 = xs
        y1, y2 = ys
        w = x2-x1
        h = y2-y1
        self.hide()

        if self.rect and w > 0 and h > 0:
            def do_capture():
                screen_cap = QScreen.grabWindow(QApplication.primaryScreen(), 0, 0, 
                                                screen.width(),
                                                screen.height())
                to_img_scale_x = lambda val: round(val * screen_cap.width()/screen.width())
                to_img_scale_y = lambda val: round(val * screen_cap.height()/screen.height())
                screen_cap = screen_cap.copy(to_img_scale_x(x1),
                                                to_img_scale_y(y1),
                                                to_img_scale_x(w),
                                                to_img_scale_y(h)) if True else screen_cap
                if using_debug_mode: print("Screenshot taken!")
                QtCore.QTimer.singleShot(1, lambda: self.callback(screen_cap) and self.deleteLater())
            QtCore.QTimer.singleShot(1, do_capture)
        else:
            if using_debug_mode: print("Screenshot canceled!")
            QtCore.QTimer.singleShot(1, lambda: self.cancel_callback() and self.deleteLater())

### Tools ###

class window_tools:
    @staticmethod
    def set_flag(widget:QWidget, flag: QtCore.Qt, on=True):
        widget.setWindowFlag(flag, on=on)
    
    @staticmethod
    def set_no_entity_mode(widget:QWidget, transparent_no_enitity=True, frameless = True):
        "the mouse can click behind the window."
        window_tools.set_flag(widget, QtCore.Qt.FramelessWindowHint, on=frameless)
        widget.setAttribute(QtCore.Qt.WA_TranslucentBackground, on=transparent_no_enitity)
    
    # 修复:添加静态方法装饰器
    @staticmethod
    def set_bg_to_almost_transparent(widget):
        "Opacity: 1/255, Clickable"
        widget.setStyleSheet('background-color: #01000000;')
    
    @staticmethod
    def set_frameless(widget, on = True):
        window_tools.set_flag(widget, QtCore.Qt.FramelessWindowHint, on=on)
    
    @staticmethod
    def set_always_on_top(widget, on = True):
        window_tools.set_flag(widget, QtCore.Qt.WindowStaysOnTopHint, on=on)
    
    @staticmethod
    def create_window(title="", borderless=False, always_on_top=False, width=300, height=300):
        new_window = QWidget()
        new_window.setWindowTitle(title)
        new_window.setWindowFlag(QtCore.Qt.FramelessWindowHint, borderless)
        new_window.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint, always_on_top)
        new_window.resize(width, height)
        return new_window

class screen:
    @staticmethod
    def size():
        return app_using.primaryScreen().size()
    
    @staticmethod
    def width():
        return screen.size().width()
    
    @staticmethod
    def height():
        return screen.size().height()

### main function ###

app_using: QApplication = None
def launch_screenshot_panel(Qapp: QApplication, on_screenshot=None, on_cancel=None, debug_logging=False):
    "Please STORE the returned widget to prevent garbage collection"
    global using_debug_mode, app_using
    using_debug_mode = debug_logging
    app_using = Qapp
    panel = DraggingPanel(on_screenshot, on_cancel)
    panel.show()
    return panel

if __name__ == "__main__":
    if not QtWidgets.QApplication.instance():
        app = QtWidgets.QApplication(sys.argv)
    else:
        app = QtWidgets.QApplication.instance()
    
    def on_screenshot(img: QPixmap):
        img.save("a.png")
        quit()
    
    def on_screenshot_cancelled():
        print("cancelled callback!")
    
    panel = launch_screenshot_panel(app, on_screenshot=on_screenshot, on_cancel=on_screenshot_cancelled, debug_logging=True)
    sys.exit(app_using.exec())

关键修改说明

  • 背景色设置:用#01000000替代完全透明的背景,alpha值为1,视觉上几乎不可见,但MacOS会识别为可交互区域,不会触发点击穿透。
  • 窗口置顶:添加window_tools.set_always_on_top(self),确保截图窗口始终在所有窗口之上,避免被遮挡导致无法操作。
  • 工具类修复:给set_bg_to_almost_transparent添加@staticmethod装饰器,确保方法能被正确调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 09:27:03