I have a widget that ends with pressing OK button. I want that the widget returns a value to the main program (which is in simple example below the obtained value increased by 1). How can I do this?
Also, is there any more elegant way to show the same QWidget with the different title?
MWE:
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self,val):
self.val=val
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
self.End= QtWidgets.QPushButton('OK', self)
self.End.clicked.connect(self.end)
MainLayout = QtWidgets.QVBoxLayout()
MainLayout.addWidget(self.End)
self.setLayout(MainLayout)
self.setWindowTitle(str(self.val))
self.show()
def end(self):
# return self.val+1
self.close()
def main():
app = QtWidgets.QApplication(sys.argv)
for i in range(10):
ex = MainWindow(i)
ex.show()
res = app.exec_()
print(res)
sys.exit()
if __name__ == '__main__':
main()
If you want to use a widget to get some value after some processing, the correct thing is to use QDialog, that prevents any other window from opening if you use exec_():
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, val, parent=None):
super(Dialog, self).__init__(parent)
self.val = val
self.initUI()
def initUI(self):
endButton = QtWidgets.QPushButton('OK')
endButton.clicked.connect(self.on_clicked)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(endButton)
self.setWindowTitle(str(self.val))
#QtCore.pyqtSlot()
def on_clicked(self):
self.val += 1
self.accept()
def main():
app = QtWidgets.QApplication(sys.argv)
for i in range(10):
ex = Dialog(i)
ex.setAttribute(QtCore.Qt.WA_DeleteOnClose)
if ex.exec_() == QtWidgets.QDialog.Accepted:
print(ex.val)
if __name__ == '__main__':
main()
It is unclear what exactly your intention is with this code. If val is supposed to be a counter for the number of MainWindow instances, then you must declare it as a static:
class MainWindow(QtWidgets.QWidget):
val = 0
def __init__(self):
super(MainWindow, self).__init__()
MainWindow.val += 1
self.initUI()
def initUI(self):
# ...
self.setWindowTitle(str(MainWindow.val))
# ...
def main():
# ...
for i in range(10):
ex = MainWindow()
# ...
# ...
There is nothing wrong in calling QWidget::setWindowTitle() so I don't see a problem here.
If you really want to have it as a non-static class member and each MainWindow uses it as some sort of ID, you can easily call the value right after the ex.show():
def main():
app = QtWidgets.QApplication(sys.argv)
for i in range(10):
ex = MainWindow()
ex.show()
print(MainWindow.val)
res = app.exec_()
print(res)
sys.exit()
Just because you close a widget doesn't mean you have destroyed it so calling the values stored inside the object is not an issue.
However it appears you are not familiar what QApplication::exec() is intended for. It runs the main loop of the given application and returns the exit status (you can set it when calling exit() as the parameter passed to that function). If you simply want to get the value of val upon closing a widget you can simply override the closeEvent handler and emit a signal. Create a QObject instance and connect each MainWindow instance's custom close signal to a slot and do with the value whatever you want to.
Related
Assume we have an interface with Buttons and LineEdits in one .py file. I have this code in another, which inherits it:
import Inter_Input_Blasting as interf
from PyQt6 import QtCore,QtWidgets,QtGui
from functools import partial
class MainWindow(QtWidgets.QMainWindow):
def on_clicked(self):
print("Button Pushed")
def __init__(self,parent=None):
super(MainWindow, self).__init__(parent)
self.ui = interf.Ui_MainWindow()
self.ui.setupUi(self)
self.ui.calc_button.clicked.connect(MainWindow.on_clicked)
self.ui.input_overall_1.textChanged.connect(MainWindow.gather_data)
def gather_data(self):
return self.ui.input_overall_1.text()
if __name__== "__main__":
import sys
app = interf.QtWidgets.QApplication(sys.argv)
Form = MainWindow()
Form.show()
sys.exit(app.exec())
So, i need to print my values into console when i put it in the lineedit fiels. The .textChanged() method is working, but the .gather_data() isn't.
Provide a variable for storing the text:
def __init__(self,parent=None):
self.txt = None
Then in method gather_data, store the text in that variable:
def gather_data(self):
sef.txt = self.ui.input_overall_1.text()
then before sys.exit, print that value:
r = app.exec()
print(Form.txt)
sys.exit(r)
I would like to know how to implement QProgressBar, which shows the progress of calculation in main thread.
Please refer to below codes.
import sys
from PySide2.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QProgressBar
from PySide2.QtCore import QThread
class BarThread(QThread):
# Progress Bar UI Definition
def __init__(self):
QThread.__init__(self)
self.window = QWidget()
self.pgsb = QProgressBar()
self.lay = QVBoxLayout()
self.lay.addWidget(self.pgsb)
self.window.setLayout(self.lay)
self.isRun = False
# Thread Function Definition
def run(self):
self.window.show()
while self.isRun:
self.pgsb.setValue(self.percent)
print(self.percent)
if self.percent == 100:
self.isRun = False
class Tool(QWidget):
# Main UI Definition
def __init__(self):
windowWidth = 300
windowHeight = 300
QWidget.__init__(self)
self.setWindowTitle("Example")
self.resize(windowWidth, windowHeight)
self.bt = QPushButton('Numbering')
self.layout = QVBoxLayout()
self.layout.addWidget(self.bt)
self.setLayout(self.layout)
# Main Function Link Definition
self.bt.clicked.connect(self.numbering)
# Main Function Definition
def numbering(self):
bth = BarThread()
bth.start()
bth.isRun = True
for x in range(0,100000):
bth.percent = x/1000
print(x)
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Tool()
widget.show()
sys.exit(app.exec_())
You can copy and paste directly onto your python IDE.
(it needs PySide2. It can be installed with 'pip install pyside2' in your prompt).
This code executes simple numbering, however, this doesn't show numbering progress.
How can I solve this problem? Thank you in advance.
P.S. I'm using Windows 10 with PyCharm.
You have at least the following errors:
You must not modify the GUI from another thread, in your case the run method is executed in another thread but you try to modify the value of the QProgressBar, in addition to displaying a widget which is not allowed. If you want to modify the GUI with the information provided in the execution in the secondary thread you must do it through signals since they are thread-safe
The for loop is the blocking task so it must be executed in another thread.
Considering the above, the solution is:
import sys
from PySide2.QtWidgets import (
QApplication,
QWidget,
QPushButton,
QVBoxLayout,
QProgressBar,
)
from PySide2.QtCore import QThread, Signal
class ProgressWidget(QWidget):
def __init__(self, parent=None):
super(ProgressWidget, self).__init__(parent)
self.pgsb = QProgressBar()
lay = QVBoxLayout(self)
lay.addWidget(self.pgsb)
class BarThread(QThread):
progressChanged = Signal(int)
def run(self):
percent = 0
for x in range(0, 100000):
percent = x / 100
self.progressChanged.emit(percent)
class Tool(QWidget):
"""Main UI Definition"""
def __init__(self, parent=None):
super(Tool, self).__init__(parent)
self.setWindowTitle("Example")
self.resize(300, 300)
self.bt = QPushButton("Numbering")
layout = QVBoxLayout(self)
layout.addWidget(self.bt)
# Main Function Link Definition
self.bt.clicked.connect(self.numbering)
self.bar_thread = BarThread(self)
self.progress_widget = ProgressWidget()
self.bar_thread.progressChanged.connect(self.progress_widget.pgsb.setValue)
# Main Function Definition
def numbering(self):
self.bar_thread.start()
self.progress_widget.show()
def closeEvent(self, event):
super(Tool, self).closeEvent(event)
self.bar_thread.quit()
self.bar_thread.wait()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Tool()
widget.show()
sys.exit(app.exec_())
I am trying to display a loading gif after a button is pressed. This is the code I currently have
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainWindow (QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow,self).__init__(parent)
self.setGeometry(50,50,240,320)
self.home()
def home(self):
but = QtGui.QPushButton("Example", self)#Creates the brew coffee button
but.clicked.connect(self.gif_display)
but.resize(200,80)
but.move(20,50)
self.show()
def gif_display(self):
l = QMovieLabel('loading.gif')
l.show()
class QMovieLabel(QLabel):
def __init__(self, fileName):
QLabel.__init__(self)
m = QMovie(fileName)
m.start()
self.setMovie(m)
def setMovie(self, movie):
QLabel.setMovie(self, movie)
s=movie.currentImage().size()
self._movieWidth = s.width()
self._movieHeight = s.height()
def run():
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
run()
I would like to display the gif called "loading.gif" after the button is pressed. Nothing appears after pressing the button and I am unsure of what to do to get the gif to properly appear. The gif is the same size as the screen that I created (240x320).
The problem is that QMovieLabel is a local variable within gif_display so it will be deleted when the function finishes running, so the solution is to avoid deleting it. There are 2 options: make it an attribute of the class or make it a child of the window , I will show the second method since I think it is the one you want:
import sys
from PyQt4 import QtCore, QtGui
class MainWindow (QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow,self).__init__(parent)
self.setGeometry(50,50,240,320)
self.home()
def home(self):
but = QtGui.QPushButton("Example", self) # Creates the brew coffee button
but.clicked.connect(self.gif_display)
but.resize(200,80)
but.move(20,50)
self.show()
#QtCore.pyqtSlot()
def gif_display(self):
l = QMovieLabel('loading.gif', self)
l.adjustSize()
l.show()
class QMovieLabel(QtGui.QLabel):
def __init__(self, fileName, parent=None):
super(QMovieLabel, self).__init__(parent)
m = QtGui.QMovie(fileName)
self.setMovie(m)
m.start()
def setMovie(self, movie):
super(QMovieLabel, self).setMovie(movie)
s=movie.currentImage().size()
self._movieWidth = s.width()
self._movieHeight = s.height()
def run():
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
run()
I want use the autoconnection feature. I am using this example:
http://www.eurion.net/python-snippets/snippet/Connecting%20signals%20and%20slots.html
it works, but I want to create my own signals and own slots, the example using built in signals.
for example, here are a custom signal with a custom slot, but don't works:
import sys
from PyQt4 import QtGui, QtCore
class SignalsAndSlots(QtGui.QWidget):
testSignal = QtCore.pyqtSignal(str,name='testSignal')
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setObjectName('testObject')
self.label = QtGui.QLabel(self)
QtCore.QMetaObject.connectSlotsByName(self)
self.emitSignal()
def emitSignal(self):
self.testSignal.emit('message')
#QtCore.pyqtSlot(str,name='on_testObject_testSignal')
def autoSlot(self,msg):
self.label.setText(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
gui = SignalsAndSlots()
gui.show()
app.exec_()
Thanks a lot
Ber is right. This what the pyqt documentation says:
QMetaObject.connectSlotsByName searches recursively for all child objects of the given object [...]
Here is a simple example with custom signals :
import sys
from PyQt4 import QtGui, QtCore
class CustomButton(QtGui.QPushButton):
custom_clicked = QtCore.pyqtSignal(str, name='customClicked')
def mousePressEvent(self, event):
self.custom_clicked.emit("Clicked!")
class SignalsAndSlots(QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
layout = QtGui.QHBoxLayout(self)
self.custom_button = CustomButton("Press Me", self)
self.custom_button.setObjectName('customButton')
self.label = QtGui.QLabel("Nothing...", parent=self)
layout.addWidget(self.custom_button)
layout.addWidget(self.label)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.pyqtSlot(str, name='on_customButton_customClicked')
def autoSlot(self, msg):
self.label.setText(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
gui = SignalsAndSlots()
gui.show()
app.exec_()
But I think you should consider not using the object names. New-style signal connection is way neater. Here is the same application :
import sys
from PyQt4 import QtGui, QtCore
class CustomButton(QtGui.QPushButton):
custom_clicked = QtCore.pyqtSignal(str)
def mousePressEvent(self, event):
self.custom_clicked.emit("Clicked!")
class SignalsAndSlots(QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
layout = QtGui.QHBoxLayout(self)
self.custom_button = CustomButton("Press Me", self)
self.custom_button.setObjectName('customButton')
self.label = QtGui.QLabel("Nothing...", parent=self)
layout.addWidget(self.custom_button)
layout.addWidget(self.label)
self.custom_button.custom_clicked.connect(self.on_clicked)
def on_clicked(self, msg):
self.label.setText(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
gui = SignalsAndSlots()
gui.show()
app.exec_()
I investigated a bit into the doc. of QtCore.QMetaObject.connectSlotsByName().
For this, I made an MCVE to collect things not working vs. things working:
#!/usr/bin/python3
import sys
from PyQt5.QtCore import QT_VERSION_STR
from PyQt5.QtCore import QMetaObject, pyqtSlot
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QPushButton, QWidget
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# build GUI
qMain = QWidget()
qVBox = QVBoxLayout()
qBtnPreview1 = QPushButton("Preview 1")
qBtnPreview1.setObjectName("preview1")
qVBox.addWidget(qBtnPreview1)
qBtnPreview2 = QPushButton("Preview 2")
qBtnPreview2.setObjectName("preview2")
qVBox.addWidget(qBtnPreview2)
qBtnPreview3 = QPushButton("Preview 3")
qBtnPreview3.setObjectName("preview3")
qVBox.addWidget(qBtnPreview3)
qBtnPreview4 = QPushButton("Preview 4")
qBtnPreview4.setObjectName("preview4")
qVBox.addWidget(qBtnPreview4)
qBtnPreview5 = QPushButton("Preview 5")
qBtnPreview5.setObjectName("preview5")
qVBox.addWidget(qBtnPreview5)
qBtnPreview6 = QPushButton("Preview 6")
qBtnPreview6.setObjectName("preview6")
qVBox.addWidget(qBtnPreview6)
qMain.setLayout(qVBox)
self.setCentralWidget(qMain)
# install signal handlers
qBtnPreview1.clicked.connect(lambda: print("preview1 clicked."))
qBtnPreview2.clicked.connect(lambda: print("preview2 clicked."))
qBtnPreview3.clicked.connect(lambda: print("preview3 clicked."))
qBtnPreview4.clicked.connect(lambda: print("preview4 clicked."))
qBtnPreview5.clicked.connect(lambda: print("preview5 clicked."))
qBtnPreview6.clicked.connect(lambda: print("preview6 clicked."))
QMetaObject.connectSlotsByName(self)
#pyqtSlot()
def preview1(self):
print("MainWindow.preview1() called.")
#pyqtSlot()
def preview2_clicked(self):
print("MainWindow.preview2_clicked() called.")
#pyqtSlot()
def on_preview3(self):
print("MainWindow.on_preview3() called.")
#pyqtSlot()
def on_preview4_clicked(self):
print("MainWindow.on_preview4_clicked() called.")
#pyqtSlot(name='on_preview5_clicked')
def preview5_clicked(self):
print("MainWindow.preview5_clicked() called.")
def on_preview6_clicked(self):
print("MainWindow.on_preview6_clicked() called.")
if __name__ == '__main__':
print("Qt Version: {}".format(QT_VERSION_STR))
app = QApplication(sys.argv)
# build GUI
qWinMain = MainWindow()
qWinMain.show()
# runtime loop
sys.exit(app.exec_())
Output:
$ ./testQMetaObjectConnectSlotsByName.py
Qt Version: 5.9.3
After clicking each of the six buttons once, I got:
preview1 clicked.
preview2 clicked.
preview3 clicked.
preview4 clicked.
MainWindow.on_preview4_clicked() called.
preview5 clicked.
MainWindow.preview5_clicked() called.
preview6 clicked.
MainWindow.on_preview6_clicked() called.
MainWindow.on_preview6_clicked() called.
Observations:
MainWindow.preview1(), MainWindow.preview2_clicked(), MainWindow.on_preview3() were not connected. They don't follow the required convention of QtCore.QMetaObject.connectSlotsByName().
MainWindow.on_preview4_clicked() is connected by name – properly following the required name convention.
MainWindow.preview5_clicked() is connected as well – by giving the required name by #pyqtSlot(name='on_preview5_clicked').
MainWindow.on_preview6_clicked() is connected as well – even without marking it as slot explicitly. (However, it is called twice for any reason which might be undesirable.)
Explicit connections work in any case and appear more robust to me.
Further readings:
QMetaObject::connectSlotsByName() (Qt5 C++ doc.)
Connecting Slots By Name (PyQt5 doc.)
PyQt5.QtCore.pyqtSlot() (PyQt5 doc.).
Actually, this was my answer to another question (SO: How to display two images in each Qlabel PyQt5) until I realized that QtCore.QMetaObject.connectSlotsByName() unlikely plays a role in that question.
So, I moved it here where it may be more appropriate even although the question is a bit aged.
from PyQt4.Qt import Qt, QObject,QLineEdit
from PyQt4 import QtGui, QtCore
import utils
class DirLineEdit(QLineEdit, QtCore.QObject):
"""docstring for DirLineEdit"""
def __init__(self):
super(DirLineEdit, self).__init__()
self.xmlDataObj = utils.ReadWriteCustomPathsToDisk()
self.defaultList = self.xmlDataObj.xmlData().values()
self._pathsList()
def focusInEvent(self, event):
self.completer().complete()
def _pathsList(self):
completerList = QtCore.QStringList()
for i in self.defaultList:
completerList.append(QtCore.QString(i))
lineEditCompleter = QtGui.QCompleter(completerList)
self.setCompleter(lineEditCompleter)
def __dirCompleter(self):
dirModel = QtGui.QFileSystemModel()
dirModel.setRootPath(QtCore.QDir.currentPath())
dirModel.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Files)
dirModel.setNameFilterDisables(0)
completer = QtGui.QCompleter(dirModel, self)
completer.setModel(dirModel)
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.setCompleter(completer)
app = QtGui.QApplication(sys.argv)
smObj = DirLineEdit()
smObj.show()
app.exec_()
The above code works except I cannot set the text from the selection I make in the AutoComplete that pops up on focus in event .. Any idea why I am not able to make set text of the selected by completer ?
The problem is that your reimplementation of focusInEvent does not call its baseclass method. You should always do this unless you are certain you want to completely override the default behaviour. In this particular case, it's also important to call the baseclass focusInEvent method before invoking the completer, because that will obviously re-take the focus.
Here's a working demo that fixes the problems in the example code:
from PyQt4 import QtGui, QtCore
class LineEdit(QtGui.QLineEdit):
def __init__(self, strings, parent):
QtGui.QLineEdit.__init__(self, parent)
self.setCompleter(QtGui.QCompleter(strings, self))
def focusInEvent(self, event):
QtGui.QLineEdit.focusInEvent(self, event)
self.completer().complete()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
strings = 'one two three four five six seven eight'.split()
self.edit1 = LineEdit(strings, self)
self.edit2 = LineEdit(strings, self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit1)
layout.addWidget(self.edit2)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())