我正在使用 PySide6 制作一个屏幕截图工具,我想要一个 不可见的无框窗口 来让用户拖动和捕获。但我知道的每一种方法都会让鼠标点击窗口后面而不是窗口上。
有没有办法让窗口透明并接收鼠标输入?(我用Tkinter成功制作了截图工具,但我不想用tkinter)
这是我使用 PySide6 的代码:
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)
### just look at here is enough ###
# method 1: cannot click on the window
'widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)'
# method 2: cannot click on the window
'''
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
self.setStyleSheet('background-color: #00000000;')
'''
# method 3: when opacity <= 0.5, same as method 1, it will become totally transparent and not receiving mouse input.
'self.setWindowOpacity(0.01)'
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):
# Get current flags and remove the FramelessWindowHint flag
if on:
widget.setWindowFlags(flag)
else:
all_flags = widget.windowFlags()
widget.setWindowFlags(all_flags & ~flag)
@staticmethod
def set_no_background(widget:QWidget, transparent=True, frameless = True):
"beware that when alpha is 0, the mouse click penetrates the window."
window_tools.set_flag(widget, QtCore.Qt.FramelessWindowHint, on=frameless)
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground, on=transparent)
@staticmethod
def set_frameless(widget, on = True):
widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
@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):
# Create a new QWidget (or QMainWindow)
new_window = QWidget()
new_window.setWindowTitle(title)
# Set window flags based on parameters
flags = QtCore.Qt.Window
if borderless:
new_window.setWindowFlags(QtCore.Qt.FramelessWindowHint)
if always_on_top:
new_window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# Set the size of the window
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")
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())
我找到了一个解决方案,尽管不是最优雅的。
只需使用样式表设置背景颜色,不透明度为 1/255:
window.setStyleSheet('background-color: #01000000;')