Troubleshooting Crash within PyQt5 Application (QDialog -> QMainWindow) - python

I am currently trying to troubleshoot an issue I am running into with a Qt application I am developing in Python. I only have a simple Main Window and a modal QDialog setup to collect user input after triggering a menu action on the Main Window. The QDialog is behaving as expected, since I've been able to confirm the test print is executed upon the QDialog returning Accepted, but the application will completely crash, without an error message, following the print statement. The same behavior occurs when QDialog returns Rejected. To clarify, the Main Window is shown for a couple seconds and the application crashes with no error messages. I would expect the function to return focus to the Main Window (with it still being open to operate on), after receiving either result from the Process function below.
I have also tried making the QDialog modeless (using show) and the accept/reject functions from the QDialog seem to return back to the main window as expected, but calling the function that brings up the QDialog again crashes the application. I am using pyqt 5.9 for this project, but I have used an essentially identical setup to the code below in a number of other projects in pyqt 5.6 without this happening. I am trying to figure out if there are any known issues with pyqt 5.9 that could be contributing to this issue, or if I have an error in my code somewhere that is causing this crash to occur. I was considering rolling back to 5.6 to see if that would get rid of the issue, but I feel like there may be something I am misunderstanding that is causing this to occur.
I am running the code out of Anaconda prompt (Anaconda 4.8.3, Python 3.7) on Windows 10, since Qt applications still have issues being run repeatedly within Spyder. I am using the pyqt that comes with Anaconda and haven't done any additional pip installs of pyqt.
Main Window Code
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout, QAction, QLabel
from PyQt5.QtWidgets import QDialog, QListWidget, QListWidgetItem, QAbstractItemView, QPushButton, QLineEdit, QSpacerItem, QSizePolicy
class FirstWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.title = 'Window 1'
self.left = 350
self.top = 150
self.width = 800
self.height = 500
self.setWindowTitle(self.title)
self.setGeometry(self.left,self.top,self.width,self.height)
widget = QWidget()
self.setCentralWidget(widget)
grid = QGridLayout()
widget.setLayout(grid)
mainMenu = self.menuBar()
mainMenu.setNativeMenuBar(False)
subMenu = mainMenu.addMenu('File')
modifyDB = QAction('Test',self)
subMenu.addAction(modifyDB)
modifyDB.triggered.connect(self.Process)
self.statusBar().showMessage('Ready')
def Process(self):
dialog = DialogWin()
if dialog.exec_() == QDialog.Accepted:
print('test passed')
if __name__ == '__main__':
if not QApplication.instance():
app = QApplication(sys.argv)
else:
app = QApplication.instance()
mainWin = FirstWindow()
mainWin.show()
app.exec_()
Dialog Code
class DialogWin(QDialog):
def __init__(self,parent=None):
super(DialogWin,self).__init__(parent)
self.title = 'Database Table Name Selection'
self.left = 350
self.top = 150
self.width = 300
self.height = 300
self.setWindowTitle(self.title)
self.setGeometry(self.left,self.top,self.width,self.height)
vbox = QVBoxLayout()
spacer = QSpacerItem(10,10,QSizePolicy.Expanding,QSizePolicy.Expanding)
self.list_exp = QLabel('Select Existing Table to Overwrite')
vbox.addWidget(self.list_exp)
self.table_list = QListWidget()
self.table_list.setSelectionMode(QAbstractItemView.SingleSelection)
vbox.addWidget(self.table_list)
hbox = QHBoxLayout()
hbox.addItem(spacer)
self.option_exp = QLabel('<span style="font-size:8pt; font-weight:600; color:#aa0000;">OR</span>')
hbox.addWidget(self.option_exp)
hbox.addItem(spacer)
vbox.addLayout(hbox)
self.new_name = QLineEdit(placeholderText='Enter New Source Name')
vbox.addWidget(self.new_name)
hbox = QHBoxLayout()
self.okButton = QPushButton('OK')
hbox.addWidget(self.okButton)
self.okButton.clicked.connect(self.accept)
self.cancelButton = QPushButton('Cancel')
hbox.addWidget(self.cancelButton)
self.cancelButton.clicked.connect(self.reject)
vbox.addLayout(hbox)
self.setLayout(vbox)
item = QListWidgetItem('No Tables Available...')
item.setFlags(item.flags() ^ Qt.ItemIsSelectable)
self.table_list.addItem(item)
Any input on this issue would be tremendously helpful. I've spent a lot of hours trying to understand how this setup could be working for me in one application and not in another.

Explanation:
The problem is that you are using the same QSpacerItem 2 times, and when the QDialog is closed as it is a local variable it will be deleted, and Qt will also eliminate the internal objects, and in that case the QSpacerItem will have a double elimination that causes the "Segmentation fault".
Solution:
You must create 2 QSpacerItem:
# ...
hbox = QHBoxLayout()
hbox.addItem(spacer)
self.option_exp = QLabel('OR')
hbox.addWidget(self.option_exp)
hbox.addItem(QSpacerItem(10,10,QSizePolicy.Expanding,QSizePolicy.Expanding))
vbox.addLayout(hbox)
# ...
Another alternative is not to use QSpacerItem but to set a stretch factor:
# ...
hbox = QHBoxLayout()
hbox.addStretch()
self.option_exp = QLabel('OR')
hbox.addWidget(self.option_exp)
hbox.addStretch()
vbox.addLayout(hbox)
# ...
Or don't use QHBoxLayout and set the QLabel directly to the QVBoxLayout by setting the alignment:
# ...
self.table_list = QListWidget()
self.table_list.setSelectionMode(QAbstractItemView.SingleSelection)
vbox.addWidget(self.table_list)
self.option_exp = QLabel('OR')
vbox.addWidget(self.option_exp, alignment=Qt.AlignHCenter)
self.new_name = QLineEdit(placeholderText='Enter New Source Name')
vbox.addWidget(self.new_name)
# ...

Related

Stretch stopped working after parenting widget to layout pyqt

I am here with pyqt question again.
I think, this question was probably answered somewhere, but I can't phrase my problem properly in a way to find it in google, so sorry if it's duplicated the question.
I wrote simple code to show the problem I am facing.
import sys
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QApplication, QMenu, QWidget, QPushButton
class MainWindow(QDialog):
def __init__(self):
QDialog.__init__(self)
self.mainLayout = QVBoxLayout()
self.setLayout(self.mainLayout)
self.create()
def create(self):
btn_widget_a = QWidget()
btn_layout_a = QHBoxLayout(btn_widget_a)
self.mainLayout.addWidget(btn_widget_a)
btn_a = QPushButton()
btn_layout_a.addWidget(btn_a)
btn_a.setFixedSize(60, 60)
btn_b = QPushButton()
btn_layout_a.addWidget(btn_b)
btn_b.setFixedSize(60, 60)
tool_widget = QWidget()
btn_layout_a.addWidget(tool_widget)
tool_menu= QMenu(tool_widget)
tool_menu.addMenu('Options')
btn_a.setMenu(tool_menu)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec_()
Problem is this btn_layout_a.addWidget(tool_widget) when I add QMenu inside the widget in my layout, buttons don't stretch anymore when you resize your UI. Is there a way to fix it? I know that if simply delete btn_layout_a.addWidget(tool_widget) it will work properly, but thing is that unless I put it in a widget, QMenu doesn't work properly inside the software called Maya. I tried adding stretch as an argument to widget/layout and tried command addStrerch() but nothing seems to give any result. Any suggestions? Thank you!

PyQt5 QCompleter for QLineEdit crashing with no exception visible

I'm trying to use the QCompleter class for the QLineEdit to give auto completion suggestions while typing, and update the suggestions after the user enters a new text. But when I try to update the Completer with text that starts with something that is already at the completer list, it just crashes the entire app with no visible exception! Even try-except doesn't catch this error, and I can't understand what I am doing wrong...
Below is a simpler example of my code: it is a simple "echo" console application that gets commands from QLineEdit (input text box) and writing it to QTextBrowser (output text box). When entering entirely new "command" (text) it works fine, and being added to the completer, so next time I can see it. But if the new text starts similar to other words in the completer list, choosing it crashes the entire GUI app with no exception visible, not even when I'm running in debug mode...
Please see my example below, and try writing at the upper text box options like: a, aa, aaa (that start similar to completer already word: aaa1)
What am I doing wrong??
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLineEdit, QTextBrowser, QCompleter
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('console')
self.setGeometry(10, 50, 500, 800)
# Create text box for input
self.consoleCommandLineEdit = QLineEdit(self)
self.consoleCommandLineEdit.setFixedHeight(25)
self.consoleCommandLineEdit.editingFinished.connect(self.gotConsoleCommand)
self.completerCommands = ['aaa1','aaa2','aaa3'] # initial completer list
completer = QCompleter(self.completerCommands)
self.consoleCommandLineEdit.setCompleter(completer)
# Create text box for output
self.consoleViewer = QTextBrowser(self)
self.consoleViewer.setLineWrapMode(QTextBrowser.NoWrap)
widget = QWidget(self)
self.setCentralWidget(widget)
self.vlay = QVBoxLayout(widget)
self.vlay.addWidget(self.consoleCommandLineEdit)
self.vlay.addWidget(self.consoleViewer)
def gotConsoleCommand(self):
cmd = self.consoleCommandLineEdit.text()
self.consoleCommandLineEdit.setText('')
self.sendCommandToConsole(cmd)
def sendCommandToConsole(self,cmd):
self.consoleViewer.append(cmd) # add cmd to output box
if cmd not in self.completerCommands: # if the command is new, add it to the completer
self.completerCommands.append(cmd) # 1. add the new text to the list we have
completer = QCompleter(self.completerCommands) # 2. create a new completer object
self.consoleCommandLineEdit.setCompleter(completer) # 3. set the new completer as the LineEdit completer
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
I have not yet found the cause of the problem but a better solution than creating a new QCompleter every time you need to add a new text. In this case it is better to use a model to store the texts that is the basis of the QCompleter information.
import sys
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import (
QApplication,
QWidget,
QMainWindow,
QVBoxLayout,
QLineEdit,
QTextBrowser,
QCompleter,
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setWindowTitle("console")
self.setGeometry(10, 50, 500, 800)
# Create text box for input
self.consoleCommandLineEdit = QLineEdit()
self.consoleCommandLineEdit.setFixedHeight(25)
self.consoleCommandLineEdit.editingFinished.connect(self.gotConsoleCommand)
self.model = QStandardItemModel()
self.model.appendRow([QStandardItem(text) for text in ("aaa1", "aaa2", "aaa3")])
completer = QCompleter(self.model, self)
self.consoleCommandLineEdit.setCompleter(completer)
# Create text box for output
self.consoleViewer = QTextBrowser(lineWrapMode=QTextBrowser.NoWrap)
widget = QWidget()
self.setCentralWidget(widget)
vlay = QVBoxLayout(widget)
vlay.addWidget(self.consoleCommandLineEdit)
vlay.addWidget(self.consoleViewer)
def gotConsoleCommand(self):
cmd = self.consoleCommandLineEdit.text()
self.consoleCommandLineEdit.clear()
self.sendCommandToConsole(cmd)
def sendCommandToConsole(self, cmd):
self.consoleViewer.append(cmd) # add cmd to output box
if not self.model.findItems(cmd):
self.model.appendRow(QStandardItem(cmd))
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
Perhaps something that helps us understand the problem is that if you add a parent to QCompleter the problem does not happen: completer = QCompleter(self.completerCommands, self)
but I stress: the best solution is to use a model.

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_())

Python3 PyQt5 setEnabled of a QAction causing crashes

For a project I'm creating a GUI using Python 3 and PyQt5. Because it has to be usable by people outside of my immediate team, I want to disable actions on the menu until they've already filled out some forms in other parts of the program (e.g. disabling the final solution view when they haven't set up the initial data connection). The issue is that when I try to call the QAction's setEnabled function outside of the function that created it (but still inside the overall class), it's causing my script to crash with no error code, so I'm having trouble understanding the issue. In the snipit below, I'm trying to set the "View Solution" menu option as true. There are some more options in that menu, but I deleted them here to make it more easy to read.
The code is structured something like this:
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QMessageBox, QStackedLayout
class MediaPlanner(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# Menu bar example from: zetcode.com/gui/pyqt5/
exitAction = QAction('&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
newProject = QAction('&New Project', self)
newProject.setShortcut('Ctrl+N')
newProject.setStatusTip('Start A New Project')
newProject.triggered.connect(self.createNewProject)
openProject = QAction('&Open Project',self)
openProject.setShortcut('Ctrl+O')
openProject.setStatusTip('Open A Saved Project')
openProject.setEnabled(False)
viewSolution = QAction('&View Solution',self)
viewSolution.setStatusTip('View the Current Solution (If Built)')
viewSolution.setEnabled(False)
self.statusBar()
menubar = self.menuBar()
filemenu = menubar.addMenu('&File')
filemenu.addAction(newProject)
filemenu.addAction(openProject)
filemenu.addAction(exitAction)
viewmenu = menubar.addMenu('&View')
viewmenu.addAction(viewSolution)
self.setGeometry(300,300,700,300)
self.setWindowTitle('Menubar')
self.show()
def createNewProject(self):
print('Project Created')
self.viewSolution.setEnabled(True)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MediaPlanner()
sys.exit(app.exec_())
The problem is that viewSolution is a variable, but it is not a member of the class so you will not be able to access it through the self instance. One possible solution is to make viewSolution member of the class as shown below:
self.viewSolution = QAction('&View Solution',self)
self.viewSolution.setStatusTip('View the Current Solution (If Built)')
self.viewSolution.setEnabled(False)
...
viewmenu.addAction(self.viewSolution)
Another possible solution is to use the sender() function, this function returns the object that emits the signal, using the following:
def createNewProject(self):
print('Project Created')
self.sender().setEnabled(True)

Missing menuBar in PyQt5

I've been developing a GUI using PyQt5 and wanted to include a menu bar. When I went to code this feature, however, my menu wouldn't appear. Figuring my understanding on how to implement menu bars in PyQt5 was off, I looked for a pre-existing example online. With some tweaking I developed the following test case:
import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenuBar, QAction, qApp
class Example(QMainWindow):
def __init__(self):
super().__init__()
exitAction = QAction(QIcon('exit.png'), '&Exit', self)
exitAction.triggered.connect(qApp.quit)
menubar = self.menuBar()
fileMenu = menubar.addMenu('&Testmenu')
fileMenu.addAction(exitAction)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
When I run this, however, Testmenu is nowhere to be found.
I have also tried creating the menu bar (and the rest of my GUI layout) in QTCreator before converting the .ui file to an importable .py using pyuic5. I thought this would eliminate some programming mistake on my end, but the menubar still won't show. Any thoughts?
Edit:
Im running this code using Python 3.5 (Anaconda 4.1) from within a Jupyter notebook, version 4.1. I'm also using a Macbook running os 10.1l, PyQt 5.7 and Qt version 5.7.0.
I've realized that the menu bar will become responsive if I click off the application window and then click back onto the window - effectively unfocusing and the focusing the application. Armed with this information I realized that I am not the first to notice this problem (see https://github.com/robotology/yarp/issues/457). Unfortunately, I'm still not sure how to resolve the issue.
It's not a Qt and PyQt5 Bug.
I think your code is zetcode pyqt5 menubar tutorial. I experienced the exact same problem on Mac OS.
First solution is a trick. Use ' &Exit' instead of '&Exit'. Insert a space at the beginning of '&Exit' like this:
...
# exitAction = QAction(QIcon('exit.png'), '&Exit', self) # Not shown
exitAction = QAction(QIcon('exit.png'), ' &Exit', self)
...
The system-wide menubar of macOS reserves keywords such as "Exit", "Quit", and etc. For the same reason, yurisnm's example code shows only the menu items except "Quit" on Mac OS. Actually "Quit" has TextHeuristicRole, so overrides "Quit " behavior in the Application menu. When you click "Quit python" in "Python" menu, it does not quit and just print "quit triggered".
If you must use that name in other menu(e.g. File, Edit), you need to change the action name like above or use QAction::setMenuRole(...) like this:
...
exitAction = QAction(QIcon('exit.png'), '&Exit', self)
print(exitAction.menuRole()) # It prints "1". QAction::TextHeuristicRole
exitAction.setMenuRole(QAction.NoRole)
...
Please read the following, it will be help you.
https://stackoverflow.com/a/11387977/5362866
http://doc.qt.io/qt-5/qmenubar.html#qmenubar-as-a-global-menu-bar
Menubar isn't visible in PyQt5
bar = self.menuBar()
bar.setNativeMenuBar(False)
file = bar.addMenu("File")
file.addAction("New")
NativeMenuBar property specifies whether or not the menubar should be used as a native menubar on platforms that support it. If this property is true, the menubar is used in the native menubar and is not in the window of its parent, if false the menubar remains in the window.
Sample program
import sys
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
from PyQt5.QtGui import QIcon
class Menu(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
exitAct = QAction(QIcon('exit.png'), ' &Quit', self)
exitAct.setShortcut('Ctrl+Q')
exitAct.setStatusTip('Exit application')
exitAct.triggered.connect(qApp.quit)
self.statusBar()
menubar = self.menuBar()
menubar.setNativeMenuBar(False)
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAct)
bar = self.menuBar()
file = bar.addMenu("Edit")
file.addAction("New")
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Simple menu')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Menu()
sys.exit(app.exec_())
If your program is running on Ubuntu, you may find your menu bar on the top of the screen.
If you want to move menu bar to window's title bar, you can toggle the setting at "System setting / Appearance / Behavior / Show the menus for a window / In the window's title bar".
try this:
menubar = QMenuBar()
self.setMenuBar(menubar)
instead of menubar = self.menuBar()
Have you tried the most simple example in the following link tutorialspoint.
Here is the most simple example.
import sys
from PyQt5.QtWidgets import QHBoxLayout, QAction, QApplication, QMainWindow
class menudemo(QMainWindow):
def __init__(self, parent = None):
super(menudemo, self).__init__(parent)
bar = self.menuBar()
file = bar.addMenu("File")
file.addAction("New")
save = QAction("Save",self)
save.setShortcut("Ctrl+S")
file.addAction(save)
edit = file.addMenu("Edit")
edit.addAction("copy")
edit.addAction("paste")
quit = QAction("Quit",self)
file.addAction(quit)
file.triggered[QAction].connect(self.processtrigger)
self.setWindowTitle("menu demo")
def processtrigger(self, q):
print(q.text()+" is triggered")
def main():
app = QApplication(sys.argv)
ex = menudemo()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Under MacOS, an application menu appears at the top of the screen. I managed to get the Quit menu option to appear in the above example by prepending the string "Quit" with a null character as follows:
close = QAction("\0Quit",self)
close.setShortcut("Ctrl+Q")
file.addAction(close)
It seems macOS somehow intercepts the menu items called "Quit" or "Exit".
You can happily use "Close" or "Leave" without the workaround.

Categories

Resources