0

I create a pyqt window without any title bar and transparent. Also added blue border for my window but when on running the app I can't see any blue border for the window.

from PyQt5.QtWidgets import * 
from PyQt5.QtGui import * 
from PyQt5.QtCore import Qt
import sys
  
class Window(QMainWindow):
    def __init__(self):
        super().__init__()
  
        # this will hide the title bar
        self.setWindowFlag(Qt.FramelessWindowHint)
        self.setStyleSheet("border : 3px solid blue;")
        self.setWindowOpacity(0.01)
        # setting  the geometry of window
        self.setGeometry(100, 100, 400, 300)
        self.showFullScreen()
  
  
  
# create pyqt5 app
App = QApplication(sys.argv)
  
# create the instance of our Window
window = Window()
window.show()
# start the app
sys.exit(App.exec())

How can I show the border for my window?

Himanshu Poddar
  • 7,112
  • 10
  • 47
  • 93
  • You don't see the border because you made the windows almost completely transparent. Also, if the window is shown as full screen, the `FramelessWindowHint` is unnecessary, as a full screen window already hides the frame and the title bar. Setting the geometry is also pointless for the same reason. – musicamante Nov 17 '21 at 01:31
  • @musicamante The question has a bounty, can you answer without using the paintEvent –  Dec 22 '21 at 15:34
  • @FarhanAhmed what is the problem with using the paint event? – musicamante Dec 22 '21 at 18:50
  • As mentioned in bounty description `I want paintEvent to be called when the user drags the mouse to create rectangle on screen. So I don't want paintEvent to draw blue border everytime the self.update is called. I know I can use if to draw the border only once. But I do not want to do that` – Himanshu Poddar Dec 23 '21 at 17:36
  • @HimanshuPoddar 1. please use @username; 2. you can't "draw the border only once", as drawing doesn't work like that: `paintEvent` is called whenever the system requires it, even dozens of times in a second, you can do *nothing* about it; 3. if you want the border to be shown only in certain cases, create an internal variable, set it whenever you want to draw the border, then just check for that variable in the `paintEvent()`; 4. this question is about the border not being shown, and the current, accepted answer *solves* that, you should've opened a *new* question for your *new* requirement. – musicamante Dec 28 '21 at 01:12
  • @musicamante why don't you read first who started the bounty? And who all are asking follow up questions here, I just repeated what the person who started bounty(`Pulkit Bhatnagar is looking for a canonical answer:`) wanted. Please see my comment for the quoted statement. – Himanshu Poddar Dec 28 '21 at 05:27
  • @HimanshuPoddar Sorry, that was my mistake. But, instead of just rewriting (part of) the bounty description, you should have explicitly pointed out that you were *not* the author of the bounty (and used @username) and then suggest to re-read the bounty (which is not that immediate, as the author didn't suggest that as they should've in their comment). We answer, comment and review dozens, if not hundreds of posts a day, we can always miss something, and we cannot remember all user names and references. That wasn't completely your fault, but your comment wasn't clear enough too. – musicamante Dec 28 '21 at 05:50
  • its right that paintEvent are pretty much unpredictable and can be called whenever the application wants. But the user is also explicitly calling update method which in turns calls paintEvent. I guess the guy is trying to optimize those calls for the least. –  Dec 28 '21 at 06:13
  • @FarhanAhmed calling `update()` is just to ensure that the new state is properly reflected in the UI, the fact that it's explicitly called is unimportant: they could resize the parent window, and the effect would be the same as long as *any* paint-related event handler will trigger an update request; even changing the [virtual] desktop or toggling the window state would call that. So, the point is *not* about "not having `paintEvent()` called", but knowing how to deal with it depending on the situation. As said, if you don't want something in a `paintEvent()`, you should take care of it there. – musicamante Dec 28 '21 at 06:29

2 Answers2

2

You can use the TranslucentBackground attribute and paint the border/background in paintEvent.

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
  
        # this will hide the title bar
        self.setWindowFlag(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        
        # setting  the geometry of window
        self.setGeometry(100, 100, 400, 300)
        self.showFullScreen()

    def paintEvent(self, event):
        qp = QPainter(self)
        qp.setPen(QPen(Qt.blue, 6))
        qp.drawRect(self.rect())
        
        qp.setOpacity(0.01)
        qp.setPen(Qt.NoPen)
        qp.setBrush(self.palette().window())
        qp.drawRect(self.rect())
alec
  • 5,799
  • 1
  • 7
  • 20
1

The main problem with the original code is that the whole window gets almost transparent (having an alpha channel value of 0.01); this makes any content of the window as much as transparent, including the border, which becomes practically invisible unless the desktop background (or underlying windows) have enough contrast with the color set.

While the proposed solution works as expected and properly answer the OP question, there's another possibility that doesn't directly involve overriding paintEvent().

The issue of setting the border in the stylesheet and setting the WA_TranslucentBackground attribute is that only explicit opaque parts of the window are visible (child widgets, and any other painting implemented in paintEvent()): the background is automatically ignored, and, normally, the border along with it[1].

A possibility is to add a child widget to the main one and set the border for that widget only using a proper style sheet selector:

class Window(QMainWindow):
    def __init__(self):
        super().__init__()
  
        self.setWindowFlag(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        borderWidget = QWidget(objectName='borderWidget')
        self.setCentralWidget(borderWidget)

        bgd = self.palette().color(QPalette.Window)
        bgd.setAlphaF(.01)
        self.setStyleSheet('''
            #borderWidget {{
                border: 3px solid blue;
                background: {bgd};
            }}
        '''.format(bgd=bgd.name(bgd.HexArgb)))

        self.showFullScreen()

Note: in the code above (and that below) the background is still visible, but with a very low alpha channel value. If you don't need it, the background value could be ignored, but, for consistency it's better to explicitly declare it: background: transparent;.

That said, consider that QMainWindow has its own private layout, which could add unwanted margins on some systems (and trying to override them might not work at all).
Unless you really need any of the QMainWindow features (menu bar, status bar, tool bars and dock widgets), you should use a basic QWidget instead, which will give you direct control over the layout:

class Window(QWidget):
    def __init__(self):
        super().__init__()
  
        self.setWindowFlag(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        borderWidget = QWidget(objectName='borderWidget')
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(borderWidget)

        bgd = self.palette().color(QPalette.Window)
        bgd.setAlphaF(.01)
        self.setStyleSheet('''
            #borderWidget {{
                border: 3px solid blue;
                background: {bgd};
            }}
        '''.format(bgd=bgd.name(bgd.HexArgb)))

        self.showFullScreen()

Remember that setting the basic background/border properties of a QWidget stylesheet only works for actual QWidget instances: Qt subclasses implement it on their own way, and if you are going to create the child widget as a custom QWidget subclass the above will NOT work (see this question and this note in the documentation).

[1] This depends on the implementation of the platform and how Qt deals with it through its QPlatformPlugin. For instance, using older versions of xcompmgr I can get the border just by unsetting the WA_NoSystemBackground attribute (which, as the documentation reports, is automatically set when WA_TranslucentBackground is). While properly implementing specific-platform issues would be better, it's almost impossible as their behavior is often inconsistent across different versions, and the combinations between the window and composition managers are almost infinite. The proposed solution should work in all the situations, and with a minimal, possible, overhead.

musicamante
  • 41,230
  • 6
  • 33
  • 58