是否可以仅使用外部边框创建QMainWindow?

时间:2019-08-29 20:26:08

标签: python pyqt pyqt5 qmainwindow

我正在尝试重建一个屏幕记录PyQt App,而ScreenToGIF对我来说是一个很好的演示,它创建了一个仅带有边框并在“中央小部件”中记录内容的界面,如下所示:

ScreenShot of ScreenToGif Software

具有以下关键功能:

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

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

  

将QMainWidgets的背景图像更改为覆盖桌面区域没有任何意义,因为应该记录鼠标在桌面上的鼠标操作(例如,双击打开文件)。鼠标事件可以穿透应用程序(例如Qt.WindowTransparentForInput是否应用于内部内容?)

2 个答案:

答案 0 :(得分:1)

请尝试这个

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_())

答案 1 :(得分:1)

要实现的目标需要设置mask,使您可以拥有一个具有特定“形状”的小部件,而该“形状”不必是矩形。

主要困难是了解window geometries的工作方式,这很棘手。
您必须确保已计算出窗口“框架”(包括其边距和标题栏-如果有的话),然后找出内部矩形并相应地创建蒙版。请注意,在Linux上,在调用show()之后"some time"发生了这种情况;我认为您使用的是Windows,但是我已经实现了应该在Linux,MacOS和Windows上都可以正常工作的方式。如果您确定程序只能在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_())