I'm trying to create a screenshot utility for linux using python. Right now I'm stuck at trying to implement a function that lets the user select a region from a live screen and screenshot it. After much pondering, I reached the conclusion to create a full-screen window on each screen to get the mouse's click and drag coordinates.
How can I have my program create a full-screen window (without the toolbar icon) for each screen connected to the system?
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
class InvisWindow(qtw.QWidget):
def __init__(self, screens):
super().__init__()
self.setWindowFlags(qtc.Qt.Tool | qtc.Qt.FramelessWindowHint)
self.show()
self.showFullScreen()
self.windowHandle().setScreen(screens[0])
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = InvisWindow(app.screens())
sys.exit(app.exec_())
I found this code searching for a way to do it, but no matter which screen I pass to setScreen() it always appears on a single screen, i.e. changing the argument doesn't change which screen it appears in.
There are two problems:
as the documentation explains:
If the screen is part of a virtual desktop of multiple screens, the window will not move automatically to newScreen.
on Linux, there's some amount of time and system events between the call to show and when the window is actually mapped on the screen the first time (see Initial Geometry), which can be overridden by the window manager if no geometry is explicitly set;
That said, there should be no need to use the QWindow for this, as using move is usually be enough, you only must do it before any call to show() or related functions:
class InvisWindow(qtw.QWidget):
def __init__(self, screens):
super().__init__()
self.setWindowFlags(qtc.Qt.Tool | qtc.Qt.FramelessWindowHint)
self.move(screens[0].geometry().topLeft())
self.showFullScreen()
Note that there's no use in calling show() before showFullScreen(), since it implicitly calls setVisible(True).
If what you want is to show a single window on top of everything, then you could try the following:
class InvisWindow(qtw.QWidget):
mapped = False
def __init__(self, screens):
super().__init__()
self.setWindowFlags(
qtc.Qt.WindowStaysOnTopHint |
qtc.Qt.Tool |
qtc.Qt.FramelessWindowHint
)
self.show()
def moveEvent(self, event):
if not self.mapped:
geometry = qtc.QRect()
for screen in qtw.QApplication.screens():
geometry |= screen.geometry()
if self.pos() != geometry.topLeft():
self.setGeometry(geometry)
self.mapped = True
Please consider the last lines, as they are very important, because trying to do geometry changes in a geometry change event (moveEvent and resizeEvent) can cause recursion.
I'm starting experimenting with Maya python, and I'm trying to do some UI.
I came across to a really strange problem, I can't get a button to stay in the center of the windows.
I've tried different things but nothing seems to work, here is the code:
import maya.cmds as cmds
cmds.window( width=200 )
WS = mc.workspaceControl("dockName", retain = False, floating = True,mw=80)
submit_widget = cmds.rowLayout(numberOfColumns=1, p=WS)
cmds.button( label='Submit Job',width=130,align='center', p=submit_widget)
cmds.showWindow()
this is a simple version but still, I can't get it to work.
can someone help me?
I honestly don't know the answer as anytime I have to dig into Maya's native UI stuff it makes me question my own life.
So I know it's not exactly what you're asking for, but I'll opt with this: Use PySide instead. At first glance it might make you go "woah, that's way too hard", but it's also a million times better (and actually easier). It's much more powerful, flexible, has great documentation, and also used outside of Maya (so actually useful to learn). Maya's own interface uses the same framework, so you can even edit it with PySide once you're more comfortable with it.
Here's a bare-bones example to create a centered button in a window:
# Import PySide libraries.
from PySide2 import QtCore
from PySide2 import QtWidgets
class MyWindow(QtWidgets.QWidget): # Create a class for our window, which inherits from `QWidget`
def __init__(self, parent=None): # The class's constructor.
super(MyWindow, self).__init__(parent) # Initialize its `QWidget` constructor method.
self.my_button = QtWidgets.QPushButton("My button!") # Create a button!
self.my_layout = QtWidgets.QVBoxLayout() # Create a vertical layout!
self.my_layout.setAlignment(QtCore.Qt.AlignCenter) # Center the horizontal alignment.
self.my_layout.addWidget(self.my_button) # Add the button to the layout.
self.setLayout(self.my_layout) # Make the window use this layout.
self.resize(300, 300) # Resize the window so it's not tiny.
my_window_instance = MyWindow() # Create an instance of our window class.
my_window_instance.show() # Show it!
Not too bad, right?
I am basically building a GUI with pyqt5 supposed to incorporate two videos. To do this, I use QMediaPlayer in combination with QVideoWidget, one for each class. The point is: while the first video plays as expected, the second one refuses to play. It uses exactly the same framework as the first one (one pushbuton for play/pause and one slidebar), and the same structure of code, but the screen remains desperately black when trying to play.
Worse, if I comment the code for the first video, the second now plays normally. Could that mean there is some conflict between the two QMedialPlayers? I can't make sense of that.
Any help would be greatly appreciated.
Here is my code (the GUI looks weird because I have removed most of it for clarity):
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QPushButton, QLineEdit, QFrame, QHBoxLayout, QCheckBox, QRadioButton, QButtonGroup, QStyle, QSlider, QStackedLayout
import sys
from tkinter import Tk
from PyQt5.QtCore import pyqtSlot, QRect, Qt, QRunnable, QThreadPool, QThread, QObject, QUrl, QSize
import time
from PyQt5 import QtMultimedia
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtGui import QFont
from PyQt5.QtGui import QImage, QPalette, QBrush, QIcon, QPixmap
class DNN_Viewer(QWidget):
def __init__(self, n_filters=2):
super(DNN_Viewer, self).__init__()
# initialise GUI
self.init_gui()
# initialise videos to display images
self.mp1.play()
self.mp1.pause()
self.mp2.play()
self.mp2.pause()
def init_gui(self):
# main window
root = Tk()
screen_width = root.winfo_screenwidth() # screen width
screen_height = root.winfo_screenheight() # screen heigth
self.width = 1900 # interface width
self.heigth = 1000 # interface height
self.left = (screen_width - self.width) / 2 # left-center interface
self.top = (screen_height - self.heigth) / 2 # top-center interface
self.setFixedSize(self.width, self.heigth)
self.move(self.left, self.top)
self.setStyleSheet("background: white"); # interface background color
# bottom left frame
self.fm2 = QFrame(self) # creation
self.fm2.setGeometry(30, 550, 850, 430) # left, top, width, height
self.fm2.setFrameShape(QFrame.Panel); # use panel style for frame
self.fm2.setLineWidth(1) # frame line width
# video for weights and gradients
self.vw1 = QVideoWidget(self) # declare video widget
self.vw1.move(50,555) # left, top
self.vw1.resize(542,380) # width, height
self.vw1.setStyleSheet("background-color:black;"); # set black background
# wrapper for the video
self.mp1 = QMediaPlayer(self) # declare QMediaPlayer
self.mp1.setVideoOutput(self.vw1) # use video widget vw1 as output
fileName = "path_to_video_1" # local path to video
self.mp1.setMedia(QMediaContent(QUrl.fromLocalFile(fileName))) # path to video
self.mp1.stateChanged.connect(self.cb_mp1_1) # callback on change state (play, pause, stop)
self.mp1.positionChanged.connect(self.cb_mp1_2) # callback to move slider cursor
self.mp1.durationChanged.connect(self.cb_mp1_3) # callback to update slider range
# play button for video
self.pb2 = QPushButton(self) # creation
self.pb2.move(50,940) # left, top
self.pb2.resize(40,30) # width, height
self.pb2.setIconSize(QSize(18,18)) # button text
self.pb2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) # standard triangle icon for play
self.pb2.clicked.connect(self.cb_pb2) # callback on click (play/pause)
# position slider for video
self.sld1 = QSlider(Qt.Horizontal,self) # creation
self.sld1.setGeometry( 110, 940, 482, 30) # left, top, width, height
self.sld1.sliderMoved.connect(self.cb_sld1) # callback on move
# title label
self.lb23 = QLabel(self) # creation
self.lb23.setText("Loss and accuracy") # label text
self.lb23.move(980,10) # left, top
self.lb23.setStyleSheet("font-size: 30px; font-family: \
FreeSans; font-weight: bold") # set font and size
# top right frame
self.fm3 = QFrame(self) # creation
self.fm3.setGeometry(980, 50, 850, 430) # left, top, width, height
self.fm3.setFrameShape(QFrame.Panel); # use panel style for frame
self.fm3.setLineWidth(1) # frame line width
# video for loss and accuracy
self.vw2 = QVideoWidget(self) # declare video widget
self.vw2.move(1000,55) # left, top
self.vw2.resize(542,380) # width, height
self.vw2.setStyleSheet("background-color:black;"); # set black background
# wrapper for the video
self.mp2 = QMediaPlayer(self) # declare QMediaPlayer
self.mp2.setVideoOutput(self.vw2) # use video widget vw1 as output
fileName2 = "path_to_video_2" # local path to video
self.mp2.setMedia(QMediaContent(QUrl.fromLocalFile(fileName2))) # path to video
self.mp2.stateChanged.connect(self.cb_mp2_1) # callback on change state (play, pause, stop)
self.mp2.positionChanged.connect(self.cb_mp2_2) # callback to move slider cursor
self.mp2.durationChanged.connect(self.cb_mp2_3) # callback to update slider range
# play button for video
self.pb3 = QPushButton(self) # creation
self.pb3.move(1000,440) # left, top
self.pb3.resize(40,30) # width, height
self.pb3.setIconSize(QSize(18,18)) # button text
self.pb3.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) # standard triangle icon for play
self.pb3.clicked.connect(self.cb_pb3) # callback on click (play/pause)
# position slider for video
self.sld2 = QSlider(Qt.Horizontal,self) # creation
self.sld2.setGeometry(1060, 440, 482, 30) # left, top, width, height
self.sld2.sliderMoved.connect(self.cb_sld2) # callback on move
def cb_mp1_1(self, state):
if self.mp1.state() == QMediaPlayer.PlayingState: # if playing, switch button icon to pause
self.pb2.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
elif self.mp1.state() == QMediaPlayer.StoppedState: # if stopped, rewind to first image
self.mp1.play()
self.mp1.pause()
else:
self.pb2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) # if paused, switch button icon to play
def cb_mp1_2(self, position):
self.sld1.setValue(position) # set slider position to video position
def cb_mp1_3(self, duration):
self.sld1.setRange(0, duration) # set slider range to video position
def cb_pb2(self):
if self.mp1.state() == QMediaPlayer.PlayingState: # set to pause if playing
self.mp1.pause()
else:
self.mp1.play() # set to play if in pause
def cb_sld1(self, position):
self.mp1.setPosition(position) # set video position to slider position
def cb_mp2_1(self, state):
if self.mp2.state() == QMediaPlayer.PlayingState: # if playing, switch button icon to pause
self.pb3.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
elif self.mp2.state() == QMediaPlayer.StoppedState: # if stopped, rewind to first image
self.mp2.play()
self.mp2.pause()
else:
self.pb3.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) # if paused, switch button icon to play
def cb_mp2_2(self, position):
self.sld2.setValue(position) # set slider position to video position
def cb_mp2_3(self, duration):
self.sld2.setRange(0, duration) # set slider range to video position
def cb_pb3(self):
if self.mp2.state() == QMediaPlayer.PlayingState: # set to pause if playing
self.mp2.pause()
else:
self.mp2.play() # set to play if in pause
def cb_sld2(self, position):
self.mp2.setPosition(position) # set video position to slider position
# run GUI
def dnn_viewer():
app = QApplication(sys.argv) # initiate app; sys.argv argument is only for OS-specific settings
viewer = DNN_Viewer() # create instance of Fil_Rouge_Dashboard class
viewer.show() # display dashboard
sys.exit(app.exec_()) # allow exit of the figure by clicking on the top right cross
# call window function
dnn_viewer()
tl:dr;
Use layout managers.
Explanation
Well, it seems you accidentally found a (possible) bug by doing something really wrong.
QVideoWidget is a widget that is more complex than it seems, since it interfaces itself with the underlying graphics system of the OS, and, in order to correctly show its contents (the video), it has to be actively notified of its geometry.
Simply speaking, QVideoWidget does not directly show the "pictures" of the video QMediaPlayer shows, but tells the Operating System to do so (well, not exactly, but we won't discuss it here). This is because video displaying might take advantage of some hardware acceleration, or require some processing (for example, for HDR videos), similarly to what 3D/OpenGL graphics does.
When a program is going to display some (system managed) video, it has to tell the OS about the available geometry for that video, so that the OS is able to show it at the correct coordinates, and possibly apply resizing, some form of "clipping" (if another window is overlayed, for example) or any other level of [post]processing.
The "something really wrong" I was talking about before is based on the fact that you are using fixed geometries (sizes and positions) for both video widgets, and I think that Qt is not able to notify the system about those geometries for more than a widget at once if that happens before the video window is actually mapped (as in "shown").
Why is it really wrong, besides the issue at hand?
Each one of our devices is mostly unique: what you see on your device will be shown in a (possibly radically) different way on other's.
There are many reasons for that, including:
Operating System (and versions) and its behavior;
screen size and DPI (for example, I wasn't able to view the complete window of your code, since I've a smaller screen);
default/customized system font size; most importantly, if the default font is very big, the widgets might overlap;
further customization (for example, default margins and spacing);
if the interface is "adaptive", the user should be able to resize the interface:
if the user has a smaller screen, the ui should be resizable, so that everything is visible instead of having the need to move the window beyond the screen margins (something that is sometimes impossible: for example on Windows you can't move a window above the top margin of the screen);
if the user has a bigger screen (or uses a very high DPI setting), the interface would be too small and some elements might be hard to read or interact with;
That's the reason for which almost any nowadays website uses "responsive" layouts, which adapt the contents according to the screen of the device they're going to be displayed into.
The solution is very simple, and will also solve the big issue about your GUI: avoid any fixed geometry for your GUI and use layout managers instead.
Note that you can still use fixed sizes (not positions, sizes!): that's not that big of an issue, but using layout managers will help you a lot with it, by repositioning all elements according to the available space.
The reason for it is that layout managers ensure that any resizing operation (something that also happens many times as soon as a window is shown the first time) is also notified to the system, whenever it's required (like, for instance, adapting the QVideoWidget output).
If you want to keep the "bottom-right/top-left" layout, you can still do that:
set a main QGridLayout for the widget (DNN_Viewer), create another grid layout for each player and add those layout to the main one.
The structure will be something like this:
+------------------------- DNN_Viewer -------------------------+
| | +------ player2Layout ------+ |
| | | | |
| | | vw2 | |
| | | | |
| | +-------+-------------------+ |
| | | pb2 | sld1 | |
| | +-------+-------------------+ |
+------------------------------+-------------------------------+
| +------ player1Layout------+ | |
| | | | |
| | vw1 | | |
| | | | |
| +-------+------------------+ | |
| | pb1 | sld2 | | |
| +-------+------------------+ | |
+------------------------------+-------------------------------+
class DNN_Viewer(QWidget):
# ...
def init_gui(self):
# create a grid layout for the widget and automatically set it for it
layout = QtWidgets.QGridLayout(self)
player1Layout = QtWidgets.QGridLayout()
# add the layout to the second row, at the first column
layout.addLayout(player1Layout, 1, 0)
# video for weights and gradients
self.vw1 = QVideoWidget(self)
# add the video widget at the first row and column, but set its column
# span to 2: we'll need to add two widgets in the second row, the play
# button and the slider
player1Layout.addWidget(self.vw1, 0, 0, 1, 2)
# ...
self.pb2 = QPushButton(self)
# add the button to the layout; if you don't specify rows and columns it
# normally means that the widget is added to a new grid row
player1Layout.addWidget(self.pb2)
# ...
self.sld1 = QSlider(Qt.Horizontal,self)
# add the slider to the second row, besides the button
player1Layout.addWidget(self.sld1, 1, 1)
# ...
player2Layout = QtWidgets.QGridLayout()
# add the second player layout to the first row, second column
layout.addLayout(player2Layout, 0, 1)
self.vw2 = QVideoWidget(self)
# same column span as before
player2Layout.addWidget(self.vw2, 0, 0, 1, 2)
# ...
self.pb3 = QPushButton(self)
player2Layout.addWidget(self.pb3, 1, 0)
# ...
self.sld2 = QSlider(Qt.Horizontal,self)
player2Layout.addWidget(self.sld2, 1, 1)
This will solve your main issue (and lots of others you didn't consider).
Some further suggestions:
use more descriptive variable names; things like pb2 or lb23 seem easier to use and you might be led to think that short variables equals less time spent typing. Actually, there's no final benefit in that: while it may be true that shorter variable names might improve compiling speed (especially for interpreted languages like Python), at the end there's almost no advantage; on the contrary, you'll have to remember what "sld2" means, while something like "player2Slider" is way more descriptive and easier to read (which means you'll read and debug faster, and people reading your code will understand it and help you much more easily)
for the same reason above, use more descriptive function names: names like cb_mp1_3 mean literally nothing; naming is really important, and the startup speed improvement reported above is almost dismissible with todays computers; it also helps you to get help from others: it took more time to understand what was your actual issue, than to understand what your code does, since all those names were almost meaningless to me; read more on the official Style Guide for Python Code (aka, PEP 8);
use comments wisely:
avoid over-commenting, it makes comments distracting while losing much of their purpose (that said, while "Let the code be the documentation" is a good concept, don't overextimate it)
avoid "fancy" formatted comments: they might seem cool, but at the end they are just annoying to deal with; if you want to comment a function to better describe what it does, use the triple quotes feature Python already provides; also consider that many code sharing services have column limits (and StackOverflow is amongst them): people would need to scroll each line to read the corresponding comment;
if you need a description for a single line function, it's possible that the function is not descriptive as it could or should be, as explained above;
be more consistent with blank lines separations between functions or classes: Python was created with readability in mind, and it's a good thing to follow that principle;
don't overwrite existing attribute names: self.width() and self.height() are base properties of all QWidgets, and you might need to access them often;
be more consistent with the imports you're using, especially with complex modules like Qt: you should either import the submodules (from PyQt5 import QtWidgets, ...) or the single classes (from PyQt5.QtWidgets import QApplication, ...); note that, while the latter could be considered more "pythonic", it's usually tricky with Qt, since it has hundreds of classes (and you might need tens of them in each script), then you always have to remember to add every class each time you need it, and you might end up importing unnecessary classes you're not using anymore; there's not much performance improvement using this approach, at least with Qt, especially if you forget to remove unnecessary imports (in your case, the possible benefit of importing single classes is completely canceled by the fact that there are at least 10 imported classes that are never actually used);
avoid unnecessary imports from other frameworks if they are not absolutely necessary: if you need to know the screen geometry, use QApplication.screens(), don't import Tk just for that;
So, I started again trying to solve the issue with musicamant's very exhaustive answer. It indeed solved the issue, but I was not happy with having a solution that would work only with adaptative GUIs. So I investigated again the issue, starting with a minimal GUI where only the two videos would be present. And, to my greatest amazement, the two videos played fine, even with a fixed size GUI.
So I started inflating the GUI again, adding all the elements till I recovered my initial GUI. And at some point, I experienced the bug again, which made it possible to identify the actual cause.
So the culprit is called... QFrame. Yes, for real. The Qframe caused all that mess. At first I was using a QFrame with setFrameShape(QFrame.Panel), so that a rectangular frame is created at once. Then I installed the video widget inside the frame. It turns out that with certain videos, the QFrame adopts a strange behaviours and kind of "covers" the video output, making the video viewer screen vanish. The sound remains unaffected. It only happens for certain videos and not for others, which does not make any real sense. Still, removing the frame instantaneously solves the issue, so that really is a bug.
It seems that with musicamante's solution, the frame does not adopt this strange behaviour, hence a working solution. Another possible solution with fixed size GUIs is to use frames that don't cover the video. Concretely, rather than using a single QFrame with setFrameShape(QFrame.Panel) which creates a rectangle in one frame, a set of four frames must be used, two of them being QFrame with setFrameShape(QFrame.Hline), and the other two being QFrame with setFrameShape(QFrame.Vline), organised to form a rectangle. I tested it and it works. The frames only cover the horizontal/vertical surfaces they go through, and so the "inside" of the rectangle is not part of any frame, which avoids the bug.
I know this has been asked many times before. I read all of those threads, and my case seems different. Everybody else who has this trouble has a few straightforward causes that I think I’ve ruled out, such as:
Starting a timer with no event loop running
Starting/stopping a timer from a thread other than the one that created the timer
Failing to set the parent property of a widget, leading to problems with the order of destruction
Below I have a minimal code sample that demonstrates the problem. Notice that I’ve started no threads or timers. I also have set the parent of every widget. If I remove the graph widgets, the problem goes away, so one is tempted to blame pyQtGraph, however, if I include the plot widgets but exclude all the blank tabs (i.e. every tab except tabCatchaTiger), the problem also goes away, and that seems to vindicate pyQtGraph.
Versions:
Windows 7
Python 2.7.8
Wing IDE 5.0.9-1
PyQt 4.11.1
PyQwt 5.2.1
PyQtGraph 0.9.8
Test case:
from PyQt4 import Qt, QtGui, QtCore
import PyQt4.Qwt5 as Qwt
import pyqtgraph as pg
pg.functions.USE_WEAVE = False # Lets pyqtgraph plot without gcc
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
# GUI for visualizing data from database
class crashyGUI(QtGui.QWidget) :
def __init__(self) :
# Make the window
QtGui.QWidget.__init__(self)
self.resize(700, QtGui.QDesktopWidget().screenGeometry(self).height()*.85)
self.setWindowTitle('Data Visualization')
# Create tab interface
tabWidget = QtGui.QTabWidget(self)
# define the tab objects
self.tabEeny = QtGui.QWidget(tabWidget)
self.tabMeeny = QtGui.QWidget(tabWidget)
self.tabMiney = QtGui.QWidget(tabWidget)
self.tabMoe = QtGui.QWidget(tabWidget)
self.tabCatchaTiger = QtGui.QWidget(tabWidget)
self.tabByThe = QtGui.QWidget(tabWidget)
self.tabToe = QtGui.QWidget(tabWidget)
# Initialize the tab objects
self.initTabCatchaTiger()
###########################################
############### Main Layout ###############
###########################################
tabWidget.addTab(self.tabEeny, 'Eeny')
tabWidget.addTab(self.tabMeeny, 'Meeny')
tabWidget.addTab(self.tabMiney, 'Miney')
tabWidget.addTab(self.tabMoe, 'Moe')
tabWidget.addTab(self.tabCatchaTiger, 'Catch a Tiger')
tabWidget.addTab(self.tabByThe, 'By The')
tabWidget.addTab(self.tabToe, 'Toe')
self.mainLayout = QtGui.QVBoxLayout(self)
self.mainLayout.addWidget(tabWidget)
self.setLayout(self.mainLayout)
def initTabCatchaTiger(self):
###########################################
############# ADC Capture Tab #############
###########################################
# define tab layout
grid = QtGui.QGridLayout(self.tabCatchaTiger)
# create copy of adc plot and add to row 3 of the grid
self.catchaTigerPlot1 = pg.PlotWidget(name = 'Catch a Tiger 1', parent = self.tabCatchaTiger)
self.catchaTigerPlot1.setTitle('Catch a Tiger 1')
grid.addWidget(self.catchaTigerPlot1, 2, 0, 1, 8)
self.catchaTigerPlot2 = pg.PlotWidget(name = 'Catch a Tiger 2', parent = self.tabCatchaTiger)
self.catchaTigerPlot2.setTitle('Catch a Tiger 2')
grid.addWidget(self.catchaTigerPlot2, 3, 0, 1, 8)
# set layout for tab
self.tabCatchaTiger.setLayout(grid)
def closeEvent(self, event) :
pass
def main() :
# open a QApplication and dialog() GUI
app = QtGui.QApplication([])
windowCrashy = crashyGUI()
windowCrashy.show()
app.exec_()
main()
There seem to be two closely-related issues in the example.
The first one causes Qt to print the QObject::startTimer: QTimer can only be used with threads started with QThread messages on exit.
The second one (which may not affect all users) causes Qt to print QPixmap: Must construct a QApplication before a QPaintDevice, and then dump core on exit.
Both of these issues are caused by python deleting objects in an unpredicable order when it exits.
In the example, the second issue can be fixed by adding the following line to the __init__ of the top-level window:
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
Unless QApplication.setQuitOnLastWindowClosed has been changed to False, this will ensure that the application quits at the right time, and that Qt has a chance to automatically delete all the children of the top-level window before the python garbage-collector gets to work.
However, for this to be completely successful, all the relevant objects must be linked together in a parent-child hierarchy. The example code does this where it can, but there seem to be some critical places in the initialization of the PlotWidget class where it is not done.
In particular, there is nothing to ensure that the central item of the PlotWidget has a parent set when it is created. If the relevant part of the code is changed to this:
class PlotWidget(GraphicsView):
...
def __init__(self, parent=None, background='default', **kargs):
GraphicsView.__init__(self, parent, background=background)
...
self.plotItem = PlotItem(**kargs)
# make sure the item gets a parent
self.plotItem.setParent(self)
self.setCentralItem(self.plotItem)
then the first issue with the QTimer messages also goes away.
Here's a better answer:
You are allowing the QApplication to be collected before python exits. This causes two different issues:
The QTimer error messages are caused by pyqtgraph trying to track its ViewBoxes after the QApplication has been destroyed.
The crash appears to be intrinsic to Qt / PyQt. The following crashes in the same way:
from PyQt4 import Qt, QtGui, QtCore
def main() :
app = QtGui.QApplication([])
x = QtGui.QGraphicsView()
s = QtGui.QGraphicsScene()
x.setScene(s)
x.show()
app.exec_()
main()
You can fix it by adding global app to your main function, or by creating the QApplication at the module level.
Try to write this in block of __init__:
self.setAttribute(Qt.WA_DeleteOnClose)
Personally, I don't put any effort into chasing exit crashes anymore--just use pg.exit() and be done with it.
(but if you do happen to find a bug in pyqtgraph, don't hesitate to open an issue on github)
I had this happen as well and in my case it was caused by a call to deleteLater() on the aboutToQuit-Signal of the application, like so:
def closeEvent(self, *args, **kwargs):
self.deleteLater()
if __name__ == "__main__":
application = QtWidgets.QApplication(sys.argv)
window = testApplication()
# Handle application exit
application.aboutToQuit.connect(window.closeEvent)
# System exit
sys.exit(application.exec_())
Getting rid of the deleteLater on the whole window seemed to solve it.
Thanks in advance for taking the time to read this. Apologies that it is somewhat verbose. But hopefully it fully explains the problem. Stripped code demonstrating the issue is included.
I'm having an issue with PyQt4 SIGNAL/SLOTS. While I can make everything work fine if I am writing in a single file, I can't make things work if I some of the functions I wish to use are moved to sub-directories/classes.
I've looked through the Python Bindings document I can see how this works when using a single file. But what I am trying to do is this:
main.py file in root dir which contains the MainWindow __init__ code.
This file imports a number of widgets. Each widget is stored in its own sub-directory. All sub-directories contain an __init__.py file. These sub-directories are inside of a directory called 'bin', which is itself in the root dir
Some of these widgets need to have SIGNAL/SLOT links between them This is where I fall down.
So the file structure is:
- main.py
- bin/textEditor/__init__.py
- bin/textEditor/plugin.py
- bin/logWindow/__init__.py
- bin/logWindow/plugin.py
The following code shows the problem. This code creates a very basic main window that contains a central QTextEdit() widget and a dockable QTextEdit() widget. All that happens is that when the text in the central widget is changed, the same text is shown in the dockable widget. The example works. But it does so by connecting the signal textChanged() in the bin/textEditor/plugin.py file that creates the central QTextEdit() with a function in main.py. I would like it to do exactly the same thing but connexted to the updateUi function in bin/textEditor/plugin.py
If anyone could shed some light on this, I would be hugely grateful. I'm sure it is simple. But direction to any tutorials that cover this or statements that I am doing it all very wrong are equally appreciated!. Thanks again for your time:
### main.py
import os
import sys
# Import PyQT modules
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# Start the main class
class MainWindow(QMainWindow):
# Initialise
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# Name and size the main window
self.setWindowTitle("EDITOR/LOG")
self.resize(800, 600)
import bin.logWindow.plugin as logWindow
logWindow.create(self)
import bin.textEditor.plugin as textEditor
textEditor.create(self)
def updateUi(self):
# I can connect to this function from within bin/textEditor/plugin.py (see
# below) but I want to connect to the function located in
# bin/textEditor/plugin.py instead
text = self.editor.toPlainText()
self.logWidget.setText(text)
# Run the app
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
# Call main
main()
The code inside of the two plugin files is:
### bin/textEditor/plugin.py
# Import PyQT modules
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def create(self):
# Add a dockable widget
self.logDockWidget = QDockWidget("Log", self)
self.logDockWidget.setObjectName("LogDockWidget")
self.logDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea|
Qt.RightDockWidgetArea)
self.logWidget = QTextEdit()
self.logDockWidget.setWidget(self.logWidget)
self.addDockWidget(Qt.LeftDockWidgetArea, self.logDockWidget)
And
### bin/logWindow/plugin.py
Import PyQT modules
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def create(self):
# Create a text editing box
self.editor = QTextEdit()
# Add to main window
self.setCentralWidget(self.editor)
# connect text change to update log window. This is presumably what I need to
# change so that it connects to the function below instead of the on in main.py
self.connect(self.editor, SIGNAL("textChanged()"), self.updateUi)
def updateUi(self):
text = self.editor.toPlainText()
self.logWidget.setText(text)
For starters, is there a reason you're using a very old version of the PyQt release document? The new one is: here
There are a few things you are doing that are a bit unusual. Generally import statements in python are placed at the top of the file (to more easily see dependencies), but I assume you're doing this to support a more generalized import system for plugins in the future.
It seems like the basic problem is you're trying to connect a signal source to a slot in another object, without storing that other object in a particular place. To do this you probably need to either make the connection in main, make a neutral "updateUi" slot that emits it's own special signal that all the plugins are waiting for, or just keep a reference to those subobjects in main and be careful with the initialization order.