I'm trying to modify a program written using pyQt (specifically, Anki). I want the program to briefly flash a picture (stored as a file on my hard drive) and then continue running normally.
This code will be inserted at some arbitrary point in the program. This is an ad-hoc single-user patch to an existing program - it does not need to be fast or elegant or easily-maintained.
My problem is that I know very little about pyQt. Do I need to define an entire new "window", or can I just run some sort of "notification" function with an image inside it?
QSplashScreen will be useful for this. It is mainly used for displaying certain image/text while a program loads, but your case also looks ideal for this. You can close it with clicking on it, or additionally you can set a timer to auto-close it after some time.
Here is a simple example with a dialog that has one button. When pressed it'll show the image and close after 2 seconds:
import sys
from PyQt4 import QtGui, QtCore
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
self.b1 = QtGui.QPushButton('flash splash')
self.b1.clicked.connect(self.flashSplash)
layout.addWidget(self.b1)
def flashSplash(self):
# Be sure to keep a reference to the SplashScreen
# otherwise it'll be garbage collected
# That's why there is 'self.' in front of the name
self.splash = QtGui.QSplashScreen(QtGui.QPixmap('/path/to/image.jpg'))
# SplashScreen will be in the center of the screen by default.
# You can move it to a certain place if you want.
# self.splash.move(10,10)
self.splash.show()
# Close the SplashScreen after 2 secs (2000 ms)
QtCore.QTimer.singleShot(2000, self.splash.close)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Dialog()
main.show()
sys.exit(app.exec_())
Related
The following is a loop that I created:
import mainui
import loginui
from PyQt5 import QtWidgets
import sys
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
ui = loginui.Ui_MainWindow()
ui.setupUi()
ui.MainWindow.show()
app.exec_()
username=ui.username
app2 = QtWidgets.QApplication(sys.argv)
ui2 = mainui.Ui_MainWindow(username)
ui2.setupUi()
ui2.MainWindow.show()
app2.exec_()
if ui2.exitFlag=='repeat':#Repeat Condition
continue
else: #Exit Condition
sys.exit()
This is a loop containing a couple of PyQt5 windows, which are displayed in order. The windows run normally when they are not contained within a loop, and they also run pretty well in the first iteration of the loop.
But, when the repeat condition is satisfied, even though the loop does iterate (prints 'test' again) - the ui and ui2 windows do not get displayed again, and subsequently the program hits the exit condition and stops.
Any suggestions about why the windows do not get displayed, and how I can get them displayed would be very much appreciated.
An important premise: usually you need only one QApplication instance.
Proposed solutions
In the following examples I'm using a single QApplication instance, and switch between windows using signals.
Since you probably need to wait for the window to be closed in some way, you might prefer to use a QDialog instead of a QMainWindow, but if for some reason you need the features provided by QMainWindow (menus, dockbars, etc) this is a possible solution:
class First(QtWidgets.QMainWindow):
closed = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.close)
def closeEvent(self, event):
self.closed.emit()
class Last(QtWidgets.QMainWindow):
shouldRestart = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.restart)
closeButton.clicked.connect(self.close)
def restart(self):
self.exitFlag = True
self.close()
def showEvent(self, event):
# ensure that the flag is always false as soon as the window is shown
self.exitFlag = False
def closeEvent(self, event):
if self.exitFlag:
self.shouldRestart.emit()
app = QtWidgets.QApplication(sys.argv)
first = First()
last = Last()
first.closed.connect(last.show)
last.shouldRestart.connect(first.show)
first.show()
sys.exit(app.exec_())
Note that you can add menubars to a QWidget too, by using setMenuBar(menuBar) on their layout.
On the other hand, QDialogs are more indicated for these cases, as they provide their exec_() method which has its own event loop and blocks everything else until the dialog is closed.
class First(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.accept)
class Last(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.accept)
closeButton.clicked.connect(self.reject)
def start():
QtCore.QTimer.singleShot(0, first.exec_)
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
first = First()
last = Last()
first.finished.connect(last.exec_)
last.accepted.connect(start)
last.rejected.connect(app.quit)
start()
sys.exit(app.exec_())
Note that in this case I had to use a QTimer to launch the first dialog. This is due to the fact that in normal conditions signals wait for theirs slot to be completed before returning control to the emitter (the dialog). Since we're constantly recalling the same dialog, this leads to recursion:
First is executed
First is closed, emitting the finished signal, which causes the following:
Second is executed
at this point the finished signal has not returned yet
Second is accepted, emitting the accepted signal, which causes:
First hasn't returned its exec_() yet, but we're trying to exec it again
Qt crashes showing the error StdErr: QDialog::exec: Recursive call detected
Using a QTimer.singleShot ensures that the signal returns instantly, avoiding any recursion for exec_().
Ok, but why doesn't it work?
As said, only one Q[*]Application instance should usually exists for each process. This doesn't actually prevent to create more instances subsequently: in fact, your code works while it's in the first cycle of the loop.
The problem is related to python garbage collection and how PyQt and Qt deals with memory access to the C++ Qt objects, most importantly the application instance.
When you create the second QApplication, you're assigning it to a new variable (app2). At that point, the first one still exists, and will be finally deleted (by Qt) as soon as the process is completed with sys.exit.
When the cycle restarts, instead, you're overwriting app, which would normally cause python to garbage collect the previous object as soon as possible.
This represents a problem, as Python and Qt need to do "their stuff" to correctly delete an existing QApplication object and the python reference.
If you put the following line at the beginning, you'll see that the first time the instance is returned correctly, while the second returns None:
app = QtWidgets.QApplication(sys.argv)
print('Instance: ', QtWidgets.QApplication.instance())
There's a related question here on StackOverflow, and an important comment to its answer:
In principle, I don't see any reason why multiple instances of QApplication cannot be created, so long as no more than one exists at the same time. In fact, it may often be a requirement in unit-testing that a new application instance is created for each test. The important thing is to ensure that each instance gets deleted properly, and, perhaps more importantly, that it gets deleted at the right time.
A workaround to avoid the garbage collection is to add a persistent reference to the app:
apps = []
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
apps.append(app)
# ...
app2 = QtWidgets.QApplication(sys.argv)
apps.append(app2)
But, as said, you should not create a new QApplication instance if you don't really need that (which is almost never the case).
As already noted in the comments to the question, you should never modify the files generated with pyuic (nor try to mimic their behavior). Read more about using Designer.
The following is a loop that I created:
import mainui
import loginui
from PyQt5 import QtWidgets
import sys
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
ui = loginui.Ui_MainWindow()
ui.setupUi()
ui.MainWindow.show()
app.exec_()
username=ui.username
app2 = QtWidgets.QApplication(sys.argv)
ui2 = mainui.Ui_MainWindow(username)
ui2.setupUi()
ui2.MainWindow.show()
app2.exec_()
if ui2.exitFlag=='repeat':#Repeat Condition
continue
else: #Exit Condition
sys.exit()
This is a loop containing a couple of PyQt5 windows, which are displayed in order. The windows run normally when they are not contained within a loop, and they also run pretty well in the first iteration of the loop.
But, when the repeat condition is satisfied, even though the loop does iterate (prints 'test' again) - the ui and ui2 windows do not get displayed again, and subsequently the program hits the exit condition and stops.
Any suggestions about why the windows do not get displayed, and how I can get them displayed would be very much appreciated.
An important premise: usually you need only one QApplication instance.
Proposed solutions
In the following examples I'm using a single QApplication instance, and switch between windows using signals.
Since you probably need to wait for the window to be closed in some way, you might prefer to use a QDialog instead of a QMainWindow, but if for some reason you need the features provided by QMainWindow (menus, dockbars, etc) this is a possible solution:
class First(QtWidgets.QMainWindow):
closed = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.close)
def closeEvent(self, event):
self.closed.emit()
class Last(QtWidgets.QMainWindow):
shouldRestart = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QHBoxLayout(central)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.restart)
closeButton.clicked.connect(self.close)
def restart(self):
self.exitFlag = True
self.close()
def showEvent(self, event):
# ensure that the flag is always false as soon as the window is shown
self.exitFlag = False
def closeEvent(self, event):
if self.exitFlag:
self.shouldRestart.emit()
app = QtWidgets.QApplication(sys.argv)
first = First()
last = Last()
first.closed.connect(last.show)
last.shouldRestart.connect(first.show)
first.show()
sys.exit(app.exec_())
Note that you can add menubars to a QWidget too, by using setMenuBar(menuBar) on their layout.
On the other hand, QDialogs are more indicated for these cases, as they provide their exec_() method which has its own event loop and blocks everything else until the dialog is closed.
class First(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
button = QtWidgets.QPushButton('Continue')
layout.addWidget(button)
button.clicked.connect(self.accept)
class Last(QtWidgets.QDialog):
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
restartButton = QtWidgets.QPushButton('Restart')
layout.addWidget(restartButton)
closeButton = QtWidgets.QPushButton('Quit')
layout.addWidget(closeButton)
restartButton.clicked.connect(self.accept)
closeButton.clicked.connect(self.reject)
def start():
QtCore.QTimer.singleShot(0, first.exec_)
app = QtWidgets.QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
first = First()
last = Last()
first.finished.connect(last.exec_)
last.accepted.connect(start)
last.rejected.connect(app.quit)
start()
sys.exit(app.exec_())
Note that in this case I had to use a QTimer to launch the first dialog. This is due to the fact that in normal conditions signals wait for theirs slot to be completed before returning control to the emitter (the dialog). Since we're constantly recalling the same dialog, this leads to recursion:
First is executed
First is closed, emitting the finished signal, which causes the following:
Second is executed
at this point the finished signal has not returned yet
Second is accepted, emitting the accepted signal, which causes:
First hasn't returned its exec_() yet, but we're trying to exec it again
Qt crashes showing the error StdErr: QDialog::exec: Recursive call detected
Using a QTimer.singleShot ensures that the signal returns instantly, avoiding any recursion for exec_().
Ok, but why doesn't it work?
As said, only one Q[*]Application instance should usually exists for each process. This doesn't actually prevent to create more instances subsequently: in fact, your code works while it's in the first cycle of the loop.
The problem is related to python garbage collection and how PyQt and Qt deals with memory access to the C++ Qt objects, most importantly the application instance.
When you create the second QApplication, you're assigning it to a new variable (app2). At that point, the first one still exists, and will be finally deleted (by Qt) as soon as the process is completed with sys.exit.
When the cycle restarts, instead, you're overwriting app, which would normally cause python to garbage collect the previous object as soon as possible.
This represents a problem, as Python and Qt need to do "their stuff" to correctly delete an existing QApplication object and the python reference.
If you put the following line at the beginning, you'll see that the first time the instance is returned correctly, while the second returns None:
app = QtWidgets.QApplication(sys.argv)
print('Instance: ', QtWidgets.QApplication.instance())
There's a related question here on StackOverflow, and an important comment to its answer:
In principle, I don't see any reason why multiple instances of QApplication cannot be created, so long as no more than one exists at the same time. In fact, it may often be a requirement in unit-testing that a new application instance is created for each test. The important thing is to ensure that each instance gets deleted properly, and, perhaps more importantly, that it gets deleted at the right time.
A workaround to avoid the garbage collection is to add a persistent reference to the app:
apps = []
while True:
print('test')
app = QtWidgets.QApplication(sys.argv)
apps.append(app)
# ...
app2 = QtWidgets.QApplication(sys.argv)
apps.append(app2)
But, as said, you should not create a new QApplication instance if you don't really need that (which is almost never the case).
As already noted in the comments to the question, you should never modify the files generated with pyuic (nor try to mimic their behavior). Read more about using Designer.
I want to show tooltip while not focusing.
I made the code by referring to this PyQt Window Focus
But, it works after click window just one. Works fine, but window always blink at taskbar.
And I think this method is inefficient.
I think it's as if os are not resting while waiting for task to come, but checking every moment for task to come.
This is a simple window window, so it won't use up the cpu much, but I want to code it more efficiently.
Is there any way to improve this?
Or this method right because focusoutEvent excuted only one? ( Cpu resource 0% )
If right, how can I remove blink at taskbar?
I check reference focusPolicy-prop
import sys, os
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.initUI()
def initUI(self):
vbox = QVBoxLayout()
vbox.addStretch(2)
btn = QPushButton("Test")
btn.setToolTip("This tooltip")
vbox.addWidget(btn)
vbox.addStretch(1)
self.setLayout(vbox)
self.setGeometry(300, 300, 300, 200)
self.show()
def focusOutEvent(self, event):
self.setFocus(True)
self.activateWindow()
self.raise_()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
You are having an XY problem: trying to find a solution (usually unorthodox and overly complicated) for a problem that is originated elsewhere.
What you want to do is to show tooltips even if the window is not focused, not to restore the focus of the window; to achieve this you must not reactivate the window when it loses focus (which not only is WRONG, but is both a wrong way and reason for doing so).
You just have to set the WA_AlwaysShowToolTips widget attribute on the top level window (and remove the unnecessary focusOutEvent override, obviously).
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.initUI()
self.setAttribute(QtCore.Qt.WA_AlwaysShowToolTips, True)
Note that the attribute must be set on a widget that is a top level window, so, unless you're using a QMainWindow or you are absolutely sure that the QWidget will always be a window, it's usually better to do this instead:
self.window().setAttribute(QtCore.Qt.WA_AlwaysShowToolTips, True)
Besides that, the blinking is normal on windows, and has nothing to do with CPU usage:
activateWindow():
[...] On Windows, if you are calling this when the application is not currently the active one then it will not make it the active window. It will change the color of the taskbar entry to indicate that the window has changed in some way. This is because Microsoft does not allow an application to interrupt what the user is currently doing in another application.
i use python 2.7 + qt4.8
how to dynamically change the number of widgets in the window? I need to remove all the widgets and create new ones in the right quantity. testarovaniya made for a simple script:
import sys
from PyQt4 import QtCore, QtGui, uic
class MainForm(QtGui.QDialog):
def __init__(self):
super(MainForm, self).__init__()
uic.loadUi("testQTwindow.ui", self)
self.connect(self.addBtn, QtCore.SIGNAL("clicked()"), self.addBtnClick)
self.connect(self.delBtn, QtCore.SIGNAL("clicked()"), self.delBtnClick)
def addBtnClick(self):
self.tempBtn = QtGui.QPushButton('button')
self.gridLayout_2.addWidget(self.tempBtn)
def delBtnClick(self):
while True:
item = self.gridLayout_2.takeAt(0)
if not item:
break
self.gridLayout_2.removeWidget(item.widget())
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
sys.exit(app.exec_())
and load this UI: https://yadi.sk/d/jBOmSubYhqbjm
I have two buttons. One for adding buttons to QScrollArea with gridLayout. And the second to remove all the widgets in the QScrollArea. Adding works. I can see how there are new buttons. But when you press the cleaning button does not disappear, and new ones continue to appear over the old ones. The old button can also be pressed, which suggests that they work, and not just the ghosts that are cleaned redrawing window.
I try repaint() and update() functions - but it has no effect...
This is a simple example, but even he is not working. And I do not need to add a button in the future, and whole blocks with a bunch of elements.
How to add and remove widgets dynamically?
This part of the loop should be enough:
while True:
item = self.gridLayout_2.takeAt(0)
I suspect you are attempting to delete widgets you already removed, and so prematurely ending your loop. There may have been an error message written somewhere.
I have a QT application written in python using PySide and I stumbled across a little problem regarding the showFullScreen method of the QGLWidget (although the problem occurs with every other widget too):
The problem is, that the widget doesn't have its 'final' resolution after the program returns from showFullScreen.
The switch seems to be triggered asynchronously between 5 and 10 milliseconds later.
This is a problem for me because I have to do some layout calculations which depend on the widget's size after it is shown.
Below is a little reproducer which subclasses QGLWidget. Using this reproducer you will take notice, that resizeEvent will be called twice after showFullScreen.
I'm looking for a convinient way of knowing which resizeEvent is the 'final' one, or a way of knowing, when the widget really is in fullscreen mode. Is there maybe any signals I could connect to?
Thanks a lot for any help on this.
#!/usr/bin/python
import sys
from PySide.QtGui import QApplication
from PySide.QtCore import QTimer
from PySide.QtOpenGL import QGLWidget
class TestWidget(QGLWidget):
def __init__(self, parent=None):
super(TestWidget, self).__init__(parent)
self._timer = QTimer()
self._timer.setInterval(5)
self._timer.timeout.connect(self.showsize)
self._timer.start()
def resizeEvent(self, event):
print "Resize event:", event.size().width(), event.size().height()
def showsize(self):
w = widget.size().width()
print "Timer: ", w, widget.size().height()
if w == 1680:
self._timer.stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = TestWidget()
widget.showFullScreen()
print "After showFullScreen:", widget.size().width(), widget.size().height()
# this will always be 640 480...1680 1050 is what I'm expecting
app.exec_()
One possibility is to check that if the resize event is spontaneous or not. From limited testing here (using Qt C++ on Linux), the second resize event is spontaneous, while the first one is not.
You could do your calculations only when the event is spontaneous.
Note: I'm not sure how portable this is, it might depend on your window manager/windowing system.