如何用PySide6实现透明但可响应鼠标点击的无框窗口?
PySide6 无框透明截图窗口修复(MacOS)
问题根源
MacOS会对完全透明(alpha=0)的窗口自动启用点击穿透,导致无法触发鼠标事件。必须让窗口拥有视觉上接近透明但实际存在极小不透明度的背景区域,才能让系统识别为可交互窗口。
修正方案
- 使用
#01000000作为窗口背景色(alpha通道为1,几乎完全透明但系统判定为非穿透区域)。 - 确保窗口置顶,避免被其他窗口遮挡。
- 修复工具类中方法的静态装饰器问题。
修正后完整代码
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
相关产品推荐
相关产品推荐

