How to display an icon in the systray reflecting NumLk state - windows-7

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.

Related

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_())

PySide: Place/move animated QMovie GIF in QSplashScreen

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_()

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.

pyQT4: How to open a window from another window

I'm trying to open a window (QWidget) when clicking on a button. My problem is that the second window doesn't show up when I click on the button no matter what I've tried. The two windows are created using QTDesigner.
Here is a little snippet explaining what I'm trying to do:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from win1 import *
from win2 import *
import sys
class win1(QWidget, Ui_Win1):
def __init__(self, parent = None):
self.parent = parent
QWidget.__init__(self)
self.setupUi(parent)
self.connect(self.pushButton, SIGNAL("clicked()"), self.on_btn_clicked)
def on_btn_clicked(self):
self.child = win2(self.parent)
self.child.show()
class win2(QWidget, Ui_Win2):
def __init__(self, parent = None):
QWidget.__init__(self)
self.setupUi(parent)
def main(args):
app = QApplication(args)
win = QWidget()
a = win1(win)
win.show()
result = app.exec_()
if __name__=="__main__":
main(sys.argv)
What am I missing here ?
Thanks.
Not sure, but two random thoughts:
If you add a print statement to on_btn_clicked, do you see anything when you click on the button? This would diagnose whether it's an event triggering issue
Does it work if you change the setupUI(parent) commands to setupUI(self)?

Resources