time.sleep() and BackGround Windows PyQt5 - python

given that, I'm starting with the PyQt5 module, I'm still slowly understanding the logic behind it. That said, I'm having a problem that I can not find an answer to and I hope you can help me.
I have this script:
import sys, socket, time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from io import BytesIO as by
class loadGame(QWidget):
wLoadDisplay = 768
hLoadDisplay = 576
wLoadBar = 650
hLoadBar = 40
pbarCSS = """
QProgressBar
{
font-size: 20px;
font-weight: bold;
background-color: #FFF;
border: 4px solid #000;
text-align: center;
}
QProgressBar::chunk
{
background-color: #FF0000;
width: 1px;
}
"""
labelCSS = """
QLabel
{
font-size: 20px;
font-weight: bold;
background-color: #FFF;
border: 4px solid #000;
}
"""
fileResource = []
imgResource = []
vidResource = []
audResource = []
diaResource = []
txtResource = []
internetConnection = False
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.outputFile = by()
self.pbar = QProgressBar(self)
self.pbar.setGeometry((self.wLoadDisplay / 2 - self.wLoadBar / 2), (self.hLoadDisplay / 2 - self.hLoadBar * 2),
self.wLoadBar, self.hLoadBar)
self.pbar.setFormat("%v/%m")
self.pbar.setValue(0)
self.pbar.setStyleSheet(self.pbarCSS)
self.label = QLabel(self)
self.label.setGeometry((self.wLoadDisplay / 2 - self.wLoadBar / 2), (self.hLoadDisplay / 2),
self.wLoadBar, self.hLoadBar)
self.label.setAlignment(Qt.AlignCenter | Qt.AlignVCenter)
self.label.setStyleSheet(self.labelCSS)
self.setGeometry(0, 0, self.wLoadDisplay, self.hLoadDisplay)
oImage = QImage("bgloading.png")
sImage = oImage.scaled(QSize(self.wLoadDisplay, self.hLoadDisplay))
palette = QPalette()
palette.setBrush(10, QBrush(sImage))
self.setPalette(palette)
qtRectangle = self.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
self.run()
def run(self):
self.checkConnection()
if self.internetConnection:
self.checkUpdate()
else:
pass
def checkConnection(self):
self.objectChange("Check Internet Connection", 1)
try:
host = socket.gethostbyname("www.google.it")
s = socket.create_connection((host, 80), 2)
self.internetConnection = True
except:
pass
self.count()
self.reset()
def checkUpdate(self):
pass
def objectChange(self, object, n):
self.label.setText(object)
self.pbar.setMaximum(n)
def count(self):
self.pbar.setValue(self.pbar.value() + 1)
def reset(self):
time.sleep(2)
self.pbar.setMaximum(0)
self.pbar.setValue(0)
self.label.setText("...")
if __name__ == '__main__':
loadDisplay = QApplication(sys.argv)
load = loadGame()
load.show()
sys.exit(loadDisplay.exec_())
Searching on the web, I discovered that the problem is related to "time.sleep (2)", that is, the instruction blocks the window that does not appear until two seconds have passed.
The fact is that I would like to spend one or two seconds, showing the completion of the bar, before resetting and move on to the next statement contained in "def run (self)".
So, is there a way to make that pause, without using the Time module? I do not know, maybe with QTimer? I repeat, I do not know much about PyQt5 yet, so I'm not aware if QTimer can do the same thing.
If QTimer can not do it, is it possible in any other way? I would like to avoid the "threads" of PyQt5, because I read that it would be possible to do it, but I would like to avoid using it only for the Timer module.
I only add one more question, to avoid opening another one and publishing the same script.
In the script, the window background is done via "oImage = QImage (" bgloading.png ")" etc.
I noticed, however, that if the file name were found to be wrong, or the file itself is missing, the background is colored black. So, if there are any errors (wrong name or missing file) it's possible set a background, for example, white?
Because when the window is loaded with "this error", no exception is raised and the script continues.
Edit: I've edited the published script so that it contains only the PyQt5 part, so you can try it out. Obviously only the image is missing, which can be replaced with any image.
Unfortunately I had forgotten to write that the part where "self.set_display ()" was reported was to show that once the work performed by PyQt5 was terminated, it would be closed (which was still missing, because using Pycharm I would close the execution of the script from the program). The script would continue by calling the "self.set_display ()" function.
Edit2: I tried, as suggested, to replace "time.sleep (2)", but I get the same result. The problem is that if I do not put the pause, the window appears normally, but the reset happens too quickly and the user does not see the filling of the progress bar. If instead I put "time.sleep (2)" or, the suggested solution, the window appears only after the pause, that is when the reset has already occurred.

An expression equivalent to time.sleep(2) that is friendly to PyQt is as follows:
loop = QEventLoop()
QTimer.singleShot(2000, loop.quit)
loop.exec_()
The problem is caused because you are showing the widget after the pause, you must do it before calling run(), Also another error is to use QImage, for questions of widgets you must use QPixmap.
If the file does not exist then QPixmap will be null and to know if it is you should use the isNull() method:
[...]
self.setGeometry(0, 0, self.wLoadDisplay, self.hLoadDisplay)
palette = self.palette()
pixmap = QPixmap("bgloading.png")
if not pixmap.isNull():
pixmap = pixmap.scaled(QSize(self.wLoadDisplay, self.hLoadDisplay))
palette.setBrush(QPalette.Window, QBrush(pixmap))
else:
palette.setBrush(QPalette.Window, QBrush(Qt.white))
self.setPalette(palette)
qtRectangle = self.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
self.show()
self.run()
[...]
def reset(self):
loop = QEventLoop()
QTimer.singleShot(2000, loop.quit)
loop.exec_()
self.pbar.setMaximum(0)
self.pbar.setValue(0)
self.label.setText("...")

Related

How to achieve Rounded corners for QMenu in pyqt5? [duplicate]

I am trying to override the paintEvent() of QMenu to make it have rounded corners.
The context menu should look something like this.
Here is the code I have tried But nothing appears:
from PyQt5 import QtWidgets, QtGui, QtCore
import sys
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = AddContextMenu(self)
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
class AddContextMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super(AddContextMenu, self).__init__()
self.painter = QtGui.QPainter(self)
self.setMinimumSize(150, 200)
self.pen = QtGui.QPen(QtCore.Qt.red)
#self.setStyleSheet('color:white; background:gray; border-radius:4px; border:2px solid white;')
def paintEvent(self, event) -> None:
self.pen.setWidth(2)
self.painter.setPen(self.pen)
self.painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
self.painter.drawRoundedRect(10, 10, 100, 100, 4.0, 4.0)
self.update()
#self.repaint()
#super(AddContextMenu, self).paintEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Note: setting a style sheet doesn't work for me:
this is what I get when using the style sheet It isn't completely rounded.
This is the paintEvent after #musicamante suggestion(This is just for him/her to check)
def paintEvent(self, event) -> None:
painter = QtGui.QPainter(self)
#self.pen.setColor(QtCore.Qt.white)
#painter.setFont(QtGui.QFont("times", 22))
#painter.setPen(self.pen)
#painter.drawText(QtCore.QPointF(0, 0), 'Hello')
self.pen.setColor(QtCore.Qt.red)
painter.setPen(self.pen)
painter.setBrush(QtCore.Qt.gray)
painter.drawRoundedRect(self.rect(), 20.0, 20.0)
and in the init()
self.pen = QtGui.QPen(QtCore.Qt.red)
self.pen.setWidth(2)
I cannot comment on the paintEvent functionality, but it is possible to implement rounded corners using style-sheets. Some qmenu attributes have to be modified in order to disable the default rectangle in the background, which gave you the unwanted result.
Here is a modified version of your Example using style-sheets + custom flags (no frame + transparent background):
from PyQt5 import QtWidgets, QtCore
import sys
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = QtWidgets.QMenu()
# disable default frame and background
cmenu.setWindowFlags(QtCore.Qt.FramelessWindowHint)
cmenu.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# set stylesheet, add some padding to avoid overlap of selection with rounded corner
cmenu.setStyleSheet("""
QMenu{
background-color: rgb(255, 255, 255);
border-radius: 20px;
}
QMenu::item {
background-color: transparent;
padding:3px 20px;
margin:5px 10px;
}
QMenu::item:selected { background-color: gray; }
""")
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Setting the border radius in the stylesheet for a top level widget (a widget that has its own "window") is not enough.
While the solution proposed by Christian Karcher is fine, two important considerations are required:
The system must support compositing; while this is true for most modern OSes, at least on Linux there is the possibility that even an up-to-date system does not support it by choice (I disabled on my computer); if that's the case, setting the WA_TranslucentBackground attribute will not work.
The FramelessWindowHint should not be set on Linux, as it may lead to problems with the window manager, so it should be set only after ensuring that the OS requires it (Windows).
In light of that, using setMask() is the correct fix whenever compositing is not supported, and this has to happen within the resizeEvent(). Do note that masking is bitmap based, and antialiasing is not supported, so rounded borders are sometimes a bit ugly depending on the border radius.
Also, since you want custom colors, using stylesheets is mandatory, as custom painting of a QMenu is really hard to achieve.
class AddContextMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super(AddContextMenu, self).__init__()
self.setMinimumSize(150, 200)
self.radius = 4
self.setStyleSheet('''
QMenu {{
background: blue;
border: 2px solid red;
border-radius: {radius}px;
}}
QMenu::item {{
color: white;
}}
QMenu::item:selected {{
color: red;
}}
'''.format(radius=self.radius))
def resizeEvent(self, event):
path = QtGui.QPainterPath()
# the rectangle must be translated and adjusted by 1 pixel in order to
# correctly map the rounded shape
rect = QtCore.QRectF(self.rect()).adjusted(.5, .5, -1.5, -1.5)
path.addRoundedRect(rect, self.radius, self.radius)
# QRegion is bitmap based, so the returned QPolygonF (which uses float
# values must be transformed to an integer based QPolygon
region = QtGui.QRegion(path.toFillPolygon(QtGui.QTransform()).toPolygon())
self.setMask(region)
Some side notes about your paintEvent implementation, not necessary in this specific case for the above reason, but still important (some points are related to portions of code that have been commented, but the fact that you tried them makes worth mentioning those aspects):
The QPainter used for a widget must never be instanciated outside a paintEvent(): creating the instance in the __init__ as you did is a serious error and might even lead to crash. The painter can only be created when the paintEvent is received, and shall never be reused. This clearly makes useless to set it as an instance attribute (self.painter), since there's no actual reason to access it after the paint event.
If the pen width is always the same, then just set it in the constructor (self.pen = QtGui.QPen(QtCore.Qt.red, 2)), continuously setting it in the paintEvent is useless.
QPen and QBrush can directly accept Qt global colors, so there's no need to create a QBrush instance as the painter will automatically (internally and fastly) set it: self.painter.setBrush(QtCore.Qt.blue).
self.update() should never be called within a paintEvent (and not even self.repaint() should). The result in undefined and possibly dangerous.
If you do some manual painting with a QPainter and then call the super paintEvent, the result is most likely that everything painted before will be hidden; as a general rule, the base implementation should be called first, then any other custom painting should happen after (in this case it obviously won't work, as you'll be painting a filled rounded rect, making the menu items invisible).
I have implemented round corners menu using QListWidget and QWidget. You can download the code in https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/menu/demo.py.

Rounded corners for QMenu in pyqt

I am trying to override the paintEvent() of QMenu to make it have rounded corners.
The context menu should look something like this.
Here is the code I have tried But nothing appears:
from PyQt5 import QtWidgets, QtGui, QtCore
import sys
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = AddContextMenu(self)
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
class AddContextMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super(AddContextMenu, self).__init__()
self.painter = QtGui.QPainter(self)
self.setMinimumSize(150, 200)
self.pen = QtGui.QPen(QtCore.Qt.red)
#self.setStyleSheet('color:white; background:gray; border-radius:4px; border:2px solid white;')
def paintEvent(self, event) -> None:
self.pen.setWidth(2)
self.painter.setPen(self.pen)
self.painter.setBrush(QtGui.QBrush(QtCore.Qt.blue))
self.painter.drawRoundedRect(10, 10, 100, 100, 4.0, 4.0)
self.update()
#self.repaint()
#super(AddContextMenu, self).paintEvent(event)
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Note: setting a style sheet doesn't work for me:
this is what I get when using the style sheet It isn't completely rounded.
This is the paintEvent after #musicamante suggestion(This is just for him/her to check)
def paintEvent(self, event) -> None:
painter = QtGui.QPainter(self)
#self.pen.setColor(QtCore.Qt.white)
#painter.setFont(QtGui.QFont("times", 22))
#painter.setPen(self.pen)
#painter.drawText(QtCore.QPointF(0, 0), 'Hello')
self.pen.setColor(QtCore.Qt.red)
painter.setPen(self.pen)
painter.setBrush(QtCore.Qt.gray)
painter.drawRoundedRect(self.rect(), 20.0, 20.0)
and in the init()
self.pen = QtGui.QPen(QtCore.Qt.red)
self.pen.setWidth(2)
I cannot comment on the paintEvent functionality, but it is possible to implement rounded corners using style-sheets. Some qmenu attributes have to be modified in order to disable the default rectangle in the background, which gave you the unwanted result.
Here is a modified version of your Example using style-sheets + custom flags (no frame + transparent background):
from PyQt5 import QtWidgets, QtCore
import sys
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Context menu')
self.show()
def contextMenuEvent(self, event):
cmenu = QtWidgets.QMenu()
# disable default frame and background
cmenu.setWindowFlags(QtCore.Qt.FramelessWindowHint)
cmenu.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# set stylesheet, add some padding to avoid overlap of selection with rounded corner
cmenu.setStyleSheet("""
QMenu{
background-color: rgb(255, 255, 255);
border-radius: 20px;
}
QMenu::item {
background-color: transparent;
padding:3px 20px;
margin:5px 10px;
}
QMenu::item:selected { background-color: gray; }
""")
newAct = cmenu.addAction("New")
openAct = cmenu.addAction("Open")
quitAct = cmenu.addAction("Quit")
action = cmenu.exec_(self.mapToGlobal(event.pos()))
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Setting the border radius in the stylesheet for a top level widget (a widget that has its own "window") is not enough.
While the solution proposed by Christian Karcher is fine, two important considerations are required:
The system must support compositing; while this is true for most modern OSes, at least on Linux there is the possibility that even an up-to-date system does not support it by choice (I disabled on my computer); if that's the case, setting the WA_TranslucentBackground attribute will not work.
The FramelessWindowHint should not be set on Linux, as it may lead to problems with the window manager, so it should be set only after ensuring that the OS requires it (Windows).
In light of that, using setMask() is the correct fix whenever compositing is not supported, and this has to happen within the resizeEvent(). Do note that masking is bitmap based, and antialiasing is not supported, so rounded borders are sometimes a bit ugly depending on the border radius.
Also, since you want custom colors, using stylesheets is mandatory, as custom painting of a QMenu is really hard to achieve.
class AddContextMenu(QtWidgets.QMenu):
def __init__(self, *args, **kwargs):
super(AddContextMenu, self).__init__()
self.setMinimumSize(150, 200)
self.radius = 4
self.setStyleSheet('''
QMenu {{
background: blue;
border: 2px solid red;
border-radius: {radius}px;
}}
QMenu::item {{
color: white;
}}
QMenu::item:selected {{
color: red;
}}
'''.format(radius=self.radius))
def resizeEvent(self, event):
path = QtGui.QPainterPath()
# the rectangle must be translated and adjusted by 1 pixel in order to
# correctly map the rounded shape
rect = QtCore.QRectF(self.rect()).adjusted(.5, .5, -1.5, -1.5)
path.addRoundedRect(rect, self.radius, self.radius)
# QRegion is bitmap based, so the returned QPolygonF (which uses float
# values must be transformed to an integer based QPolygon
region = QtGui.QRegion(path.toFillPolygon(QtGui.QTransform()).toPolygon())
self.setMask(region)
Some side notes about your paintEvent implementation, not necessary in this specific case for the above reason, but still important (some points are related to portions of code that have been commented, but the fact that you tried them makes worth mentioning those aspects):
The QPainter used for a widget must never be instanciated outside a paintEvent(): creating the instance in the __init__ as you did is a serious error and might even lead to crash. The painter can only be created when the paintEvent is received, and shall never be reused. This clearly makes useless to set it as an instance attribute (self.painter), since there's no actual reason to access it after the paint event.
If the pen width is always the same, then just set it in the constructor (self.pen = QtGui.QPen(QtCore.Qt.red, 2)), continuously setting it in the paintEvent is useless.
QPen and QBrush can directly accept Qt global colors, so there's no need to create a QBrush instance as the painter will automatically (internally and fastly) set it: self.painter.setBrush(QtCore.Qt.blue).
self.update() should never be called within a paintEvent (and not even self.repaint() should). The result in undefined and possibly dangerous.
If you do some manual painting with a QPainter and then call the super paintEvent, the result is most likely that everything painted before will be hidden; as a general rule, the base implementation should be called first, then any other custom painting should happen after (in this case it obviously won't work, as you'll be painting a filled rounded rect, making the menu items invisible).
I have implemented round corners menu using QListWidget and QWidget. You can download the code in https://github.com/zhiyiYo/PyQt-Fluent-Widgets/blob/master/examples/menu/demo.py.

Changing Appearance of PyQt5 Window Title and Customizing the Window Title

The code:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import*
from PyQt5 import QtGui
from PyQt5.QtPrintSupport import *
class Pencere(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100,50,1080,1080)
self.setWindowIcon(QtGui.QIcon("note.png"))
self.setWindowTitle("M Content Re-Writer")
self.widget = QWidget(self)
self.widget.setObjectName("widget")
self.texteditor()
vbox2 = QVBoxLayout(self.widget)
vbox2.addWidget(self.button, alignment=Qt.AlignLeft)
vbox2.addWidget(self.editor, alignment=Qt.AlignLeft | Qt.AlignTop)
vbox = QVBoxLayout(self)
vbox.setContentsMargins(0, 0, 0, 0)
vbox.addWidget(self.widget)
def texteditor(self):
self.editor = QTextEdit()
self.editor.resize(500, 500)
self.editor.move(5,40)
self.button = QPushButton("re-write")
self.button.setFont(QFont('Segoe Script', 11))
self.button.setStyleSheet("border : 2px lemonchiffon; border-style : solid")
self.button.clicked.connect(self.function)
def function(self):
text = self.editor.toPlainText() # editor'de yazan yaziyi al
# path, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Text documents (*.txt);All files (*.*)")
if not text: # == "":
print("none")
return
# else:
path, _ = QFileDialog.getSaveFileName(
self,
"Save file",
"",
"Text documents (*.txt);All files (*.*)")
if path:
with open(path, 'w') as murti:
murti.write(text)
qss = """
#widget {
border-image: url(2.jpg) 0 0 0 0 stretch stretch;
}
QPushButton {background-color : yellow;}
QPushButton:hover:pressed {background-color: red;}
QPushButton:hover {background-color: #0ff;}
QTextEdit {
background-image: url("hand.jpeg");
min-width: 400px;
min-height: 400px;
border: 2px solid black;
color:white;
font-size:24px;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet(qss)
demo = Pencere()
demo.show()
sys.exit(app.exec_())
Hello, How can I make the background of the title of the GUI window appear transparent instead of white? In addition, I want to ask this: How can I change the color and font style of the M Content Re-Writer text in the title? I also added a screenshot to make it better. Thanks for your help.
Since you are modifying the window title so much, I believe it would be helpful to simply remove it create a custom one.
self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
This code gets rid of the window frame (which removes the title bar.)
Now, we just need to create our own title bar. This will look something like:
self.topMenu = QLabel(self)
self.topMenu.setGeometry(0, 0, 1920, 60)
self.topMenu.setStyleSheet("background-color: rgba(255,255,255, 120);")
This code creates a blank bar for everything to rest on.
From here, you just need to create a label for text, followed by three buttons for closing, minimizing, and full screening the window.

How to implement rounded corners for window that call SetWindowCompositionAttribute

Calling SetWindowCompositionAttribute can indeed add the acrylic effect of Win10 to the window, but I have a problem that I still can't solve, that is, how to realize the rounded window while adding the acrylic effect. As shown in the picture below, even if I use win32guiSetWindowRgn(int(self.winId()),win32gui.CreateRoundRectRgn(0, 0, 500, 500, 500, 500), True) in pyqt, the acrylic panel cannot be cropped. May I ask Do you have any good ideas?
The SetWindowCompositionAttribute API is not public. To apply one or more high quality effects to an image or a set of images, you can use Direct2D.
Here are some helpful links for you get started:
How to load an image into Direct2D effects using the FilePicker
Directional blur effect
Custom effects
And if you still you still want to use SetWindowCompositionAttribute API, I would suggest that you raise your voice on Feedback Hub.
You can use border-radius to make a workaround
(UpdateLayeredWindow is equivalent to WA_TranslucentBackground, SetWindowRgn don' have affect over SetWindowCompositionAttribute blur)
Full code:
Stylesheet = """
#Custom_Widget {
border-radius: 20px;
opacity: 100;
border: 8px solid #cdced4;
}
#closeButton {
min-width: 36px;
min-height: 36px;
border-radius: 10px;
}
#closeButton:hover {
color: #FFFFFF;
background: red;
}
"""
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from BlurWindow.blurWindow import GlobalBlur
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setStyleSheet(Stylesheet)
self.setWindowFlag(Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.resize(488, 388)
GlobalBlur(self.winId(),Acrylic=True,hexColor='#FFFFFF20')
self.Borders() #the real MainWindow
def Borders(self):
window = QMainWindow(self)
window.setAttribute(Qt.WA_TranslucentBackground)
window.setWindowFlag(Qt.FramelessWindowHint)
window.resize(500, 400)
self.widget = QWidget(window)
window.setCentralWidget(self.widget)
self.widget.setObjectName('Custom_Widget')
self.layout = QHBoxLayout(self.widget)
self.layout.addWidget(QPushButton(
'X', self,clicked=exit, objectName='closeButton'))
self.layout.addWidget(QLabel("<h2 style='color:blue;'>Blurry</h2>"))
window.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
UpdateLayeredWindow alpha example: https://github.com/wxWidgets/Phoenix/issues/1544

How display a QPropertyAnimation() on top of the QScrollArea()?

1. Intro
I'm working in Python 3.7 on Windows 10 and use PyQt5 for the GUI. In my application, I got a QScrollArea() with an array of buttons inside. When clicked, a button has to move outside the area. I use a QPropertyAnimation() to show the movement.
2. Minimal, Reproducible Example
I've created a small application for testing. The application shows a small QScrollArea() with a bunch of buttons inside. When you click on a button, it will move to the right:
Here is the code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MyButton(QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setFixedWidth(300)
self.setFixedHeight(30)
self.clicked.connect(self.animate)
return
def animate(self):
self.anim = QPropertyAnimation(self, b'position')
self.anim.setDuration(3000)
self.anim.setStartValue(QPointF(self.pos().x(), self.pos().y()))
self.anim.setEndValue(QPointF(self.pos().x() + 200, self.pos().y() - 20))
self.anim.start()
return
def _set_pos_(self, pos):
self.move(pos.x(), pos.y())
return
position = pyqtProperty(QPointF, fset=_set_pos_)
class CustomMainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 600, 300)
self.setWindowTitle("ANIMATION TEST")
# OUTER FRAME
# ============
self.frm = QFrame()
self.frm.setStyleSheet("""
QFrame {
background: #d3d7cf;
border: none;
}
""")
self.lyt = QHBoxLayout()
self.frm.setLayout(self.lyt)
self.setCentralWidget(self.frm)
# BUTTON FRAME
# =============
self.btn_frm = QFrame()
self.btn_frm.setStyleSheet("""
QFrame {
background: #ffffff;
border: none;
}
""")
self.btn_frm.setFixedWidth(400)
self.btn_frm.setFixedHeight(200)
self.btn_lyt = QVBoxLayout()
self.btn_lyt.setAlignment(Qt.AlignTop)
self.btn_lyt.setSpacing(5)
self.btn_frm.setLayout(self.btn_lyt)
# SCROLL AREA
# ============
self.scrollArea = QScrollArea()
self.scrollArea.setStyleSheet("""
QScrollArea {
border-style: solid;
border-width: 1px;
}
""")
self.scrollArea.setWidget(self.btn_frm)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setFixedWidth(400)
self.scrollArea.setFixedHeight(150)
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.lyt.addWidget(self.scrollArea)
# ADD BUTTONS TO BTN_LAYOUT
# ==========================
self.btn_lyt.addWidget(MyButton("Foo"))
self.btn_lyt.addWidget(MyButton("Bar"))
self.btn_lyt.addWidget(MyButton("Baz"))
self.btn_lyt.addWidget(MyButton("Qux"))
self.show()
return
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())
3. The problem
When the button moves, it stays in the QScrollArea(). I would need to get it on top of everything:
4. Solution (almost)
Thank you #magrif for pointing me in the right direction. Thanks to your suggestions, I got something working.
So I changed the animate() function into this:
def animate(self):
self.anim = QPropertyAnimation(self, b'position')
self.anim.setDuration(3000)
startpoint = self.mapToGlobal(self.pos())
endpoint = self.mapToGlobal(QPoint(self.pos().x() + 200, self.pos().y() - 20))
self.setWindowFlags(Qt.Popup)
self.show()
self.anim.setStartValue(QPointF(startpoint.x(), startpoint.y()))
self.anim.setEndValue(QPointF(endpoint.x(), endpoint.y()))
self.anim.start()
QTimer.singleShot(1000, self.hide)
return
Note that I install a single-shot timer to hide() the button after one second. That's because the Qt eventloop is blocked as long as this button behaves as a "popup" (because of self.setWindowFlags(Qt.Popup)). Anyway, the one-shot timer works good enough for me.
Unfortunately I got one issue left. When I click on the first button Foo, it starts its animation (almost) from where it was sitting initially. If I click on one of the other buttons - like Baz - it suddenly jumps down about 100 pixels and starts its animation from there.
I think this has something to do with the startpoint = self.mapToGlobal(self.pos()) function and the fact that those buttons are sitting in a QScrollArea(). But I don't know how to fix this.
5. Objective
My purpose is to build a rightmouse-click-menu like this:
When the user clicks on Grab and move, the button should disappear from the QScrollArea() and move quickly towards the mouse. When it arrives at the mouse pointer, the button should fade out and the drag-and-drop operation can start.
Note: The following question related to this topic is this one:Qt: How to perform a drag-and-drop without holding down the mouse button?
The position of a widget is relative to its parent, so you should not use startpoint = self.mapToGlobal(self.pos()), but startpoint = self.mapToGlobal(QPoint()), since for the widget the position of the topLeft is the QPoint(0, 0).
So if you want to use the #magrif solution you should change it to:
def animate(self):
startpoint = self.mapToGlobal(QPoint())
self.setWindowFlags(Qt.Popup)
self.show()
anim = QPropertyAnimation(
self,
b"pos",
self,
duration=3000,
startValue=startpoint,
endValue=startpoint + QPoint(200, -20),
finished=self.deleteLater,
)
anim.start()
But the drawback is that while the animation is running you can not interact with the window.
Another solution is to change the parent of the QFrame to the window itself:
def animate(self):
startpoint = self.window().mapFromGlobal(self.mapToGlobal(QPoint()))
self.setParent(self.window())
anim = QPropertyAnimation(
self,
b"pos",
self,
duration=3000,
startValue=startpoint,
endValue=startpoint + QPoint(200, -20),
finished=self.deleteLater,
)
anim.start()
self.show()
Note: it is not necessary to create the qproperty position since the qproperty pos already exists.
Calculate global coordinates for button using mapToGlobal().
Set flag Qt.Popup using setWindowFlags() and show()
Init start and end values relate global coordinates from (1), and start animation
ps At C++ at works :)

Categories

Resources