是否可以只创建外部边框的QMainWindow?

2024-09-30 01:22:17 发布

您现在位置:Python中文网/ 问答频道 /正文

我正试图重建一个屏幕记录PyQt应用程序,ScreenToGIF对我来说是一个非常好的演示,它创建了一个界面,它只在“中央小部件”中有边框和记录内容,如下所示:

ScreenShot of ScreenToGif Software

主要功能包括:

  1. 边界存在,可以通过鼠标拖动和调整大小
  2. 内部内容是透明的
  3. 鼠标点击可以穿透应用程序,并与它下面的其他应用程序进行交互。在

但是,它是用C实现的(link:https://github.com/NickeManarin/ScreenToGif),我想知道是否有可能在不学习C的专业知识的情况下制作一个类似的PyQt应用程序?在

Changing the background image of QMainWidgets to the desktop area been overlayed doesn't make sense, because mouse operation on desktop (such as double click to open files) should be recorded. Mouse event can penetrate the app (like Qt.WindowTransparentForInput applied for inner contents?)


Tags: theto应用程序内容界面屏幕部件记录
2条回答

你想要实现的是设置一个mask,这样你就可以拥有一个不必是矩形的特定“形状”的小部件。在

主要的困难是理解window geometries是如何工作的,这可能很棘手。
你必须确保窗口“框架”(包括它的边距和标题栏-如果有的话)已经被计算出来,然后找出内部矩形并相应地创建一个遮罩。{{1}我认为这两种操作系统都是在Linux下实现的。如果你确定你的程序只能在Windows上运行,那就有评论了。在

最后,我只能在Linux、Wine和虚拟化的WinXP环境中运行它。它在任何系统上都能正常工作,但是,根据我的经验,有一个特殊的“装饰性”缺陷:标题栏不是根据当前的Windows主题绘制的。我认为这是因为每当应用掩码时,底层windows系统不会像通常那样绘制“样式化”的窗口框。如果这种情况也发生在较新的系统中,那么可能是一个解决办法,但这并不容易,我不能保证它能解决这个问题。在

NB:请记住,这种方法不会允许永远在“抓取矩形”内绘制任何内容(没有阴影,也没有半透明的色板);原因是,您显然需要实现与窗口小部件“下方”的鼠标交互,而在其上绘制则需要更改覆盖遮罩。在

from PyQt5 import QtCore, QtGui, QtWidgets

class VLine(QtWidgets.QFrame):
    # a simple VLine, like the one you get from designer
    def __init__(self):
        super(VLine, self).__init__()
        self.setFrameShape(self.VLine|self.Sunken)


class Grabber(QtWidgets.QWidget):
    dirty = True
    def __init__(self):
        super(Grabber, self).__init__()
        self.setWindowTitle('Screen grabber')
        # ensure that the widget always stays on top, no matter what
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)
        # limit widget AND layout margins
        layout.setContentsMargins(0, 0, 0, 0)
        self.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        # create a "placeholder" widget for the screen grab geometry
        self.grabWidget = QtWidgets.QWidget()
        self.grabWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        layout.addWidget(self.grabWidget)

        # let's add a configuration panel
        self.panel = QtWidgets.QWidget()
        layout.addWidget(self.panel)

        panelLayout = QtWidgets.QHBoxLayout()
        self.panel.setLayout(panelLayout)
        panelLayout.setContentsMargins(0, 0, 0, 0)
        self.setContentsMargins(1, 1, 1, 1)

        self.configButton = QtWidgets.QPushButton(self.style().standardIcon(QtWidgets.QStyle.SP_ComputerIcon), '')
        self.configButton.setFlat(True)
        panelLayout.addWidget(self.configButton)

        panelLayout.addWidget(VLine())

        self.fpsSpinBox = QtWidgets.QSpinBox()
        panelLayout.addWidget(self.fpsSpinBox)
        self.fpsSpinBox.setRange(1, 50)
        self.fpsSpinBox.setValue(15)
        panelLayout.addWidget(QtWidgets.QLabel('fps'))

        panelLayout.addWidget(VLine())

        self.widthLabel = QtWidgets.QLabel()
        panelLayout.addWidget(self.widthLabel)
        self.widthLabel.setFrameShape(QtWidgets.QLabel.StyledPanel|QtWidgets.QLabel.Sunken)

        panelLayout.addWidget(QtWidgets.QLabel('x'))

        self.heightLabel = QtWidgets.QLabel()
        panelLayout.addWidget(self.heightLabel)
        self.heightLabel.setFrameShape(QtWidgets.QLabel.StyledPanel|QtWidgets.QLabel.Sunken)

        panelLayout.addWidget(QtWidgets.QLabel('px'))

        panelLayout.addWidget(VLine())

        self.recButton = QtWidgets.QPushButton('rec')
        panelLayout.addWidget(self.recButton)

        self.playButton = QtWidgets.QPushButton('play')
        panelLayout.addWidget(self.playButton)

        panelLayout.addStretch(1000)

    def updateMask(self):
        # get the *whole* window geometry, including its titlebar and borders
        frameRect = self.frameGeometry()

        # get the grabWidget geometry and remap it to global coordinates
        grabGeometry = self.grabWidget.geometry()
        grabGeometry.moveTopLeft(self.grabWidget.mapToGlobal(QtCore.QPoint(0, 0)))

        # get the actual margins between the grabWidget and the window margins
        left = frameRect.left() - grabGeometry.left()
        top = frameRect.top() - grabGeometry.top()
        right = frameRect.right() - grabGeometry.right()
        bottom = frameRect.bottom() - grabGeometry.bottom()

        # reset the geometries to get "0-point" rectangles for the mask
        frameRect.moveTopLeft(QtCore.QPoint(0, 0))
        grabGeometry.moveTopLeft(QtCore.QPoint(0, 0))

        # create the base mask region, adjusted to the margins between the
        # grabWidget and the window as computed above
        region = QtGui.QRegion(frameRect.adjusted(left, top, right, bottom))
        # "subtract" the grabWidget rectangle to get a mask that only contains
        # the window titlebar, margins and panel
        region -= QtGui.QRegion(grabGeometry)
        self.setMask(region)

        # update the grab size according to grabWidget geometry
        self.widthLabel.setText(str(self.grabWidget.width()))
        self.heightLabel.setText(str(self.grabWidget.height()))

    def resizeEvent(self, event):
        super(Grabber, self).resizeEvent(event)
        # the first resizeEvent is called *before* any first-time showEvent and
        # paintEvent, there's no need to update the mask until then; see below
        if not self.dirty:
            self.updateMask()

    def paintEvent(self, event):
        super(Grabber, self).paintEvent(event)
        # on Linux the frameGeometry is actually updated "sometime" after show()
        # is called; on Windows and MacOS it *should* happen as soon as the first
        # non-spontaneous showEvent is called (programmatically called: showEvent
        # is also called whenever a window is restored after it has been
        # minimized); we can assume that all that has already happened as soon as
        # the first paintEvent is called; before then the window is flagged as
        # "dirty", meaning that there's no need to update its mask yet.
        # Once paintEvent has been called the first time, the geometries should
        # have been already updated, we can mark the geometries "clean" and then
        # actually apply the mask.
        if self.dirty:
            self.updateMask()
            self.dirty = False


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    grabber = Grabber()
    grabber.show()
    sys.exit(app.exec_())

请试试这个

from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import Qt
import sys


class MainWindowExample(QMainWindow):
    def __init__(self, parent=None):
        try:
            QMainWindow.__init__(self, parent)
            self.setWindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint)
            self.setStyleSheet("border: 1px solid rgba(0, 0, 0, 0.15);")
        except Exception as e:
            print(e)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_widow = MainWindowExample()
    main_widow.show()
    sys.exit(app.exec_())

相关问题 更多 >

    热门问题