Skip to content Skip to sidebar Skip to footer

Is It Possible To Create Qmainwindow With Only Outer Border?

I am trying to rebuild a screen record PyQt App, and the ScreenToGIF is a very good demo for me, it creates an interface which only has the border and record contents in the 'Centr

Solution 1:

What you want to achieve requires setting a mask, allowing you to have a widget that has a specific "shape" that doesn't have to be a rectangle.

The main difficulty is to understand how window geometries work, which can be tricky. You have to ensure that the window "frame" (which includes its margins and titlebar - if any) has been computed, then find out the inner rectangle and create a mask accordingly. Note that on Linux this happens "some time" after show() has been called; I think you're on Windows, but I've implemented it in a way that should work fine for both Linux, MacOS and Windows. There's a comment about that, if you're sure that your program will run on Windows only.

Finally, I've only been able to run this on Linux, Wine and a virtualized WinXP environment. It should work fine on any system, but, from my experience, there's a specific "cosmetic" bug: the title bar is not painted according to the current Windows theme. I think that this is due to the fact that whenever a mask is applied, the underlying windows system doesn't draw its "styled" window frame as it usually would. If this happens in newer systems also, there could be a workaround, but it's not easy, and I cannot guarantee that it would solve this issue.

NB: remember that this approach will never allow you to draw anything inside the "grab rectangle" (no shade, nor semi-transparent color mask); the reason for this is that you obviously need to achieve mouse interaction with what is "beneath" the widget, and painting over it would require altering the overlaying mask.

from PyQt5 import QtCore, QtGui, QtWidgets

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


classGrabber(QtWidgets.QWidget):
    dirty = Truedef__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)

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

    defresizeEvent(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 belowifnot self.dirty:
            self.updateMask()

    defpaintEvent(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 = Falseif __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    grabber = Grabber()
    grabber.show()
    sys.exit(app.exec_())

Solution 2:

please try this

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


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

Post a Comment for "Is It Possible To Create Qmainwindow With Only Outer Border?"