I am trying to create a small widget to display information. This widget is intended to be always on top, and set hidden when the mouse hovers over it so you can click or see whatever is underneath it without disruption, and then reappear once your mouse leaves this widget. The problem I am currently facing is that once the widget is hidden, there is no pixel drawed thus no mouse actitvity is tracked anymore, which immediately triggers the leaveEvent, thus the widget keeps blinking. Here is an example:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
from PyQt5.QtCore import Qt
class TransparentWindow(QWidget):
def __init__(self):
super().__init__()
# Set window attributes
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # | Qt.WindowTransparentForInput)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setMouseTracking(True)
# Set example text
self.layout = QVBoxLayout()
self.label = QLabel(self)
self.label.setText("Hello, World!")
self.label.setStyleSheet("background-color: rgb(255, 255, 255); font-size: 50px;")
self.label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
def enterEvent(self, event):
print("Mouse entered the window")
self.label.setHidden(True)
def leaveEvent(self, event):
print("Mouse left the window")
self.label.setHidden(False)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TransparentWindow()
window.show()
sys.exit(app.exec_())
Now I have tried to add an almost transparent Qwidget item under it so I can pick up mouse events with these nearly transparent pixels:
def __init__(self):
super().__init__()
# Set window attributes
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setMouseTracking(True)
# Set example text
self.layout = QVBoxLayout()
self.label = QLabel(self)
self.label.setText("Hello, World!")
self.label.setStyleSheet("background-color: rgb(255, 255, 255); font-size: 50px;")
self.label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
# Set an almost transparent widget
self.box = QWidget(self)
self.box.setStyleSheet("background-color: rgba(255, 255, 255, 0.01)")
self.layout.addWidget(self.box)
which makes the disappear-then-reappear part work. But I can no longer click whatever is underneath it. I have tried to add Qt.WindowTransparentForInput, but it made the window transparent to enter/leave event as well. Is there any solution to make this window only transparent to click event but not enter/leave event? Or do I have to use other global mouse tracking libraries to make this work?
Platform: Windows 11 23H2
Thanks for all your help! This is how I've decided to implement it for the moment:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt, QTimer
class TransparentWindow(QWidget):
def __init__(self):
super().__init__()
# Set window attributes
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) # | Qt.WindowTransparentForInput)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setMouseTracking(True)
# Set example text
self.layout = QVBoxLayout()
self.label = QLabel(self)
self.label.setText("Hello, World!")
self.label.setStyleSheet("background-color: rgb(255, 255, 255); font-size: 50px;")
self.label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
self.hidetimer = QTimer(self)
self.hidetimer.setSingleShot(True)
self.hidetimer.timeout.connect(self.hidecheck)
self.hidecheckperiod = 300
def hidecheck(self):
if self.geometry().contains(QCursor.pos()):
self.hidetimer.start(self.hidecheckperiod)
return
print("Showing.....")
self.setHidden(False)
def enterEvent(self, event):
self.setHidden(True)
self.hidetimer.start(self.hidecheckperiod)
print("Hiding.....")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TransparentWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TransparentWindow()
window.show()
sys.exit(app.exec_())
You could poll the cursor position to see if it's contained by the current window geometry. This has the side benefit of allowing for a short delay, so that the window isn't continually shown/hidden when the cursor moves quickly over it. The delay could be configurable by the user. I think this should work on all platforms, but I've only tested it on Linux.
Here's a basic demo:
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt, QTimer
class TransparentWindow(QWidget):
def __init__(self):
super().__init__()
# Set window attributes
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # | Qt.WindowTransparentForInput)
self.setAttribute(Qt.WA_TranslucentBackground)
# Set example text
self.layout = QVBoxLayout()
self.label = QLabel(self)
self.label.setText("Hello, World!")
self.label.setStyleSheet("background-color: rgb(255, 255, 255); font-size: 50px;")
self.label.setAlignment(Qt.AlignCenter)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
self._timer = QTimer(self)
self._timer.setInterval(500)
self._timer.timeout.connect(self.pollCursor)
self._timer.start()
def pollCursor(self):
over = self.geometry().contains(QCursor.pos())
if over != self.isHidden():
self.setHidden(over)
self._timer.start()
print("Mouse is over the window:", over)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = TransparentWindow()
window.show()
sys.exit(app.exec_())