I'm having trouble getting this to work. I'd like to be able to place an animated GIF in a specific spot on my QSplashScreen.
The GIF has to be animated using multiprocessing and the onNextFrame method so that it will play during the initial load (otherwise it just freezes on the first frame).
I've tried inserting self.move(500,500) everywhere but nothing is working (well not working well enough). Right now, the GIF will play in the spot I want, but then it will snap back to screen center on the next frame, then back to the spot I want, etc. Inserting the move method every possible place hasn't fixed this issue.
Here's the code:
from PySide import QtCore
from PySide import QtGui
from multiprocessing import Pool
class Form(QtGui.QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.browser = QtGui.QTextBrowser()
self.setWindowTitle('Just a dialog')
self.move(500,500)
class MySplashScreen(QtGui.QSplashScreen):
def __init__(self, animation, flags):
# run event dispatching in another thread
QtGui.QSplashScreen.__init__(self, QtGui.QPixmap(), flags)
self.movie = QtGui.QMovie(animation)
self.movie.frameChanged.connect(self.onNextFrame)
#self.connect(self.movie, SIGNAL('frameChanged(int)'), SLOT('onNextFrame()'))
self.movie.start()
self.move(500, 500)
def onNextFrame(self):
pixmap = self.movie.currentPixmap()
self.setPixmap(pixmap)
self.setMask(pixmap.mask())
self.move(500, 500)
# Put your initialization code here
def longInitialization(arg):
time.sleep(args)
return 0
if __name__ == "__main__":
import sys, time
app = QtGui.QApplication(sys.argv)
# Create and display the splash screen
# splash_pix = QPixmap('a.gif')
splash = MySplashScreen('S:\_Studio\_ASSETS\Tutorials\Maya\Coding\Python\_PySide\GIF\dragonGif.gif',
QtCore.Qt.WindowStaysOnTopHint)
# splash.setMask(splash_pix.mask())
#splash.raise_()
splash.move(500, 500)
splash.show()
# this event loop is needed for dispatching of Qt events
initLoop = QtCore.QEventLoop()
pool = Pool(processes=1)
pool.apply_async(longInitialization, [2], callback=lambda exitCode: initLoop.exit(exitCode))
initLoop.exec_()
form = Form()
form.show()
splash.finish(form)
app.exec_()
Related
I'm trying to center a QLabel showing a pixmap inside a QWidget both horizontally and vertically, but for some reason, this seems impossible. I have read many similar questions, and it seems they all comes down to specifying the alignment when adding the label to the layout. Well, I'm doing that and still it's aligning to the top left corner. Can someone please help me center my darn QLabel already? :)
main.py
import sys
from PyQt5.QtWidgets import QApplication
from mainwindow import MainWindow
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
mainwindow.py
from PyQt5.QtGui import QGuiApplication, QWheelEvent
from PyQt5.QtWidgets import QMainWindow
from imagewidget import ImageWidget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(QGuiApplication.primaryScreen().availableSize() * 3 / 5)
self.image_widget = ImageWidget()
self.setCentralWidget(self.image_widget)
def wheelEvent(self, event: QWheelEvent) -> None:
angleDelta = event.angleDelta().y()
if angleDelta >= 0:
self.image_widget.zoomIn()
else:
self.image_widget.zoomOut()
imagewidget.py
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QPalette
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QPushButton
class ImageWidget(QWidget):
def __init__(self):
super().__init__()
self.scale_factor = 1.0
self.label = QLabel()
self.label.setAlignment(Qt.AlignVCenter)
self.label.setPixmap(QPixmap("image.png")) # Loads local test image
self.label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.label.setScaledContents(True)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label, Qt.AlignCenter) # Why does this not align the label to the center of the layout?
self.setLayout(self.layout)
def zoomIn(self):
self.scale_factor *= 1.1
self.resizeUsingScaleFactor()
def zoomOut(self):
self.scale_factor /= 1.1
self.resizeUsingScaleFactor()
def getImageSize(self):
return self.label.pixmap().size()
def resizeUsingScaleFactor(self):
self.label.resize(self.getImageSize() * self.scale_factor)
When you resize a widget it doesn't adjust its position, it just resize it. All widgets set their geometry based on the origin point (the top left corner), if you use resize the origin point will always remain the same.
Since you are using a layout, you should leave the positioning to the layout (which you are also preventing since you're using the Ignore size policy, which is a problem in these cases. Also note that you are using the alignment as the second argument for addWidget, but its signature is addWidget(widget, stretch=0, alignment=Qt.Alignment()), so you should use the correct keyword.
The solution is to use setFixedSize instead, which will ensure that the layout takes care of the correct alignment once it has been notified about the new fixed size (which does not happen when you use resize).
class ImageWidget(QWidget):
def __init__(self):
super().__init__()
self.scale_factor = 1.0
self.label = QLabel()
# no need for this
# self.label.setAlignment(Qt.AlignCenter)
# don't do this
# self.label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
self.label.setPixmap(QPixmap("image.png")) # Loads local test image
self.label.setScaledContents(True)
self.layout = QVBoxLayout()
self.layout.addWidget(self.label, alignment=Qt.AlignCenter)
self.setLayout(self.layout)
# ...
def resizeUsingScaleFactor(self):
self.label.setFixedSize(self.getImageSize() * self.scale_factor)
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_())
My computer doesn't have any way of letting me know if my NumLk is on or off, so I am trying to add an icon in my systray that will changed depending on the state of my NumLk. This .py will always be running when my computer is on.
So far I was able to mix 3 codes and I am able to display the icon in the systray but it doesn't get updated when the state of NumLk change. Actually if I press NumLk twice, I still get the same icon (the on one) and I get this error:
QCoreApplication::exec: The event loop is already running
File "\systray_icon_NumLk_on_off.py", line 21, in on_key_press
main(on)
File "\systray_icon_NumLk_on_off.py", line 46, in main
sys.exit(app.exec_())
SystemExit: -1
My code may not be the best way to do it, so any alternative is welcome! Here is what I came up so far:
#####get the state of NumLk key
from win32api import GetKeyState
from win32con import VK_NUMLOCK
#how to use: print(GetKeyState(VK_NUMLOCK))
#source: http://stackoverflow.com/questions/21160100/python-3-x-getting-the-state-of-caps-lock-num-lock-scroll-lock-on-windows
#####Detect if NumLk is pressed
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: http://stackoverflow.com/questions/28324372/detecting-a-numlock-capslock-scrlock-keypress-keyup-in-python
on=r'on.png'
off=r'off.png'
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.NUMLOCK:
if GetKeyState(VK_NUMLOCK):
#print(GetKeyState(VK_NUMLOCK))#should be 0 and 1 but
main(on)
else:
main(off)
#window.event
def on_draw():
window.clear()
### display icon in systray
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
#source: http://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application - add answer PyQt5
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
def main(image):
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pyglet.app.run()
The reason for QCoreApplication::exec: The event loop is already running is actually because you're trying to start app.run() twice. Qt will notice there's already an instance running and throw this exception. When instead, what you want to do is just swap the icon in the already running instance.
Your main problem here is actually the mix of libraries to solve one task if you ask me.
Rather two tasks, but using Qt5 for the graphical part is fine tho.
The way you use Pyglet is wrong from the get go.
Pyglet is intended to be a highly powerful and effective graphics library where you build a graphics engine ontop of it. For instance if you're making a game or a video-player or something.
The way you use win32api is also wrong because you're using it in a graphical window that only checks the value when a key is pressed inside that window.
Now, if you move your win32api code into a Thread (a QtThread to be precise) you can check the state no matter if you pressed your key inside your graphical window or not.
import sys
import win32api
import win32con
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread, enumerate
from time import sleep
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(QtWidgets.qApp.quit)
self.setContextMenu(menu)
class KeyCheck(QtCore.QThread):
def __init__(self, mainWindow):
QtCore.QThread.__init__(self)
self.mainWindow = mainWindow
def run(self):
main = None
for t in enumerate():
if t.name == 'MainThread':
main = t
break
while main and main.isAlive():
x = win32api.GetAsyncKeyState(win32con.VK_NUMLOCK)
## Now, GetAsyncKeyState returns three values,
## 0 == No change since last time
## -3000 / 1 == State changed
##
## Either you use the positive and negative values to figure out which state you're at.
## Or you just swap it, but if you just swap it you need to get the startup-state correct.
if x == 1:
self.mainWindow.swap()
elif x < 0:
self.mainWindow.swap()
sleep(0.25)
class GUI():
def __init__(self):
self.app = QtWidgets.QApplication(sys.argv)
self.state = True
w = QtWidgets.QWidget()
self.modes = {
True : SystemTrayIcon(QtGui.QIcon('on.png'), w),
False : SystemTrayIcon(QtGui.QIcon('off.png'), w)
}
self.refresh()
keyChecker = KeyCheck(self)
keyChecker.start()
sys.exit(self.app.exec_())
def swap(self, state=None):
if state is not None:
self.state = state
else:
if self.state:
self.state = False
else:
self.state = True
self.refresh()
def refresh(self):
for mode in self.modes:
if self.state == mode:
self.modes[mode].show()
else:
self.modes[mode].hide()
GUI()
Note that I don't do Qt programming often (every 4 years or so).
So this code is buggy at it's best. You have to press Ctrl+C + Press "Exit" in your menu for this to stop.
I honestly don't want to put more time and effort in learning how to manage threads in Qt or how to exit the application properly, it's not my area of expertis. But this will give you a crude working example of how you can swap the icon in the lower corner instead of trying to re-instanciate the main() loop that you did.
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.
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.