I am working on put some drawings over some child widgets in a window. So I came up with a transparent overlay widget, making its background transparent, putting it on top of the other windows, painting directly on it. It all worked fine on Mac. However, this overlay widget just won't show up in Windows 10. Here are some code for creating this overlay transparent widget:
class OverlayWidget(QWidget):
def __init__(self, x, y, width, height):
super(OverlayWidget, self).__init__()
self.setWindowFlags( Qt.FramelessWindowHint | Qt.Widget | Qt.WindowStaysOnTopHint | Qt.TransparentMode)
self.setAttribute(Qt.WA_TranslucentBackground)
self.org_x = x
self.org_y = y
self.window_width = width
self.window_height = height
self.setGeometry(self.org_x, self.org_y, self.window_width, self.window_height)
self.setMouseTracking(True)
self.setCursor(Qt.BlankCursor)
Once I set the WA_TranslucentBackground attribute, nothing shows up, even the mouse cursor won't change, which means this widget probably wasn't even loaded. Any suggestions?
EDIT:
It seems that this is related to how this widget is added to its parent. If I create a layout and add it to the layout and then set its parent layout as the one I just created, it did show up. However, doing this contradicts with my purpose of overlaying it on top of another existing layout.
But if I just created this widget by passing the parent widget into it constructor, it doesn't work in Windows, but works in Mac.
This is quite annoying.
Related
This attached image is the screenshot of an application developed using PyQt5.
The image clearly has an invisible line running in the middle of the boxes enclosing the contents.
What code should I add in my program to draw an invisible line overlaying all other objects created earlier. I couldn't find any documentation regarding this but as the image suggests, it has somehow been implemented.
A code snippet is not needed to be provided by me since this is a question about adding/developing a feature rather than debugging or changing any existing code.
Premise: what you provided as an example doesn't seem a very good thing to do. It also seems more a glich than a "feature", and adding "invisible" lines like that might result in an annoying GUI for the user. The only scenario in which I'd use it would be a purely graphical/fancy one, for which you actually want to create a "glitch" for some reason. Also, note that the following solutions are not easy, and their usage requires you an advanced skill level and experience with Qt, because if you don't really understand what's happening, you'll most certainly encounter bugs or unexpected results that will be very difficult to fix.
Now. You can't actually "paint an invisible line", but there are certain work arounds that can get you a similar result, depending on the situation.
The main problem is that painting (at least on Qt) happens from the "bottom" of each widget, and each child widget is painted over the previous painting process, in reverse stacking order: if you have widgets that overlap, the topmost one will paint over the other. This is more clear if you have a container widget (such as a QFrame or a QGroupBox) with a background color and its children use another one: the background of the children will be painted over the parent's.
The (theoretically) most simple solution is to have a child widget that is not added to the main widget layout manager.
Two important notes:
The following will only work if applied to the topmost widget on which the "invisible line" must be applied.
If the widget on which you apply this is not the top level window, the line will probably not be really invisible.
class TestWithChildLine(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
# create a widget child of this one, but *do not add* it to the layout
self.invisibleWidget = QtWidgets.QWidget(self)
# ensure that the widget background is painted
self.invisibleWidget.setAutoFillBackground(True)
# and that it doesn't receive mouse events
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
def resizeEvent(self, event):
super().resizeEvent(event)
# create a rectangle that will be used for the "invisible" line, wide
# as the main widget but with 10 pixel height, then center it
rect = QtCore.QRect(0, 0, self.width(), 10)
rect.moveCenter(self.rect().center())
# set the geometry of the "invisible" widget to that rectangle
self.invisibleWidget.setGeometry(rect)
Unfortunately, this approach has a big issue: if the background color has an alpha component or uses a pixmap (like many styles do, and you have NO control nor access to it), the result will not be an invisible line.
Here is a screenshot taken using the "Oxygen" style (I set a 20 pixel spacing for the layout); as you can see, the Oxygen style draws a custom gradient for window backgrounds, which will result in a "not invisible line":
The only easy workaround for that is to set the background using stylesheets (changing the palette is not enough, as the style will still use its own way of painting using a gradient derived from the QPalette.Window role):
self.invisibleWidget = QtWidgets.QWidget(self)
self.invisibleWidget.setObjectName('InvisibleLine')
self.invisibleWidget.setAutoFillBackground(True)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.setStyleSheet('''
TestWithChildFull, #InvisibleLine {
background: lightGray;
}
''')
The selectors are required to avoid stylesheet propagation to child widgets; I used the '#' selector to identify the object name of the "invisible" widget.
As you can see, now we've lost the gradient, but the result works as expected:
Now. There's another, more complicated solution, but that should work with any situation, assuming that you're still using it on a top level window.
This approach still uses the child widget technique, but uses QWidget.render() to paint the current background of the top level window on a QPixmap, and then set that pixmap to the child widget (which now is a QLabel).
The trick is to use the DrawWindowBackground render flag, which allows us to paint the widget without any children. Note that in this case I used a black background, which shows a "lighter" gradient on the borders that better demonstrate the effect:
class TestWithChildLabel(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
layout.setSpacing(40)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
self.invisibleWidget = QtWidgets.QLabel(self)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
palette = self.palette()
palette.setColor(palette.Window, QtGui.QColor('black'))
self.setPalette(palette)
def resizeEvent(self, event):
super().resizeEvent(event)
pm = QtGui.QPixmap(self.size())
pm.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(pm)
maskRect = QtCore.QRect(0, 0, self.width(), 50)
maskRect.moveTop(50)
region = QtGui.QRegion(maskRect)
self.render(qp, maskRect.topLeft(), flags=self.DrawWindowBackground,
sourceRegion=region)
qp.end()
self.invisibleWidget.setPixmap(pm)
self.invisibleWidget.setGeometry(self.rect())
And here is the result:
Finally, an further alternative would be to manually apply a mask to each child widget, according to their position. But that could become really difficult (and possibly hard to manage/debug) if you have complex layouts or a high child count, since you'd need to set (or unset) the mask for all direct children each time a resize event occurs. I won't demonstrate this scenario, as I believe it's too complex and unnecessary.
I'm working on a small GUI made in PYQT5. I have a main window with a couple of buttons which open new windows. One of these windows has an embedded matplotlib plot and 2 buttons.
So, from this existing window called "PlotWindow" I want to create a new window called "DynamicPlotWindow" but add more elements (Comboboxes, buttons, methods, etc.). In other words, I want to reuse existing windows and put more components on them. I´m able to create new DynamicPlotWindow windows, but the new components added to it aren´t visible.
Based on this question: PyQt5 Making a subclass widgets the definition of both clases is as follows:
class PlotWindow(QMainWindow): #Matplotlib embeded + 2 buttons
def __init__(self, parent):
super(QMainWindow, self).__init__(parent)
self.width = 1000
self.height = 540
self.setGeometry(10, 10, self.width, self.height)
...
self.show()
...
class DynamicPlotWindow(PlotWindow):
def __init__(self, parent):
super(PlotWindow, self).__init__(parent)
self.btn = QPushButton("Test") # -> Not visible
self.btn.resize(120,30)
self.btn.move(600,800)
...
self.show()
My question is what am I doing wrong here? Is it possible to do it?
Best,
Your code has the following errors:
The botton is not a child of the window so it will not be shown, the solution is to pass it to self as parent
The window has a size of 1000x540 but you want to place the button in the position (600,800) that is clearly outside the height: 800> 540.
The solution is:
self.btn = QPushButton("Test", self)
self.btn.resize(120,30)
self.btn.move(600, 200) # change y coordinate
I'm using PyQT5 to create a GUI. Basically, what I want is a transparent window that extends the entire width and height of the screen (including the toolbar and dock on MacOS)
The code I am using to achieve this is like so:
class Gui(QWidget):
def __init__(self):
#Initialize the QApp/QWidget things
super().__init__()
#Add a default rectangle
self.rectangle = QRect(0, 0, 0, 0)
#Build the window in a method to keep the init clean
self.buildWindow()
#Build the window
def buildWindow(self):
#Make the window transparent
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.Popup)
self.setAttribute(Qt.WA_TranslucentBackground)
#Maximize the window
self.resize(1920, 1080)
#Enable mouse tracking
self.setMouseTracking(True)
#Render the window
self.show()
I open the GUI like so:
#Instantiate our app and Gui stuff.
app = QApplication(sys.argv)
gui = Gui()
#Make the cursor the "cross cursor" for effect
app.setOverrideCursor(QCursor(Qt.CrossCursor))
#Exit when our app exits
sys.exit(app.exec_())
The issue is that the GUI opens, renders for a second, and disappears immediately. If I remove Qt.Popup from the window flags, it will do exactly what I want it to do (but it will not extend past the dock or the toolbar on MacOS)
I've heard that this problem is causes (generally) by a widget being rendered and leaving the scope due to Python's garbage collection system, but I'm unsure if that is the problem here because it will actually render if I remove the Qt.Popup
Anyone who has experience with QT and could help would be awesome.. I've been trying to figure out this bug for a couple days.
EDIT: If you can't tell already, I am developing this on MacOS
I have...
class ToolWindow(QtWidgets.QMainWindow):
"""Generic window to be used as non-modal tool
Usage:
tool_win = ToolWindow()
layout = QtWidgets.QHBoxLayout()
button = QtWidgets.QPushButton('hello')
layout.addWidget(button)
tool_win.setup(layout)
button.released.connect(lambda: print('hello'))
tool_win.show()
"""
def __init__(self):
super(ToolWindow, self).__init__()
def setup(self, layout,
window_title='Untitled', object_name=None, tool=True):
"""Setup tool window"""
if tool:
self.setWindowFlags(QtCore.Qt.Tool)
self.widget = QtWidgets.QWidget()
self.widget.setLayout(layout)
self.setCentralWidget(self.widget)
self.setWindowTitle(window_title)
def closeEvent(self, event):
"""Delete object when closed"""
self.deleteLater()
However, I wish to add the typical maximize and minimize window controls to the window. I've attempted to add the following to the ToolWindow class without success (the tool window still doesn't show the maximize/minimize window controls):
self.setWindowFlags(self.windowFlags() |
QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowMinMaxButtonsHint)
Is it possible to add these controls to a tool window?
Alternatively, can I create a non-modal window but which always sits atop my parent application and which shows the maximize/minimize window controls?
Please note, I don't want this tool window staying on top of ALL windows on my system. I only want it to always stay on top of my application.
You should be able to just use the QMainWindow class without any flags. As long as the tool window is a child of the primary application window, it will stay on top of it (but not windows from other applications, like it would if you set the "Window Stays On Top" flag).
You'll need to change your __init__ to accept parent arguments
def __init__(self, parent):
super(ToolWindow, self).__init__(parent)
If you have multiple Tool Windows and you want them to stay on top in a specific order, you can call my_tool_window.raise_() to bring it to the top of the z-order.
Qt ships with a window flags example. You may want to check that out to see how the different flags affect the window display and behavior.
I am using Win7, Eclipse, python 2.7 + kivy framework for developing a openGL app.
I am trying to check some collision points(x, y) of a Widget on a mouse click. I create a layout with a specific size, but when entering the on_touch_down(mouse click) callback the layout size is strangely changed(here is the problem). Let me show you my code:
class PlayScreen(Screen):
layout = None
def __init__(self):
self.layout = StackLayout(size=(Types.SCREEN_SIZE_WIDTH, 100))
#then I create widgets inside the layout:
self.lblScore = Label(text='SCORE:', size_hint=(.1, .1))
self.lblScoreValue = Label(text='0', size_hint=(.1, .1))
.....
self.layout.add_widget(self.lblScore)
self.layout.add_widget(self.lblScoreValue)
#here the debugger shows me self.layout size to be(800, 100)
#and then I want to test if I click on the self.layout:
def on_touch_down(self, touch):
bCanAddTower = True
if self.layout.collide_point(touch.x, touch.y) == True:
print "colision with menu"
#here self.layout has size=(800, 600)(here is the problem) the entire window size, and of course I get into collision all the time.
Does anybody have any idea why the size of the self.layout changes in on_touch_down method?
The initial size is because you instantiate the widget with that size, so all the code you run immediately aftewards sees this value. However, its parent widget is a Screen which is a Layout class (specifically a RelativeLayout) that automatically resizes its children to fill itself unless you set some other options. This automatic resizing only takes place after its __init__ (but before the next frame), which is why on_touch_down, or any other method, will see the new size.
In this case, you can add size_hint=(None, None) to the self.layout instantiation. This simply tells the parent Screen not to manually control its size, so it will remain as you have set it.
In the longer term, you may want to work with some proportional size setting rather than a fixed value, as a totally fixed size won't appear the same way on different screen sizes, pixel densities etc.