PySide application losses palette settings after PC suspend - pyside

I'm using the Fusion style for my application. I define a dark theme by customizing the palette. With the dark theme active, it sometimes happens that the palette colors are partially lost when the PC goes to sleep or is suspended.
This is how the application normally looks like
And this is how it looks after suspend. Sometimes even the names list has grey background
To mitigate this I've added an event filter but it doesn't seem to do the job. The palette is re-applied in the set_theme method
class WinEventFilter(QtCore.QAbstractNativeEventFilter):
def __init__(self):
super().__init__()
self.main_window = None
def nativeEventFilter(self, event_type, message):
if event_type == b"windows_generic_MSG":
msg = ctypes.wintypes.MSG.from_address(message.__int__())
if msg.message == 0x15 and self.main_window is not None:
self.main_window.hide()
self.main_window.set_theme(apply=True)
self.main_window.show()
return False, 0
win_event_filter = WinEventFilter()
app.installNativeEventFilter(win_event_filter)
Any idea how to fix this?

Related

Animating QPushButton PyQt

I'm having an issue trying to animate a QPushButtons. I've a queue of buttons that once popped, I would like to animate from one color to another. I've recreated a minimum working example below:
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QApplication, QWidget
from main import comboBox
app = QApplication([])
app.setStyle('Fusion')
window = QWidget()
def change_color():
anim = QtCore.QPropertyAnimation(changeButton, b"color")
anim.setDuration(1000)
anim.setStartValue(QtGui.QColor(255, 144, 0))
anim.setEndValue(QtGui.QColor(255, 255, 255))
anim.start()
hbox = QHBoxLayout()
hbox.addStretch(1)
changeButton = QPushButton()
changeButton.setText("change Grid")
hbox.addWidget((changeButton))
window.setLayout((hbox))
window.show()
app.exec()
changeButton.clicked.connect(lambda event: change_color())
When I go into the debug mode it shows that it's reaching each of the lines, however, it there is no color change happening. Am I doing something wrong here?
You have two problems.
The first is that you're connecting the signal after the app.exec() call. That call starts the event loop, and, as such, it's blocking (nothing after that line will be processed until it returns), so you must move that before starting the event loop.
Then, as the QPropertyAnimation documentation explains:
QPropertyAnimation interpolates over Qt properties.
color is not a Qt property for QPushButton (nor any of its base classes), in fact, if you look at the debug/terminal output for your program (after moving the signal connection as said above), you'll see the following:
StdErr: QPropertyAnimation: you're trying to animate a non-existing property color of your QObject
A simple solution would be to use QVariantAnimation instead, and update the color in a function connected to the animation's valueChanged signal.
def change_color():
def updateColor(color):
# ... change the color
anim = QtCore.QVariantAnimation(changeButton)
anim.setDuration(1000)
anim.setStartValue(QtGui.QColor(255, 144, 0))
anim.setEndValue(QtGui.QColor(255, 255, 255))
anim.valueChanged.connect(updateColor)
anim.start()
# ...
changeButton.clicked.connect(change_color)
The choice becomes the way you change color: if you're not using stylesheets for the button, the more appropriate way would be to use the button palette:
def change_color(button):
def updateColor(color):
palette.setColor(role, color)
button.setPalette(palette)
palette = button.palette()
role = button.foregroundRole()
anim = QtCore.QVariantAnimation(button)
anim.setDuration(1000)
anim.setStartValue(QtGui.QColor(255, 144, 0))
anim.setEndValue(QtGui.QColor(255, 255, 255))
anim.valueChanged.connect(updateColor)
anim.start(anim.DeleteWhenStopped)
# ...
changeButton.clicked.connect(lambda: change_color(changeButton))
Note that I added the button argument (you should not use globals anyway), and also the DeleteWhenStopped flag to start() in order to avoid unnecessary stacking of unused animations when they're finished.
If, instead, you're using stylesheets, you need to override the button's stylesheet. Obviously, you have to be careful: the button shouldn't have any stylesheet set directly on it.
def change_color(button):
def updateColor(color):
button.setStyleSheet('color: {}'.format(color.name()))
# ...
Note that a more appropriate approach would be to subclass the button and implement the animation directly on it. By doing that you can use a QPropertyAnimation by using a custom pyqtProperty, but you will still need to manually set the color as above in the property setter. In this case, you can just have a single animation (possibly creating it in the __init__) and just update its start/end values.
Alternatively, you can just call update in the setter, and override the paintEvent() by using QStyle functions to draw the button primitive and label using the property value.

QComboBox popup animation glitch

When using a QComboBox in PySide2 the popup menu seems to initially start about 10 pixels or so to the left until its finished animating down at which point it pops (about) 10 pixels to the right into the correct position.
How can I fix this? Or am I able to disable the animation so the menu just opens without animating? And am I able to control the animation time for the popup?
Here are two screenshots, the top one is while the combobox dropdown is animating down and the bottom one is after the dropdown is open:
Here's the simple example code use to produce the combobox above:
from PySide2 import QtCore, QtWidgets
import sys
class MyDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.setWindowTitle('Modal Dialogs')
self.setMinimumSize(300,80)
# remove help icon (question mark) from window
self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
# create widgets, layouts and connections (signals and slots)
self.create_widgets()
self.create_layouts()
self.create_connections()
def create_widgets(self):
self.combo = QtWidgets.QComboBox()
self.combo.addItems(['one','two','three'])
def create_layouts(self):
# self must be passed to the main_layout so it is parented to the dialog instance
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.combo)
def create_connections(self):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
my_dialog = MyDialog()
my_dialog.show() # Show the UI
sys.exit(app.exec_())

Play image sequence using Qt QMainWindow

I have an image sequence rendered out. which I want to payback in a simple QMainWindow or QDialog. This is what I have sofar. It loads the images into the qlabel, but I cant see the label being updated, its just show the last loaded image, and nothing in between.
Maybe someone knows something?
from PySide import QtCore, QtGui
import shiboken
import maya.OpenMayaUI as apiUI
import time
def getMayaWindow():
"""
Get the main Maya window as a QtGui.QMainWindow instance
#return: QtGui.QMainWindow instance of the top level Maya windows
"""
ptr = apiUI.MQtUtil.mainWindow()
if ptr is not None:
return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
class Viewer(QtGui.QMainWindow):
def __init__(self, parent = getMayaWindow()):
super(Viewer, self).__init__(parent)
self.setGeometry(400, 600, 400, 300)
self.setUi()
def setUi(self):
self.label = QtGui.QLabel()
self.setCentralWidget(self.label)
def showUi(self):
self.show()
def loadImage(self, path):
self.label.clear()
image = QtGui.QImage(path)
pp = QtGui.QPixmap.fromImage(image)
self.label.setPixmap(pp.scaled(
self.label.size(),
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
x = Viewer()
x.showUi()
for i in range(1, 11):
x.loadImage("C://anim%03d.png" % i)
time.sleep(0.5)
You change pixmaps in loop and sleep (stop) all GUI thread, that's why your GUI freeze.
http://www.tutorialspoint.com/python/time_sleep.htm
It is not correct. qLabel.repaint() it is bad solution because it still blocks GUI. Of course you can use processEvents but it is bad approach too.
You should use QTimer for this purpose, use timeout() signal, create slot and change pixmaps in this slot. In this case your GUI will not be blocked because QTimer works asynchronously and images will be successfuly changed.
Same code with loop and sleep can help you only when this code will execute in another thread (multi threading) but it is not necessary because there is special class QTimer.

Pyqt docks get hidden when window minimized and restored

When I minimize the application window on Windows XP and restore it later, the dock will be hidden. This has to do with view menu which has toggles to set visibility and of course is connected by signals.
I hope this will save someone a few hours of debugging.
Here is a full functional example with both wrong and right code:
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
class Ui_QMainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(200, 200)
self.menubar = QtGui.QMenuBar(self)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 27))
self.menuMenu = QtGui.QMenu(self.menubar)
self.setMenuBar(self.menubar)
self.dock = QtGui.QDockWidget(self)
self.dock.setObjectName("dock")
self.dockContents = QtGui.QWidget()
self.dockContents.setObjectName("dockContents")
self.dock.setWidget(self.dockContents)
self.addDockWidget(QtCore.Qt.DockWidgetArea(4), self.dock)
self.action = QtGui.QAction(self)
self.action.setCheckable(True)
self.action.setChecked(True)
self.action.setObjectName("action")
self.menuMenu.addAction(self.action)
self.menubar.addAction(self.menuMenu.menuAction())
self.setWindowTitle("Example of dock remaining minimized")
self.menuMenu.setTitle("Menu")
self.dock.setWindowTitle("I'm a dock")
self.action.setText("Dock visibility")
if True:
# This is NOT working on Windows XP.
# Minimize the window and restore again, the dock is gone.
# Other than that it works.
QtCore.QObject.connect(self.action,
QtCore.SIGNAL("toggled(bool)"),
self.dock.setVisible)
QtCore.QObject.connect(self.dock,
QtCore.SIGNAL("visibilityChanged(bool)"),
self.action.setChecked)
else:
# This DOES work, but boy it looks nasty, writing useless
# per dock is not nice.
QtCore.QObject.connect(self.action,
QtCore.SIGNAL("triggered()"),
self.toggle_dock)
QtCore.QObject.connect(self.dock,
QtCore.SIGNAL("visibilityChanged(bool)"),
self.action.setChecked)
def toggle_dock(self):
self.dock.setVisible(not self.dock.isVisible())
def main():
app = QtGui.QApplication(sys.argv)
ui = Ui_QMainWindow()
ui.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
There is a much simpler way to do this, using QDock.toggleViewAction. This function returns a ready-made action that handles the checked state automatically.
So your code would become simply:
self.action = self.dock.toggleViewAction()
self.action.setObjectName("action")
self.menuMenu.addAction(self.action)
self.menubar.addAction(self.menuMenu.menuAction())
self.setWindowTitle("Example of dock remaining minimized")
self.menuMenu.setTitle("Menu")
self.dock.setWindowTitle("I'm a dock")
self.action.setText("Dock visibility")
and you can then get rid of all the signal handling.

How to handle sprite animation in Kivy

I am coding a game using Kivy. I have a Screen class where I put my animation code. It's not a usual game, it's more like several screens, each with its own animation, with button commands for going back and forth to different screens.
It works ok, but when I make more classes like this and put it all in a ScreenManager, the animation is disrupted with random white screens.
class Pas(Screen):
def __init__(self, **kwargs):
super(Pas, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1 / 60.0)
self.ani_speed_init = 15
self.ani_speed = self.ani_speed_init
self.ani = glob.glob("img/pas_ani*.png")
self.ani.sort()
self.ani_pos = 0
self.ani_max = len(self.ani)-1
self.img = self.ani[0]
self.update(1)
back = Button(
background_normal=('img/back-icon.png'),
background_down=('img/back-icon.png'),
pos=(380, 420))
self.add_widget(back)
def callback(instance):
sm.current = 'game'
back.bind(on_press=callback)
def update(self, dt):
self.ani_speed -= 1
if self.ani_speed == 0:
self.img = self.ani[self.ani_pos]
self.ani_speed = self.ani_speed_init
if self.ani_pos == self.ani_max:
self.ani_pos = 0
else:
self.ani_pos += 1
with self.canvas:
image = Image(source=self.img, pos=(0, 0), size=(320, 480))
What am I doing wrong? I am also accepting ideas for a different way of doing this.
If you want to use Screen and ScreenManager for your screens, it would be better to use the transition system they define and use, so, to define your own Transitions, and apply them. If you want more control, i would advise getting ride of Screen and ScreenManager, and just using Widgets, to control the whole drawing/positioning process.
Also, Clock.schedule_interval(self.update, 0) is equivalent to the call you are making, the animation will be called each frame, and you can use dt to manage the animation progress.
Also, kivy can manage gifs, as well as zip archives of images to directly do animations (useful to have animated pngs), you can let kivy manage the whole animation process this way.

Resources