How to wait for user input in PyQt with Line Edit? - python

I'm working on a project of "wrapping" a program I built with PyQt GUI and I'm stuck with a basic thing.
I want the program to stop procceeding with the code and wait for my input, just like raw_input(). Here is my code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class myWidget(QDialog):
def __init__(self,parent=None):
super(myWidget, self).__init__(parent)
self.lineEdit = QLineEdit()
self.textBrowser = QTextBrowser()
self.top_btn = QPushButton("Ask me")
self.bottom_btn = QPushButton("disable")
layout = QVBoxLayout()
layout.addWidget(self.textBrowser)
layout.addWidget(self.lineEdit)
layout.addWidget(self.top_btn)
layout.addWidget(self.bottom_btn)
self.setLayout(layout)
self.lineEdit.setDisabled(True)
self.lineEdit.clear()
self.connect(self.top_btn, SIGNAL("clicked()"), self.inputFunc)
self.connect(self.bottom_btn, SIGNAL("clicked()"), self.disableLine)
def inputFunc(self):
self.lineEdit.clear()
self.lineEdit.setDisabled(False)
self.textBrowser.setText("Welcome to #1 button. what do you want to do?")
userInput = self.lineEdit.text()
if userInput == "anything":
self.textBrowser.append("Ok i will leave you alone")
exit()
else:
self.textBrowser.append("say what?")
def disableLine(self):
self.lineEdit.clear()
self.textBrowser.append("Line edit is disabled")
self.lineEdit.setDisabled(True)
app = QApplication(sys.argv)
form = myWidget()
form.show()
app.exec_()
As you can see, there is a Line Edit and its variable. But it doesn't wait for my input, it proceed with the code and of course it doesn't let me to change the result of my "if" statement.
How do I pause the code and wait for the user input so my "if" statement result will be changed just like raw_input()? (if possible, without adding any new layouts.)
Thank you.

Structured programming has a different paradigm than event-oriented programming, GUIs use events to warn the slots that should perform that task.
In your case, the processing part must be done in another method and called when a signal is issued
For your case the QLineEdit widget has 2 signals that could serve you, the first one is editingFinished, and the second is returnPressed, in this case I choose the second that is emitted when I press the enter or return key. Then we connect that signal with the called process slot, that executes the task.
I made some changes that do not affect the design, first change the base class from QDialog to QWidget, in addition to the style of connection between the signals and the slots. If you want to close the window you must use close()
complete code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class myWidget(QWidget):
def __init__(self,parent=None):
super(myWidget, self).__init__(parent)
self.lineEdit = QLineEdit()
self.textBrowser = QTextBrowser()
self.top_btn = QPushButton("Ask me", )
self.bottom_btn = QPushButton("disable")
layout = QVBoxLayout()
layout.addWidget(self.textBrowser)
layout.addWidget(self.lineEdit)
layout.addWidget(self.top_btn)
layout.addWidget(self.bottom_btn)
self.setLayout(layout)
self.lineEdit.setDisabled(True)
self.top_btn.clicked.connect(self.inputFunc)
self.lineEdit.returnPressed.connect(self.process)
#self.bottom_btn.clicked.connect(self.disableLine)
def inputFunc(self):
self.lineEdit.setDisabled(False)
self.textBrowser.setText("Welcome to #1 button. what do you want to do?")
def process(self):
userInput = self.lineEdit.text()
if userInput == "anything":
self.textBrowser.append("Ok i will leave you alone")
#self.close()
else:
self.textBrowser.append("say what?")
self.lineEdit.clear()
app = QApplication(sys.argv)
w = myWidget()
w.show()
sys.exit(app.exec_())

Related

Maya wait for Qt window to close

Normally in Qt applications you would use this at the start and end of your code:
app = QtWidgets.QApplication(sys.argv)
...
app.exec_()
But in Maya, you don't use this because Qt runs on the Maya application it self. I'm sure this works the same for many other applications as well if you don't know what Maya is. That said, my code looks like this:
import sys
from PySide2 import QtWidgets, QtGui, QtCore
class Test():
def __init__(self):
self.open_qt()
def open_qt(self):
# app = QtWidgets.QApplication(sys.argv) # Don't need this in Maya
self.window = QtWidgets.QWidget() # I tried QDialog also
btn = QtWidgets.QPushButton("press me")
btn.clicked.connect(self.login)
lay = QtWidgets.QVBoxLayout()
lay.addWidget(btn)
self.window.setLayout(lay)
self.window.show()
# app.exec_() # Don't need this in Maya
def login(self):
print("logged in!")
print("before")
temp = Test()
print("after")
But running this in Maya I get this result:
before
after
logged in!
But I need it to be:
before
logged in!
after
If you run this code outside of Maya (and you use those two commented out lines) then you get the correct result (block above here).
How can I get the Maya Qt to also wait correctly like it would if you used QtWidgets.QApplication(sys.argv)?
A QDialog might be more well suited for your needs, as it runs its own event loop that won't block the program, while waiting for the dialog to be completed.
The important thing to do is to call of its exec_() and call accept() when needed.
from PySide2 import QtWidgets, QtGui, QtCore
class Test(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
layout = QtWidgets.QVBoxLayout(self)
self.spinBox = QtWidgets.QSpinBox()
layout.addWidget(self.spinBox)
btn = QtWidgets.QPushButton("press me")
layout.addWidget(btn)
btn.clicked.connect(self.login)
def login(self):
print("logged in!")
self.accept()
dialog = Test()
if dialog.exec_():
print(dialog.spinBox.value())
I don't have Maya, but according to this answer you can get its main window using the maya.OpenMayaUI module and shiboken's wrapInstance().

Why does editingFinished signal generates two times when QMessageBox is being shown by connected slot?

from PySide2 import QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.lineEdit = QtWidgets.QLineEdit()
self.lineEdit.setText("1")
self.lineEdit.editingFinished.connect(self.check)
self.lineEdit2 = QtWidgets.QLineEdit()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addWidget(self.lineEdit)
vlay.addWidget(self.lineEdit2)
def check(self):
if self.lineEdit.text() == "1":
popup = QtWidgets.QMessageBox(self)
popup.setWindowTitle("why")
popup.show()
print("test")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
So in this script if you press "Enter" while editing "lineEdit", the "check" slot is called two times. But if you click on "lineEdit2", the slot will be called only one time, as it should be. This happens because of QMessageBox, but why?
If you check the docs:
void QLineEdit::editingFinished()
This signal is emitted when the Return or Enter key is pressed or the
line edit loses focus. Note that if there is a validator() or
inputMask() set on the line edit and enter/return is pressed, the
editingFinished() signal will only be emitted if the input follows the
inputMask() and the validator() returns QValidator::Acceptable.
(emphasis mine)
In your case the first print is given when you press Enter and the second print is given when the QLineEdit loses focus since the QMessageBox obtains it.
If you want to avoid this behavior, you can block the emittion of QLineEdit events a moment before the QMessageBox is displayed until a moment after it is displayed:
self.lineEdit.blockSignals(True)
popup = QtWidgets.QMessageBox(self)
popup.setWindowTitle("why")
QtCore.QTimer.singleShot(100, lambda: self.lineEdit.blockSignals(True))
popup.show()
print("test")

Cannot automatically close second window with QInputDialog

Issue in my code sample:
On "Select..." the selection dialog appears and works basically, but closing it with "Ok" or "Cancel", unfortunately leaves a second window open (title "New Popup"), which is very ugly... :/ I am searching for the way to close that automatically after the user applied the selection in the list, returning to the main window (and the data processing should still work of course).
Thanks for any help! :)
Background: Inside a basic pyqt5 window application, I created a simple list selection as a second (popup) window (separate class), where the user can select an item and the result is then processed in the main window class.
That works basically but I've got an issue with correctly closing the popup window and I was not able to find a solution since hours... Basically I tried self.close, self.destroy, self.hide etc. but nothing had an effect, probably I am missing a piece.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtCore import QTimer
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QLineEdit, QInputDialog, QLabel, QVBoxLayout
class PopupDialog(QtWidgets.QDialog):
def __init__(self):
super(PopupDialog, self).__init__()
self.selected_item = None
layout = QtWidgets.QFormLayout()
self.setLayout(layout)
self.setWindowTitle("New Popup")
self.setMinimumWidth(400)
items = ("C", "C++", "Java", "Python")
item, ok = QInputDialog.getItem(self, "select input dialog",
"list of languages", items, 0, False)
self.selected_item = item
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.setMinimumWidth(600)
self.setWindowTitle("Main Window")
self.le = QtWidgets.QLineEdit()
button = QtWidgets.QPushButton("Select...")
button.clicked.connect(self.get_input)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.le)
layout.addWidget(button)
self.setLayout(layout)
def get_input(self):
popup = PopupDialog()
popup.exec_()
print("got selection data: ", popup.selected_item)
self.le.setText(popup.selected_item)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

PySide2 Non-Blocking QMessageBox doesn't show text

While my application does some time-consuming stuff, I want to display a message box to the user while the applications is busy. I don't want any buttons (like OK or Cancel) and I can't call exec_() on the message box because that is blocking.
I checked a number of qt sites and the code I need seems to boil down to:
message_box = QMessageBox()
message_box.setText(str('Reading Device, Please Wait...'))
message_box.show()
# do work here
message_box.close()
When I run the code, I get the message box, but without the text. What am I doing wrong?
I've included a working example below:
#!/usr/bin/env python3
import sys
import time
from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication,
QVBoxLayout, QDialog, QMessageBox)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button = QPushButton("Click Me")
layout = QVBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
# Add button signal to dowork slot
self.button.clicked.connect(self.dowork)
def dowork(self):
message_box = QMessageBox()
message_box.setText(str('Reading Device, Please Wait...'))
message_box.show()
delay = 2.5
while delay:
sys.stdout.write('Working...\n')
time.sleep(0.5) # do some time-consuming stuff...
delay -= 0.5
message_box.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Form()
print('starting app...')
form.show()
sys.exit(app.exec_())
If you click the button, the message box pops up and is shown while the 'work' is being done. When the 'work' is finished, the message box disappears again - as it should. But no text is shown in the message box.
There is a similar question here: qmessagebox-not-show-text-when-call-show, but that does not answer my question.
You can not have a task that consumes a lot of time (more than 30 ms) since it blocks the GUI eventloop preventing Qt from doing its job normally, instead it uses a thread next to the signals to update the GUI from the other thread:
import sys
import threading
import time
from PySide2 import QtCore, QtWidgets
class Form(QtWidgets.QDialog):
started = QtCore.Signal()
finished = QtCore.Signal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button = QtWidgets.QPushButton("Click Me")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
# Add button signal to dowork slot
self.button.clicked.connect(self.on_clicled)
self._message_box = QtWidgets.QMessageBox()
self._message_box.setText(str('Reading Device, Please Wait...'))
self._message_box.setStandardButtons(QtWidgets.QMessageBox.NoButton)
self.started.connect(self._message_box.show)
self.finished.connect(self._message_box.accept)
#QtCore.Slot()
def on_clicled(self):
thread = threading.Thread(target=self.dowork, daemon=True)
thread.start()
def dowork(self):
delay = 2.5
self.started.emit()
while delay:
sys.stdout.write('Working...\n')
time.sleep(0.5) # do some time-consuming stuff...
delay -= 0.5
self.finished.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
form = Form()
print('starting app...')
form.show()
sys.exit(app.exec_())
You should almost certainly be using QProgressDialog. Set minimum and maximum to zero for an 'indeterminate' look.
https://doc.qt.io/qt-5/qprogressdialog.html
Although forcing a redraw after your setText() will probably work:
self._message_box.repaint()
QtWidgets.QApplication.processEvents()

PyQt application freezes if dialog rejected

I have a small application, that requires login before it starts.
But if user rejects login(press cancel button), application won't close, it's just freeze.
Here is the simplified code:
import sys
from PyQt5 import QtWidgets, QtCore
class LoginWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(LoginWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Login')
self.login_input = QtWidgets.QLineEdit(self)
self.login_input.move(10, 10)
self.password_input = QtWidgets.QLineEdit(self)
self.password_input.move(10, 50)
self.password_input.setEchoMode(QtWidgets.QLineEdit.Password)
self.button_box = QtWidgets.QDialogButtonBox(self)
self.button_box.move(10, 80)
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel |
QtWidgets.QDialogButtonBox.Ok)
self.button_box.accepted.connect(self.login)
self.button_box.rejected.connect(self.reject)
def login(self):
self.accept()
def cancel(self):
self.reject()
class MainWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Main')
self.input = QtWidgets.QLineEdit(self)
self.input.move(10, 10)
self.show()
def main():
app = QtWidgets.QApplication([])
l = LoginWindow()
l.show()
login_result = l.exec_()
print(login_result)
if login_result == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What am I doing wrong?
I use python 3 and PyQt5
This happens because PySide has not processed any of it's events.
app.exec_()
This starts the main event loop that continually processes every GUI interaction. This should be called before you execute any GUI code, so the events can be processed correctly from the event Queue.
The QDialog.exec_() is a blocking operation preventing the code from continuing until it gets a response.
If you want to see the dialog items then you may be able to get around this.
QtGui.QApplication.processEvents()
This processes all of the events in the event Queue, so you would probably have to keep calling this method.
Also after you initialize your main window you will have to show the main window.
I find a way to avoid this bug:
I've changed main function:
def main():
app = QtWidgets.QApplication([])
if LoginWindow().exec_() == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
And it works normal, but is still can't understand, what was the root cause of the problem

Categories

Resources