PyQt5 QFileDialog stops application from closing - python

I am trying to write a PyQt5 application that does the following:
Creates and opens a Main Window. The MainWindow has a function that opens a QFileDialog window.
Function to open QFileDialog can be triggered in two ways (1) from a file menu option called 'Open' (2) automatically, after the Main window is shown.
My problem is that I haven't found a way to get the QfileDialog to automatically open (2) that doesn't cause the application to hang when the main window is closed. Basic example of code can be found below:
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QWidget,
QHBoxLayout, QCalendarWidget, QScrollArea, QFileDialog, QAction, QFrame)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QAction(QIcon('/usr/share/icons/breeze/places/64/folder-open.svg'), 'Open', self)
self.openAction.triggered.connect(self.openDialog)
self.menubar = QMenuBar(self)
fileMenu = self.menubar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.event_widgets = EventWidgets(self)
self.setMenuBar(self.menubar)
self.setCentralWidget(self.event_widgets)
def openDialog(self):
ics_path = QFileDialog.getOpenFileName(self, 'Open file', '/home/michael/')
class EventWidgets(QWidget):
def __init__(self, parent):
super(EventWidgets, self).__init__(parent)
self.initUI()
def initUI(self):
self.calendar = QCalendarWidget(self)
self.frame = QFrame()
self.scrollArea = QScrollArea()
self.scrollArea.setWidget(self.frame)
horizontal_box = QHBoxLayout()
horizontal_box.addWidget(self.calendar)
horizontal_box.addWidget(self.scrollArea)
self.setLayout(horizontal_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
app_window.openDialog()
sys.exit(app.exec_())
Code has been tested on KDE Neon and Arch Linux, both have same issue.
I can get round this issue by handling the close event of the Main Window manually - i.e. adding this function to MainWindow:
def closeEvent(self, event):
sys.exit()
But I am not sure a) why this is necessary b) if it is best practice.

I tried your code on macOS Sierra and it works as it's supposed to. However I would propose a different approach to solve your problem.
What you could do is to implement the showEvent() function in your MainWindow class, which is executed whenever a widget is displayed (either using .show() or .showMaximized()) and trigger your custom slot to open the QFileDialog. When doing this you could make use of a single shot timer to trigger the slot with some minimal delay: the reason behind this is that if you simply open the dialog from within the showEvent(), you will see the dialog window but not the MainWindow below it (because the QFileDialog is blocking the UI until the user perform some action). The single shot timer adds some delay to the opening of the QFileDialog, and allows the MainWindow to be rendered behind the modal dialog. Here is a possible solution (not that I made some minor changes to your code, which you should be able to easily pick up):
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QtWidgets.QAction('Open', self)
self.openAction.triggered.connect(self.openDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.event_widgets = EventWidgets(self)
self.setCentralWidget(self.event_widgets)
def showEvent(self, showEvent):
QtCore.QTimer.singleShot(50, self.openDialog)
#QtCore.pyqtSlot()
def openDialog(self):
ics_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/Users/daniele/')
class EventWidgets(QtWidgets.QWidget):
def __init__(self, parent):
super(EventWidgets, self).__init__(parent)
self.calendar = QtWidgets.QCalendarWidget(self)
self.frame = QtWidgets.QFrame()
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidget(self.frame)
horizontal_box = QtWidgets.QHBoxLayout()
horizontal_box.addWidget(self.calendar)
horizontal_box.addWidget(self.scrollArea)
self.setLayout(horizontal_box)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())

Related

The program just stops running once i click the signinButton and exits with Process finished with exit code -1073740791 (0xC0000409)

from PyQt6.QtWidgets import (
QMainWindow, QApplication, QDialog, QDialogButtonBox, QLabel, QTextEdit, QPushButton, QMessageBox, QMdiArea,
QTableWidgetItem, QStackedWidget
)
from PyQt6 import uic
import sys
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi(r"C:\Users\csc\Documents\Rentour\front.ui", self)
self.show()
# define widgets
self.button = self.findChild(QPushButton, "signinButton")
self.signinButton.clicked.connect(self.OpenSignUp)
def OpenSignUp(self):
Sign_Up = Second()
widget.addWidget(Sign_Up)
widget.setCurrentIndex(widget.currentIndex()+1)
class Second(QMainWindow):
def __init__(self):
super(Second, self).__init__()
uic.loadUi(r"C:\Users\csc\Documents\Rentour\signpopup.ui", self)
# define widgets
self.button = self.findChild(QPushButton, "SubmitSignButton")
self.SubmitSignButton.clicked.connect(self.SignUpSave)
def SignUpSave(self):
email =self.EmailLine.text()
phoneno =self.PhonenoLine.text()
name =self.NameLine.text()
password = self.PasswordLine.text()
print(password)
app = QApplication(sys.argv)
mainwindow = UI()
widget = QStackedWidget()
widget.addWidget(mainwindow)
widget.show()
app.exec()
this is my code. Am trying to create a login/signup page. So when i click the signinButton, i want it to load the ui for the page which will have a bunch of line edits whose inputs im attempting to store in variables.
The ui files were made using qt designer and and i made this file from scratch. I also referred code with Hala(Youtuber). I Am trying to create a login/signup page. So when i click the signinButton, i want it to load the ui for the page which will have a bunch of line edits whose inputs im attempting to store in variables.
The problem is in your OpenSignUp function. The line widget.addWidget(Sign_Up) is not a valid command for a couple of reasons.
Also Sign_Up varable is holding a newly constructed QMainWindow, which are meant to be top level widgets and shouldn't be added to a layout.
It isn't totally clear what you are trying to achieve, but I am going to assume that you are trying to launch a secondary sign in window, In which case you want to use the open() method to launch the new window.
For example:
def OpenSignUp(self):
self.Sign_Up = Second()
self.Sign_Up.open()
Since you are using a stacked widget it is also possible that your goal is to simply switch to a different widget in the same window. In which case your Second class should probably just be a standard QWidget, and not a QMainWindow. and your stacked widget should be set as the central widget of your UI class.
So that would probably look something like this:
from PyQt6.QtWidgets import (
QMainWindow, QApplication, QDialog, QDialogButtonBox, QLabel, QTextEdit, QPushButton, QMessageBox, QMdiArea,
QTableWidgetItem, QStackedWidget
)
from PyQt6 import uic
import sys
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
# uic.loadUi(r"C:\Users\csc\Documents\Rentour\front.ui", self)
self.widget = QStackedWidget()
self.widget.addWidget(mainwindow)
self.setCentralWidget(self.widget)
self.Sign_Up = Second()
self.widget.addWidget(self.Sign_Up)
# define widgets
self.button = self.findChild(QPushButton, "signinButton")
self.signinButton.clicked.connect(self.OpenSignUp)
def OpenSignUp(self):
self.widget.setCurrentIndex(widget.currentIndex()+1)
class Second(QWidget):
def __init__(self):
super(Second, self).__init__()
uic.loadUi(r"C:\Users\csc\Documents\Rentour\signpopup.ui", self)
# define widgets
self.button = self.findChild(QPushButton, "SubmitSignButton")
self.SubmitSignButton.clicked.connect(self.SignUpSave)
def SignUpSave(self):
email =self.EmailLine.text()
phoneno =self.PhonenoLine.text()
name =self.NameLine.text()
password = self.PasswordLine.text()
print(password)
app = QApplication(sys.argv)
mainwindow = UI()
mainwindow.show()
app.exec()
Except you will need to rearrange the .ui file for UI class so that it is applied on top of the stacked widget as well.

Timers cannot be stopped from another thread - Remove Focus

import sys
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit
class Worker(QThread):
def __init__(self, textBox):
super().__init__()
self.textBox = textBox
def run(self):
while True:
if self.textBox.text() == "close":
app.quit()
break
if self.textBox.text() == "removeFocus":
self.textBox.clearFocus()
class window(QWidget):
def __init__(self):
super().__init__()
vBox = QVBoxLayout()
self.setLayout(vBox)
self.resize(600, 400)
textBox = QLineEdit()
vBox.addWidget(textBox)
worker = Worker(textBox)
worker.start()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = window()
sys.exit(app.exec())
When I type "close" in the textBox it works very fine but when I type "removeFocus", it still works but I get this error:
QObject::killTimer: Timers cannot be stopped from another thread
Why am I getting such an error even though the program is running?
(Since the process I want to do is very simple, I don't think I can go into much detail. I've just started learning Python. This is the first time I use this site. I'm sorry if I made a mistake while creating a post. Thank you)
In Qt you must not access or modify the GUI information from another thread (see this for more information) since it does not guarantee that it works (the GUI elements are not thread-safe), in your case luckily you have no problems but It is dangerous to use your approach in real.
In your case it is also unnecessary to use threads since it is enough to use the textChanged signal from QLineEdit.
import sys
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit
class Window(QWidget):
def __init__(self):
super().__init__()
vBox = QVBoxLayout(self)
self.resize(600, 400)
self.textBox = QLineEdit()
vBox.addWidget(self.textBox)
self.textBox.textChanged.connect(self.on_text_changed)
#pyqtSlot(str)
def on_text_changed(self, text):
if text == "close":
QApplication.quit()
elif text == "removeFocus":
self.textBox.clearFocus()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())

Sending Data from Child to Parent Window in PyQt5

What I can't do
I'm not able to send data back from a child to a parent window.
What I have
I've got a complex GUI with several windows sendíng data to child windows. Each window represents a unique Python-script in the same directory. There was no need to explicitely specify parents and childs, as the communication was always unidirectional (parent to child). However, now I need to send back data from childs to parents and can't figure out how to do this as each window (i.e. each class) has its own file.
Example
Here's a minimal example showing the base of what I want to accomplish.
What it does: win01 opens win02 and win02 triggers func in win01.
# testfile01.py
import sys
from PyQt5.QtWidgets import *
import testfile02 as t02
class win01(QWidget):
def __init__(self, parent=None):
super(win01, self).__init__(parent)
self.win02 = t02.win02()
self.button = QPushButton("open win02", self)
self.button.move(100, 100)
self.button.clicked.connect(self.show_t02)
def initUI(self):
self.center
def show_t02(self):
self.win02.show()
def func(self):
print("yes!")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = win01()
ex.show()
sys.exit(app.exec_())
##########################################################################
# testfile02.py
from PyQt5.QtWidgets import *
import testfile01 as t01
class win02(QWidget):
def __init__(self, parent=None):
super(win02, self).__init__(parent)
self.win01 = t01.win01()
self.button = QPushButton()
self.button.clicked.connect(self.win01.func)
def initUI(self):
self.center()
What I tried
Importing testfile01 in the second window always leads to the error:
RecursionError: maximum recursion depth exceeded.
Then, I tried the following approaches, but they didn't work either:
Not importing testfile01 in win02 and adjusting parent=None to different other objects
Importing testfile01 within the __init__ call of win02
Creating a signal in win02 to trigger func in win01
The Question
Is there a solution how to properly trigger func in win01 from win02?
Why are you getting RecursionError: maximum recursion depth exceeded?
You are getting it because you have a circular import that generates an infinite loop, in testfile01 you are importing the file testfile02, and in testfile02 you are importing testfile01, .... So that is a shows of a bad design.
Qt offers the mechanism of signals for objects to communicate information to other objects, and this has the advantage of non-dependence between classes that is a great long-term benefit (as for example that avoids circular import), so for that reason I think it is the most appropriate.
For this I will create the clicked signal in the class win02 that will be triggered by the clicked signal of the button, and make that clicked signal call the func:
testfile01.py
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication
import testfile02 as t02
class win01(QWidget):
def __init__(self, parent=None):
super(win01, self).__init__(parent)
self.win02 = t02.win02()
self.win02.clicked.connect(self.func)
self.button = QPushButton("open win02", self)
self.button.move(100, 100)
self.button.clicked.connect(self.show_t02)
def show_t02(self):
self.win02.show()
def func(self):
print("yes!")
if __name__ == "__main__":
app = QApplication(sys.argv)
ex = win01()
ex.show()
sys.exit(app.exec_())
testfile02.py
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout
class win02(QWidget):
clicked = pyqtSignal()
def __init__(self, parent=None):
super(win02, self).__init__(parent)
self.button = QPushButton("call to func")
self.button.clicked.connect(self.clicked)
lay = QVBoxLayout(self)
lay.addWidget(self.button)
I recommend you read:
https://doc.qt.io/qt-5/signalsandslots.html
Both the Widgets are independent and have no link in between.
Set win01 parent of win02.
In class win01
Replace :
self.win01 = t02.win02()
#and
self.win02.show()
with:
self.win01 = t02.win02(self)
#and
self.win01.show()
and in class win02
Replace:
self.win02 = t01.win01()
#and
self.button.clicked.connect(self.win01.func)
with:
self.win02 = self.parent()
#and
self.button.clicked.connect(self.win02.func)

PyQt: how to load multiple .ui Files from Qt Designer

I want to add startup window that when I click button, it will open another window and close current window. For each window, it has seperated UI which created from Qt Designer in .ui form.
I load both .ui file via uic.loadUiType(). The first window(first UI) can normally show its UI but when I click button to go to another window, another UI (second UI) doesn't work. It likes open blank window.
Another problem is if I load first UI and then change to second UI (delete that Class and change to another Class, also delete uic.loadUiType()), the second UI still doesn't work (show blank window)
Please help... I research before create this question but can't find the answer.
Here's my code. How can I fix it?
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QIcon
from PyQt5 import uic
#load both ui file
uifile_1 = 'UI/openPage.ui'
form_1, base_1 = uic.loadUiType(uifile_1)
uifile_2 = 'UI/mainPage.ui'
form_2, base_2 = uic.loadUiType(uifile_2)
class Example(base_1, form_1):
def __init__(self):
super(base_1,self).__init__()
self.setupUi(self)
self.startButton.clicked.connect(self.change)
def change(self):
self.main = MainPage()
self.main.show()
class MainPage(base_2, form_2):
def __int__(self):
super(base_2, self).__init__()
self.setupUi(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
First you have an error, you must change __int__ to __init__. To close the window call the close() method.
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QIcon
from PyQt5 import uic
#load both ui file
uifile_1 = 'UI/openPage.ui'
form_1, base_1 = uic.loadUiType(uifile_1)
uifile_2 = 'UI/mainPage.ui'
form_2, base_2 = uic.loadUiType(uifile_2)
class Example(base_1, form_1):
def __init__(self):
super(base_1,self).__init__()
self.setupUi(self)
self.startButton.clicked.connect(self.change)
def change(self):
self.main = MainPage()
self.main.show()
self.close()
class MainPage(base_2, form_2):
def __init__(self):
super(base_2, self).__init__()
self.setupUi(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())

PyQt signal between QObjects

I'm trying to make a view and controller in PyQt where the view is emitting a custom signal when a button is clicked, and the controller has one of its methods connected to the emitted signal. It does not work, however. The respond method is not called when I click the button. Any idea what I did wrong ?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import QPushButton, QVBoxLayout, QDialog, QApplication
class TestView(QDialog):
def __init__(self, parent=None):
super(TestView, self).__init__(parent)
self.button = QPushButton('Click')
layout = QVBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
self.connect(self.button, SIGNAL('clicked()'), self.buttonClicked)
def buttonClicked(self):
self.emit(SIGNAL('request'))
class TestController(QObject):
def __init__(self, view):
self.view = view
self.connect(self.view, SIGNAL('request'), self.respond)
def respond(self):
print 'respond'
app = QApplication(sys.argv)
dialog = TestView()
controller = TestController(dialog)
dialog.show()
app.exec_()
Works for me - might be the version of Qt/PyQt you're using, but there are a couple things you can try:
Use a proper method syntax - so SIGNAL('request()') vs. SIGNAL('request')
Use new-style signal syntax
The style you are using is the old-style PyQt syntax and the new-style signal/slot definition is recommended:
import sys
from PyQt4.QtCore import QObject, pyqtSignal # really shouldn't import * here...QtCore library is quite large
from PyQt4.QtGui import QPushButton, QVBoxLayout, QDialog, QApplication
class TestView(QDialog):
request = pyqtSignal()
def __init__(self, parent=None):
super(TestView, self).__init__(parent)
self.button = QPushButton('Click')
layout = QVBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
self.button.clicked.connect(self.buttonClicked)
def buttonClicked(self):
self.request.emit()
class TestController(QObject):
def __init__(self, view):
super(QObject, self).__init__()
self.view = view
self.view.request.connect(self.respond)
def respond(self):
print 'respond'
app = QApplication(sys.argv)
dialog = TestView()
controller = TestController(dialog)
dialog.show()
app.exec_()
Again tho, I would really, really discourage building your code this way...you are creating a lot of unnecessary work and duplication of objects when you don't need to.

Categories

Resources