首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否可以创建只有外部边框的QMainWindow?

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

Stack Overflow用户
提问于 2019-08-29 20:26:08
回答 2查看 1.2K关注 0票数 2

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

具有以下主要职能:

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

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

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

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-08-31 23:04:51

您想要实现的目标是设置一个https://doc.qt.io/qt-5/qwidget.html#setMask-1,允许您拥有一个具有特定“形状”的小部件,该小部件不一定是矩形。

主要的困难是理解窗口几何图形是如何工作的,这可能很棘手。

您必须确保窗口“框架”(包括其边距和标题栏-如果有的话)已经计算,然后找出内部矩形,并创建相应的掩码。请注意,在Linux上,这发生在调用了“某一段时间”之后;我认为您在show()上,但是我已经以一种对Linux、MacOS和show()都很好的方式实现了它。如果您确信您的程序将只在Windows上运行,则有关于此的评论。

最后,我只能在Linux、葡萄酒和一个虚拟化的WinXP环境上运行这个程序。它应该在任何系统上都能正常工作,但是,根据我的经验,有一个特定的“化妆品”错误:标题栏不是按照当前的Windows主题绘制的。我认为这是因为无论何时应用掩码,底层的windows系统都不会像通常那样绘制“样式”窗口框架。如果在较新的系统中也出现这种情况,可能会有一个解决办法,但这并不容易,我不能保证它能解决这个问题。

NB:请记住,这种方法永远不会允许您在“抓取矩形”内绘制任何东西(没有阴影,也没有半透明的彩色掩码);原因是您显然需要实现鼠标与“下方”小部件的交互,并对其进行绘制将需要更改覆盖掩码。

代码语言:javascript
复制
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_())
票数 2
EN

Stack Overflow用户

发布于 2019-08-29 21:46:44

请尝尝这个

代码语言:javascript
复制
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
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57717331

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档