I am trying to have a throbber (in the form of an animated chasing arrows gif) playing while I call a Popen command but it doesn't work because I think the gui is completely unresponsive while the Popen command is running. How can I fix this?
Please check my code below.
import subprocess
import os
import sys
from PyQt4 import QtCore, QtGui
class Test(QtGui.QDialog):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setMinimumSize(200, 200)
self.buttonUpdate = QtGui.QPushButton()
self.buttonUpdate.setText("Get updates")
self.lbl1 = QtGui.QLabel()
self.lbl2 = QtGui.QLabel()
self.lblm2 = QtGui.QLabel()
gif = os.path.abspath("chassingarrows.gif")#throbber
self.movie = QtGui.QMovie(gif)
self.movie.setScaledSize(QtCore.QSize(20, 20))
self.pixmap = QtGui.QPixmap("checkmark.png")#green checkmark
self.pixmap2 = self.pixmap.scaled(20, 20)
verticalLayout = QtGui.QVBoxLayout(self)
h2 = QtGui.QHBoxLayout()
h2.addWidget(self.lblm2)
h2.addWidget(self.lbl2)
h2.setAlignment(QtCore.Qt.AlignCenter)
verticalLayout.addWidget(self.lbl1)
verticalLayout.addLayout(h2)
verticalLayout.addWidget(self.buttonUpdate, 0, QtCore.Qt.AlignRight)
self.buttonUpdate.clicked.connect(self.get_updates)
def get_updates(self):
try:
self.lbl1.setText("Updating")
self.lblm2.setMovie(self.movie)
self.movie.start()
self.setCursor(QtCore.Qt.BusyCursor)
p1 = subprocess.Popen(['apt', 'update'], stdout=subprocess.PIPE, bufsize=1)
p1.wait()
self.movie.stop()
self.lblm2.setPixmap(self.pixmap2)
self.unsetCursor()
self.lbl1.setText("Done update")
except subprocess.CalledProcessError, e:
print e.output
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
test = Test()
test.show()
sys.exit(app.exec_())
Instead of using subprocess.Popen, use QProcess which allow to callback when the process finished using finished signal:
def get_updates(self):
self.lbl1.setText("Updating")
self.lblm2.setMovie(self.movie)
self.movie.start()
self.setCursor(QtCore.Qt.BusyCursor)
self.p1 = QProcess()
self.p1.finished.connect(self.on_apt_update_finished)
self.p1.start('apt', ['update'])
def on_apt_update_finished(self, exit_code, exit_status):
self.movie.stop()
self.lblm2.setPixmap(self.pixmap2)
self.unsetCursor()
self.lbl1.setText("Done update")
Related
I'm writing a PyQt programe where I'd like to allow the user to launch their preferred editor to fill in a TextEdit field.
So the goal is to launch an editor (say vim) externally on a tmp file, and upon editor closing, get its contexts into a python variable.
I've found a few similar questions like Opening vi from Python, call up an EDITOR (vim) from a python script, invoke an editor ( vim ) in python. But they are all in a "blocking" manner that works like the git commit command. What I am after is a "non-blocking" manner (because it is a GUI), something like the "Edit Source" function in zimwiki.
My current attempt:
import os
import tempfile
import threading
import subprocess
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
proc = subprocess.Popen(popenArgs)
# this immediately finishes OPENING vim.
rec=proc.wait()
print('# <runInThread>: rec=', rec)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
def openEditor():
fd, filepath=tempfile.mkstemp()
print('filepath=',filepath)
def cb(tmppath):
print('# <cb>: cb tmppath=',tmppath)
with open(tmppath, 'r') as tmp:
lines=tmp.readlines()
for ii in lines:
print('# <cb>: ii',ii)
return
with os.fdopen(fd, 'w') as tmp:
cmdflag='--'
editor_cmd='vim'
cmd=[os.environ['TERMCMD'], cmdflag, editor_cmd, filepath]
print('#cmd = ',cmd)
popenAndCall(cb, cmd)
print('done')
return
if __name__=='__main__':
openEditor()
I think it failed because the Popen.wait() only waits until the editor is opened, not until its closing. So it captures nothing from the editor.
Any idea how to solve this? Thanks!
EDIT:
I found this answer which I guess is related. I'm messing around trying to let os wait for the process group, but it's still not working. Code below:
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
proc = subprocess.Popen(popenArgs, preexec_fn=os.setsid)
pid=proc.pid
gid=os.getpgid(pid)
#rec=proc.wait()
rec=os.waitid(os.P_PGID, gid, os.WEXITED | os.WSTOPPED)
print('# <runInThread>: rec=', rec, 'pid=',pid, 'gid=',gid)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
I assume this gid=os.getpgid(pid) gives me the id of the group, and os.waitid() wait for the group. I also tried os.waitpid(gid, 0), didn't work either.
I'm on the right track?
UPDATE:
It seems that for some editors that works, like xed. vim and gvim both fails.
With QProcess you can launch a process without blocking the Qt event loop.
In this case I use xterm since I do not know which terminal is established in TERMCMD.
from PyQt5 import QtCore, QtGui, QtWidgets
class EditorWorker(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self, command, parent=None):
super(EditorWorker, self).__init__(parent)
self._temp_file = QtCore.QTemporaryFile(self)
self._process = QtCore.QProcess(self)
self._process.finished.connect(self.on_finished)
self._text = ""
if self._temp_file.open():
program, *arguments = command
self._process.start(
program, arguments + [self._temp_file.fileName()]
)
#QtCore.pyqtSlot()
def on_finished(self):
if self._temp_file.isOpen():
self._text = self._temp_file.readAll().data().decode()
self.finished.emit()
#property
def text(self):
return self._text
def __del__(self):
self._process.kill()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._button = QtWidgets.QPushButton(
"Launch VIM", clicked=self.on_clicked
)
self._text_edit = QtWidgets.QTextEdit(readOnly=True)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._button)
lay.addWidget(self._text_edit)
#QtCore.pyqtSlot()
def on_clicked(self):
worker = EditorWorker("xterm -e vim".split(), self)
worker.finished.connect(self.on_finished)
#QtCore.pyqtSlot()
def on_finished(self):
worker = self.sender()
prev_cursor = self._text_edit.textCursor()
self._text_edit.moveCursor(QtGui.QTextCursor.End)
self._text_edit.insertPlainText(worker.text)
self._text_edit.setTextCursor(prev_cursor)
worker.deleteLater()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I guess in your case you should change
"xterm -e vim".split()
to
[os.environ['TERMCMD'], "--", "vim"]
Possible commands:
- xterm -e vim
- xfce4-terminal --disable-server -x vim
Update:
Implementing the same logic that you use with pyinotify that is to monitor the file, but in this case using QFileSystemWatcher which is a multiplatform solution:
from PyQt5 import QtCore, QtGui, QtWidgets
class EditorWorker(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self, command, parent=None):
super(EditorWorker, self).__init__(parent)
self._temp_file = QtCore.QTemporaryFile(self)
self._process = QtCore.QProcess(self)
self._text = ""
self._watcher = QtCore.QFileSystemWatcher(self)
self._watcher.fileChanged.connect(self.on_fileChanged)
if self._temp_file.open():
self._watcher.addPath(self._temp_file.fileName())
program, *arguments = command
self._process.start(
program, arguments + [self._temp_file.fileName()]
)
#QtCore.pyqtSlot()
def on_fileChanged(self):
if self._temp_file.isOpen():
self._text = self._temp_file.readAll().data().decode()
self.finished.emit()
#property
def text(self):
return self._text
def __del__(self):
self._process.kill()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._button = QtWidgets.QPushButton(
"Launch VIM", clicked=self.on_clicked
)
self._text_edit = QtWidgets.QTextEdit(readOnly=True)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._button)
lay.addWidget(self._text_edit)
#QtCore.pyqtSlot()
def on_clicked(self):
worker = EditorWorker("gnome-terminal -- vim".split(), self)
worker.finished.connect(self.on_finished)
#QtCore.pyqtSlot()
def on_finished(self):
worker = self.sender()
prev_cursor = self._text_edit.textCursor()
self._text_edit.moveCursor(QtGui.QTextCursor.End)
self._text_edit.insertPlainText(worker.text)
self._text_edit.setTextCursor(prev_cursor)
worker.deleteLater()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
The issue I reproduced is that proc is the gnome-terminal process and not the vim process.
Here are the two options that work for me.
1) Find the process of your text editor and not that of your terminal. With the right process ID, the code can wait for the process of your text editor to finish.
With psutil (portable)
Finds the latest editor process in the list of all running processes.
import psutil
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
editor_cmd=popenArgs[-2] # vim
proc = subprocess.Popen(popenArgs)
proc.wait()
# Find the latest editor process in the list of all running processes
editor_processes = []
for p in psutil.process_iter():
try:
process_name = p.name()
if editor_cmd in process_name:
editor_processes.append((process_name, p.pid))
except:
pass
editor_proc = psutil.Process(editor_processes[-1][1])
rec=editor_proc.wait()
print('# <runInThread>: rec=', rec)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
Without psutil (works on Linux, but not portable to Mac OS or Windows)
Draws from https://stackoverflow.com/a/2704947/241866 and the source code of psutil.
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
editor_cmd=popenArgs[-2] # vim
proc = subprocess.Popen(popenArgs)
proc.wait()
# Find the latest editor process in the list of all running processes
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
editor_processes = []
for pid in pids:
try:
process_name = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read().split('\0')[0]
if editor_cmd in process_name:
editor_processes.append((process_name, int(pid)))
except IOError:
continue
editor_proc_pid = editor_processes[-1][1]
def pid_exists(pid):
try:
os.kill(pid, 0)
return True
except:
return
while True:
if pid_exists(editor_proc_pid):
import time
time.sleep(1)
else:
break
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
2) As a last resort, you can catch a UI event before updating the text:
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
proc = subprocess.Popen(popenArgs)
# this immediately finishes OPENING vim.
rec=proc.wait()
raw_input("Press Enter") # replace this with UI event
print('# <runInThread>: rec=', rec)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
I think #eyllanesc's solution is very close to what zim is doing (zim is using GObject.spawn_async() and GObject.child_watch_add(), I've no experiences with GObject, I guess that's the equivalent to QProcess.start()). But we run into some problems regarding how some terminals (like gnome-terminal) handles new terminal session launching.
I tried to monitor the temporary file opened by the editor, and on writing/saving the temp file I could call my callback. The monitoring is done using pyinotify. I've tried gnome-terminal, xterm, urxvt and plain gvim, all seem to work.
Code below:
import threading
from PyQt5 import QtCore, QtGui, QtWidgets
import pyinotify
class EditorWorker(QtCore.QObject):
file_close_sig = QtCore.pyqtSignal()
edit_done_sig = QtCore.pyqtSignal()
def __init__(self, command, parent=None):
super(EditorWorker, self).__init__(parent)
self._temp_file = QtCore.QTemporaryFile(self)
self._process = QtCore.QProcess(self)
#self._process.finished.connect(self.on_file_close)
self.file_close_sig.connect(self.on_file_close)
self._text = ""
if self._temp_file.open():
program, *arguments = command
self._process.start(
program, arguments + [self._temp_file.fileName()]
)
tmpfile=self._temp_file.fileName()
# start a thread to monitor file saving/closing
self.monitor_thread = threading.Thread(target=self.monitorFile,
args=(tmpfile, self.file_close_sig))
self.monitor_thread.start()
#QtCore.pyqtSlot()
def on_file_close(self):
if self._temp_file.isOpen():
print('open')
self._text = self._temp_file.readAll().data().decode()
self.edit_done_sig.emit()
else:
print('not open')
#property
def text(self):
return self._text
def __del__(self):
try:
self._process.kill()
except:
pass
def monitorFile(self, path, sig):
class PClose(pyinotify.ProcessEvent):
def my_init(self):
self.sig=sig
self.done=False
def process_IN_CLOSE(self, event):
f = event.name and os.path.join(event.path, event.name) or event.path
self.sig.emit()
self.done=True
wm = pyinotify.WatchManager()
eventHandler=PClose()
notifier = pyinotify.Notifier(wm, eventHandler)
wm.add_watch(path, pyinotify.IN_CLOSE_WRITE)
try:
while not eventHandler.done:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
notifier.stop()
return
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._button = QtWidgets.QPushButton(
"Launch VIM", clicked=self.on_clicked
)
self._text_edit = QtWidgets.QTextEdit(readOnly=True)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._button)
lay.addWidget(self._text_edit)
#QtCore.pyqtSlot()
def on_clicked(self):
worker = EditorWorker(["gnome-terminal", '--', "vim"], self)
worker.edit_done_sig.connect(self.on_edit_done)
#QtCore.pyqtSlot()
def on_edit_done(self):
worker = self.sender()
prev_cursor = self._text_edit.textCursor()
self._text_edit.moveCursor(QtGui.QTextCursor.End)
self._text_edit.insertPlainText(worker.text)
self._text_edit.setTextCursor(prev_cursor)
worker.deleteLater()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
BUT pyinotify only works in Linux. If you could find a cross-platform solution (at least on Mac) please let me know.
UPDATE: this doesn't seem to be robust. pyinotify reports file writing instead of just file closing. I'm depressed.
I have problem with the results of my pop-up window. Below I have shown part of my code to understand the problem.
It's a kind of pop-up window where the user makes some choice in the GUI. After this it should show a window where there will be the question "Are you sure?", and two buttons "Yes" and "No".
The problem is that when I test the code below (before and after the msg.show()), I have the same value set as False.
Why doesnt it work like this:
Before function -> False
Show my window and wait to click the button
If I clicked button "Yes", then give True, else False
How I can handle this properly? Is there another approach?
from PyQt4 import QtCore, QtGui
from Message import Ui_Message
import sys
class MessageBox(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent=None)
self.msg = Ui_Message()
self.msg.setupUi(self)
self.confirmed=False
self.declined=False
QtCore.QObject.connect(self.msg.NoButton, QtCore.SIGNAL(("clicked()")), self.Declined)
QtCore.QObject.connect(self.msg.YesButton, QtCore.SIGNAL(("clicked()")), self.Confirmed)
def Confirmed(self):
self.confirmed = True
MessageBox.close(self)
return True
def Declined(self):
self.declined = True
MessageBox.close(self)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
msg = MessageBox()
print('Befor show window',msg.confirmed)
msg.show()
print('After show window', msg.confirmed)
sys.exit(app.exec_())
Your example doesn't work, because you are printing "After show window" before the window has closed. It is the exec() method that blocks, not the show() method, so your example would need to be written like this:
app = QtGui.QApplication(sys.argv)
msg = MessageBox()
print('Before show window', msg.confirmed)
msg.show()
app.exec_() # this blocks, waiting for close
print('After show window', msg.confirmed)
sys.exit()
However, a much more realistic example showing how to use a dialog to confirm an action would be something like this:
import sys
from PyQt4 import QtCore, QtGui
class MessageBox(QtGui.QDialog):
def __init__(self, parent=None):
super(MessageBox, self).__init__(parent)
self.yesButton = QtGui.QPushButton('Yes')
self.noButton = QtGui.QPushButton('No')
layout = QtGui.QGridLayout(self)
layout.addWidget(QtGui.QLabel('Are you sure?'), 0, 0)
layout.addWidget(self.yesButton, 1, 0)
layout.addWidget(self.noButton, 1, 1)
self.yesButton.clicked.connect(self.accept)
self.noButton.clicked.connect(self.reject)
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtGui.QPushButton('Do Something')
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
if self.confirmSomething():
print('Yes')
else:
print('No')
def confirmSomething(self):
msg = MessageBox(self)
result = msg.exec_() == QtGui.QDialog.Accepted
msg.deleteLater()
return result
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
Just a simple problem (not for me): when I close the window, the program is still running. Here is the code:
from PyQt4 import QtCore, QtGui
from PyQt4.Qt import QString
import sys
import sensors
from sensors import *
import threading
class MainWindow(QtGui.QWidget):
signalUpdate = QtCore.pyqtSignal() # 1 - define a new signal in mainwindow class
# 2 -connect this signal to the update() function
#emit signal
#main window
def __init__(self):
#vars for core temp and name
self.tempValue = 0
self.name = '0'
super(MainWindow, self).__init__()
self.setGeometry(50, 50, 250, 150)
self.setWindowTitle("Title here")
self.setFixedSize(250, 150)
self.home()
#make widgets (progressBar and labe)
def home(self):
self.prgB = QtGui.QProgressBar(self)
self.prgB.setGeometry(20, 20, 210, 20)
#self.prgB.setOrientation(QtCore.Qt.Vertical)
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("motif"))#stles -> motif, Plastique, Windows
self.lbl = QtGui.QLabel(self)
self.lbl.setGeometry(60, 40, 210, 20)
self.signalUpdate.connect(self.update) #connect this signal to the update() function
lay = QtGui.QVBoxLayout()
lay.addWidget(self.prgB)
lay.addWidget(self.lbl)
self.setLayout(lay)
self.tmp()
self.show()
#update() to update label and progressbar values
def update(self):
textas = ('%s : %.1f' % (self.name, self.tempValue))
self.lbl.setText(str(textas + ' C'))
self.prgB.setFormat(QString.number(self.tempValue)+ ' C')
self.prgB.setValue(self.tempValue)
#temp() to get chip data from sensors (temp, name etc)
def tmp(self):
sensors.init()
try:
for chip in sensors.iter_detected_chips():
#print (chip)
#print('Adapter:', chip.adapter_name)
for feature in chip:
if feature.label == 'Physical id 0':
self.tempValue = feature.get_value()
self.name = feature.label
#print ('%s (%r): %.1f' % (feature.name, feature.label, feature.get_value()))
threading.Timer(2.0, self.tmp).start()
self.signalUpdate.emit() #emit signal
#print
finally:
sensors.cleanup()
def run():
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
run()
Why is that happening, and how to fix it? (I was trying to research on google and yes there are many forums with same question but I get no luck so far to fix it).
EDIT: problem is still not fixed, can someone show/tell how to stop threading.Time on program exit? Please :)
Call the timer's cancel() method in your widget's (overridden) closeEvent() method:
def tmp(self):
...
self.timer = threading.Timer(2.0, self.tmp)
self.timer.start()
self.signalUpdate.emit() #emit signal
def closeEvent(self):
self.timer.cancel()
I've tested that this works:
without the threading, app exits;
with the threading.Timer, but without the timer cancel, app never exits;
with the timer cancel, app exits
I am new to pyqt.I am trying to invoke a child GUI when a button is clicked in the parent GUI. In this process, parent GUI has to wait for the child GUI to be closed by the user after selecting some inputs. But this is not happening, Parent GUI does execute the next lines after which the child GUI has been invoked. Below is the code where I am passing an argument to child GUI from parent GUI. The child GUI will return value based on OK/Cancel button click
Code:
import sys
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Child(QtGui.QWidget):
def __init__(self,switches=None):
super(Child,self).__init__()
self.swwidget = QtGui.QWidget()
self.swlayout = QtGui.QGridLayout()
switches = ['abc1','def1']
switches.sort()
self.switches = switches
def switchesUI(self):
self.swwidget.setWindowModality(QtCore.Qt.ApplicationModal)
self.swl = len(self.switches)
self.sw = {}
self.addsw = []
print ("I am in switchesUI")
#Add the switches to layout dynamically
for i in range(self.swl):
self.sw[i] = QtGui.QCheckBox(self.switches[i])
self.swlayout.addWidget(self.sw[i],i,0)
self.swbuttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel);
self.swbuttonbox.setOrientation(QtCore.Qt.Horizontal)
self.swlayout.addWidget(self.swbuttonbox)
self.swwidget.setWindowTitle('Switches')
self.swwidget.setLayout(self.swlayout)
self.swwidget.show()
self.connect(self.swbuttonbox,QtCore.SIGNAL("accepted()"),self.swaccept)
self.connect(self.swbuttonbox,QtCore.SIGNAL("rejected()"),self.swreject)
def swaccept(self):
for i in range(self.swl):
if self.sw[i].isChecked():
self.addsw.append(self.switches[i])
self.swwidget.close()
return self.addsw
def swreject(self):
self.swwidget.close()
return None
class Parent(QtGui.QWidget):
def __init__(self):
super(Parent,self).__init__()
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.assw = ['Test1','Test2']
self.CH = Child(self.assw)
self.connect(self.button,SIGNAL("clicked()"),self.popup)
print ("Child GUI closed")
def popup(self):
self.CH.switchesUI()
def main():
app = QtGui.QApplication(sys.argv)
form = Parent()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
After the button "Test" is clicked, a child GUI will pop-up. I don't want the statement "Child GUI Closed" to be printed till the child GUI is closed.
Can someone suggest me how to achieve this functionality ?
You have to handle closeEvent to perform operations when a window wants to close, also since your Child class inherits from QWidget which means it's a QWidget itself you dont need to create another one with self.swwidget
import sys
from PyQt4 import QtGui,QtCore,Qt
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Child(QtGui.QWidget):
def __init__(self,switches=None):
super(Child,self).__init__()
# self.swwidget = QtGui.QWidget() # you don't need to do this you can add all the properties to self
self.swlayout = QtGui.QGridLayout()
switches = ['abc1','def1']
switches.sort()
self.switches = switches
def switchesUI(self):
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.swl = len(self.switches)
self.sw = {}
self.addsw = []
print ("I am in switchesUI")
#Add the switches to layout dynamically
for i in range(self.swl):
self.sw[i] = QtGui.QCheckBox(self.switches[i])
self.swlayout.addWidget(self.sw[i],i,0)
self.swbuttonbox = QtGui.QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel);
self.swbuttonbox.setOrientation(QtCore.Qt.Horizontal)
self.swlayout.addWidget(self.swbuttonbox)
self.setWindowTitle('Switches')
self.setLayout(self.swlayout)
self.show()
self.connect(self.swbuttonbox,QtCore.SIGNAL("accepted()"),self.swaccept)
self.connect(self.swbuttonbox,QtCore.SIGNAL("rejected()"),self.swreject)
def swaccept(self):
for i in range(self.swl):
if self.sw[i].isChecked():
self.addsw.append(self.switches[i])
self.close()
return self.addsw
def swreject(self):
self.close()
return None
def closeEvent(self, event):
print ("Child GUI closed")
class Parent(QtGui.QWidget):
def __init__(self):
super(Parent,self).__init__()
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.assw = ['Test1','Test2']
self.CH = Child(self.assw)
self.connect(self.button,SIGNAL("clicked()"),self.popup)
def popup(self):
self.CH.switchesUI()
def main():
app = QtGui.QApplication(sys.argv)
form = Parent()
form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I have a browser application written in python with PySide Qt. But now I want to add a button in the toolbar to print the website. How do I do this? Because CTRL + P is not working in the application.
Here is the code:
import sys
from PySide import QtCore, QtGui, QtWebKit, QtHelp, QtNetwork
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
Action1 = QtGui.QAction('Google', self)
Action1.setShortcut('Ctrl+M')
Action1.triggered.connect(self.load_message)
self.toolbar1 = self.addToolBar('Google')
self.toolbar1.addAction(Action1)
Action2 = QtGui.QAction('Yahoo', self)
Action2.setShortcut('Ctrl+H')
Action2.triggered.connect(self.load_list)
self.toolbar2 = self.addToolBar('Yahoo')
self.toolbar2.addAction(Action2)
exitAction = QtGui.QAction('Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(self.on_exit)
self.toolbar3 = self.addToolBar('Exit')
self.toolbar3.addAction(exitAction)
self.resize(750, 750)
self.setWindowTitle('Browser')
self.web = QtWebKit.QWebView(self)
self.web.load(QtCore.QUrl('http://www.google.com'))
self.setCentralWidget(self.web)
def on_exit(self):
QtGui.qApp.quit
def load_message(self):
self.web.load(QtCore.QUrl('http://www.google.com'))
def load_list(self):
self.web.load(QtCore.QUrl('http://www.yahoo.com'))
app = QtGui.QApplication(sys.argv)
app.setWindowIcon(QtGui.QIcon('myicon.ico'))
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
In your __init__ method add a Print action:
printAction = QtGui.QAction('Print', self)
printAction.setShortcut('Ctrl+P')
printAction.triggered.connect(self.do_print)
self.toolbar4 = self.addToolBar('Print')
self.toolbar4.addAction(printAction)
And create a do_print method:
def do_print(self):
p = QtGui.QPrinter()
p.setPaperSize(QtGui.QPrinter.A4)
p.setFullPage(True)
p.setResolution(300)
p.setOrientation(QtGui.QPrinter.Portrait)
p.setOutputFileName('D:\\test.pdf')
self.web.print_(p)
This will print to a file D:\test.pdf.
To configure your printer differently see the QPrinter documentation. Also if you want a print preview dialog see the QPrintPreviewDialog docs.
If you want a standard print dialog the use:
def do_print(self):
p = QtGui.QPrinter()
dialog = QtGui.QPrintDialog(p)
dialog.exec_()
self.web.print_(p)