I'm having a strange issue that I can't figure out.
When I click a button, I want it to update it's own text, but in some cases it's as if it's being blocked!
Excuse my rubbish coding - my first time trying to make a GUI for something. Anyways, this is my buttons function
def ConvertToSTL(self):
if DEBUG:
print("You Clicked on ConvertToSTL")
self.btnConvertToSTL.setText("Processing...")
self.btnConvertToSTL.setEnabled(False)
if not (FILE_OUTLINE and FILE_OTHER):
print("You MUST select BOTH files!")
self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)
self.btnConvertToSTL.setEnabled(True)
else:
if DEBUG:
print("Outline: " + FILE_OUTLINE)
if self.inputPCB.isChecked():
print("Mask Top: " + FILE_OTHER)
elif self.inputSolderStencil.isChecked():
print("Paste Mask Top: " + FILE_OTHER)
# Processing Files!
outline_file = open(FILE_OUTLINE, "r")
other_file = open(FILE_OTHER, "r")
outline = gerber.loads(outline_file.read())
other = gerber.loads(other_file.read())
# outline=None
if outline and other:
output = process_gerber(
outline,
other,
self.inputThickness.value(),
self.inputIncludeLedge.isChecked,
self.inputHeight.value(),
self.inputGap.value(),
self.inputIncreaseHoleSize.value(),
self.inputReplaceRegions.isChecked(),
self.inputFlip.isChecked(),
)
file_id = randint(1000000000, 9999999999)
scad_filename = "./gerbertostl-{}.scad".format(file_id)
stl_filename = "./gerbertostl-{}.stl".format(file_id)
with open(scad_filename, "w") as scad_file:
scad_file.write(output)
p = subprocess.Popen(
[
SCAD_BINARY,
"-o",
stl_filename,
scad_filename,
]
)
p.wait()
if p.returncode:
print("Failed to create an STL file from inputs")
else:
with open(stl_filename, "r") as stl_file:
stl_data = stl_file.read()
os.remove(stl_filename)
# Clean up temporary files
os.remove(scad_filename)
self.btnConvertToSTL.setText("Saving file...")
saveFilename = QFileDialog.getSaveFileName(None, "Save STL", stl_filename, "STL File (*.stl)")[0]
if DEBUG:
print("File saved to: " + saveFilename)
# needs a handler if user clicks cancel!
saveFile = open(saveFilename,'w')
saveFile.write(stl_data)
saveFile.close()
self.btnConvertToSTL.setEnabled(True)
self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)
Now, if outline or other is FALSE for any reason - to test I manually added set outline=None then my Button DOES correctly set it's text to read "Processing...". The problem however is that if outline and other are both TRUE so the functions progresses, the button text does NOT get set to "Processing".
Instead, the button text is not changed until it reaches self.btnConvertToSTL.setText("Saving file...") which as expected sets the text correctly. Then, once the file is saved, the button again correctly updates again to the variable CONVERT_BTN_TEXT
So my question is, why on earth does the initial "Processing..." text NOT get set correctly? I don't understand
Edit: minimal reproducible example. In this case, the button text never changes to "Processing..." for me. Obviously, Requires PYQT5
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'tmp.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
import time
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(160, 160, 421, 191))
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.PushButtonClicked)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 24))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
def PushButtonClicked(self):
print("Button Clicked")
self.pushButton.setText("Processing")
self.pushButton.setEnabled(False)
if True and True:
print("Sleep")
time.sleep(5)
print("Continue")
self.pushButton.setText("DONE")
self.pushButton.setEnabled(True)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
The button will correctly say "DONE" after 5 seconds, but it never changes to "Processing"
The issue is because the code you are invoking once the button is pressed is locking the GUI, so when it is finally released back to the main event loop it processes all of the changes at once which is why you only see the final message displayed in the button description. That is also why you can't move around the window or interact with it in any other way while the method is processing.
The solution to this is to simply not write code that freezes the GUI. This means either breaking up the process or running it in a separate thread, and using Qt's signals and slots API.
For example:
from PyQt5 import QtCore, QtWidgets
import time
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("MainWindow")
self.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(self)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton("Button", self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(160, 160, 421, 191))
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.PushButtonClicked)
self.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(self)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 24))
self.menubar.setObjectName("menubar")
self.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(self)
self.statusbar.setObjectName("statusbar")
self.setStatusBar(self.statusbar)
def PushButtonClicked(self):
self.thread = Thread(self)
self.thread.started.connect(self.show_processing)
self.thread.finished.connect(self.show_done)
self.thread.start()
def show_done(self):
self.pushButton.setText("DONE")
self.pushButton.setEnabled(True)
def show_processing(self):
self.pushButton.setText("Processing")
self.pushButton.setEnabled(False)
class Thread(QtCore.QThread):
def run(self):
print("Button Clicked")
if True and True:
print("Sleep")
time.sleep(5)
print("Continue")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
So for your specific use case, it might look something like this:
...
...
...
...
...
def convert_to_STL(self):
if DEBUG:
print("You Clicked on ConvertToSTL")
self.btnConvertToSTL.setText("Processing...")
self.btnConvertToSTL.setEnabled(False)
if not (FILE_OUTLINE and FILE_OTHER):
print("You MUST select BOTH files!")
self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)
self.btnConvertToSTL.setEnabled(True)
else:
self.thread = Thread(self)
self.thread.finished.connect(self.show_done)
self.thread.start()
def show_done(self):
self.btnConvertToSTL.setEnabled(True)
self.btnConvertToSTL.setText(CONVERT_BTN_TEXT)
class Thread(QtCore.QThread):
def __init__(self, parent):
self.window = parent
def run(self):
if DEBUG:
print("Outline: " + FILE_OUTLINE)
if self.window.inputPCB.isChecked():
print("Mask Top: " + FILE_OTHER)
elif self.window.inputSolderStencil.isChecked():
print("Paste Mask Top: " + FILE_OTHER)
# Processing Files!
outline_file = open(FILE_OUTLINE, "r")
other_file = open(FILE_OTHER, "r")
outline = gerber.loads(outline_file.read())
other = gerber.loads(other_file.read())
# outline=None
if outline and other:
output = process_gerber(
outline,
other,
self.window.inputThickness.value(),
self.window.inputIncludeLedge.isChecked,
self.window.inputHeight.value(),
self.window.inputGap.value(),
self.window.inputIncreaseHoleSize.value(),
self.window.inputReplaceRegions.isChecked(),
self.window.inputFlip.isChecked(),
)
file_id = randint(1000000000, 9999999999)
scad_filename = "./gerbertostl-{}.scad".format(file_id)
stl_filename = "./gerbertostl-{}.stl".format(file_id)
with open(scad_filename, "w") as scad_file:
scad_file.write(output)
p = subprocess.Popen(
[
SCAD_BINARY,
"-o",
stl_filename,
scad_filename,
]
)
p.wait()
if p.returncode:
print("Failed to create an STL file from inputs")
else:
with open(stl_filename, "r") as stl_file:
stl_data = stl_file.read()
os.remove(stl_filename)
# Clean up temporary files
os.remove(scad_filename)
saveFilename = QFileDialog.getSaveFileName(None, "Save STL", stl_filename, "STL File (*.stl)")[0]
if DEBUG:
print("File saved to: " + saveFilename)
# needs a handler if user clicks cancel!
saveFile = open(saveFilename,'w')
saveFile.write(stl_data)
saveFile.close()
P.S. You shouldn't edit UIC files.
Related
I am taking a programming course now, CSE 111 programming with functions, and my last assignment is to find an interesting code and write some test functions, to test the correct working of the code. This is the code:
#Imported modules
import sys
import wave, contextlib, math, time
import speech_recognition as sr
from moviepy.editor import AudioFileClip
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal
class Ui_MainWindow(object):
"""Main window GUI."""
def __init__(self):
"""Initialisation function."""
self.mp4_file_name = ""
self.output_file = ""
self.audio_file = "speech.wav"
def setupUi(self, MainWindow):
"""Define visual components and positions."""
# Main window
MainWindow.setObjectName("MainWindow")
MainWindow.resize(653, 836)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(50, 20, 161, 41))
# Selected video file label
font = QtGui.QFont()
font.setPointSize(14)
self.label.setFont(font)
self.label.setObjectName("label")
self.selected_video_label = QtWidgets.QLabel(self.centralwidget)
self.selected_video_label.setGeometry(QtCore.QRect(230, 20, 371, 41))
font = QtGui.QFont()
font.setPointSize(8)
self.selected_video_label.setFont(font)
self.selected_video_label.setFrameShape(QtWidgets.QFrame.Box)
self.selected_video_label.setText("")
self.selected_video_label.setObjectName("selected_video_label")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(50, 90, 161, 41))
# Transcribed text box
font = QtGui.QFont()
font.setPointSize(14)
self.label_3.setFont(font)
self.label_3.setObjectName("label_3")
self.transcribed_text = QtWidgets.QTextBrowser(self.centralwidget)
self.transcribed_text.setGeometry(QtCore.QRect(230, 320, 381, 431))
self.transcribed_text.setObjectName("transcribed_text")
self.label_5 = QtWidgets.QLabel(self.centralwidget)
self.label_5.setGeometry(QtCore.QRect(230, 280, 161, 41))
font = QtGui.QFont()
font.setPointSize(14)
self.label_5.setFont(font)
self.label_5.setObjectName("label_5")
self.transcribe_button = QtWidgets.QPushButton(self.centralwidget)
self.transcribe_button.setEnabled(False)
self.transcribe_button.setGeometry(QtCore.QRect(230, 150, 221, 81))
# Transcribe button
font = QtGui.QFont()
font.setPointSize(14)
self.transcribe_button.setFont(font)
self.transcribe_button.setObjectName("transcribe_button")
self.transcribe_button.clicked.connect(self.process_and_transcribe_audio)
# progeress bar
self.progress_bar = QtWidgets.QProgressBar(self.centralwidget)
self.progress_bar.setGeometry(QtCore.QRect(230, 250, 381, 23))
self.progress_bar.setProperty("value", 0)
self.progress_bar.setObjectName("progress_bar")
self.message_label = QtWidgets.QLabel(self.centralwidget)
self.message_label.setGeometry(QtCore.QRect(0, 760, 651, 21))
# Message label (for errors and warnings)
font = QtGui.QFont()
font.setPointSize(8)
self.message_label.setFont(font)
self.message_label.setFrameShape(QtWidgets.QFrame.Box)
self.message_label.setText("")
self.message_label.setObjectName("message_label")
self.output_file_name = QtWidgets.QPlainTextEdit(self.centralwidget)
self.output_file_name.setGeometry(QtCore.QRect(230, 90, 371, 41))
# Output file name
font = QtGui.QFont()
font.setPointSize(14)
self.output_file_name.setFont(font)
self.output_file_name.setObjectName("output_file_name")
# Menubar options
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 653, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
self.menuAbout = QtWidgets.QMenu(self.menubar)
self.menuAbout.setObjectName("menuAbout")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.actionOpen_mp4_video_recording = QtWidgets.QAction(MainWindow)
self.actionOpen_mp4_video_recording.setObjectName("actionOpen_mp4_video_recording")
self.actionOpen_mp4_video_recording.triggered.connect(self.open_audio_file)
self.actionAbout_vid2text = QtWidgets.QAction(MainWindow)
self.actionAbout_vid2text.setObjectName("actionAbout_vid2text")
self.actionAbout_vid2text.triggered.connect(self.show_about)
self.actionNew = QtWidgets.QAction(MainWindow)
self.actionNew.setObjectName("actionNew")
self.actionNew.triggered.connect(self.new_project)
self.menuFile.addAction(self.actionOpen_mp4_video_recording)
self.menuFile.addAction(self.actionNew)
self.menuAbout.addAction(self.actionAbout_vid2text)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuAbout.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
"""Translate UI method."""
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "Selected video file:"))
self.label_3.setText(_translate("MainWindow", "Output file name:"))
self.label_5.setText(_translate("MainWindow", "Transcribed text:"))
self.transcribe_button.setText(_translate("MainWindow", "Transcribe"))
self.output_file_name.setPlaceholderText(_translate("MainWindow", "interview1.txt"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.menuAbout.setTitle(_translate("MainWindow", "About"))
self.actionOpen_mp4_video_recording.setText(_translate("MainWindow", "Open mp4 video recording"))
self.actionAbout_vid2text.setText(_translate("MainWindow", "About video to speech"))
self.actionNew.setText(_translate("MainWindow", "New"))
def open_audio_file(self):
"""Open the audio (*.mp4) file."""
file_name = QFileDialog.getOpenFileName()
if file_name[0][-3:] == "mp4":
self.transcribe_button.setEnabled(True)
self.mp4_file_name = file_name[0]
self.selected_video_label.setText(file_name[0])
else:
self.message_label.setText("Please select an *.mp4 file")
def convert_mp4_to_wav(self):
"""Convert the mp4 video file into an audio file."""
self.message_label.setText("Converting mp4 to audio (*.wav)...")
self.convert_thread = convertVideoToAudioThread(self.mp4_file_name, self.audio_file)
self.convert_thread.finished.connect(self.finished_converting)
self.convert_thread.start()
def get_audio_duration(self, audio_file):
"""Determine the length of the audio file."""
with contextlib.closing(wave.open(audio_file,'r')) as f:
frames = f.getnframes()
rate = f.getframerate()
duration = frames / float(rate)
return duration
def transcribe_audio(self, audio_file):
"""Transcribe the audio file."""
total_duration = self.get_audio_duration(audio_file) / 10
total_duration = math.ceil(total_duration)
self.td = total_duration
if len(self.output_file_name.toPlainText()) > 0:
self.output_file = self.output_file_name.toPlainText()
else:
self.output_file = "my_speech_file.txt"
# Use thread to process in the background and avoid freezing the GUI
self.thread = transcriptionThread(total_duration, audio_file, self.output_file)
self.thread.finished.connect(self.finished_transcribing)
self.thread.change_value.connect(self.set_progress_value)
self.thread.start()
def finished_converting(self):
"""Reset message text when conversion is finished."""
self.message_label.setText("Transcribing file...")
self.transcribe_audio(self.audio_file)
def finished_transcribing(self):
"""This run when transcription finished to tidy up UI."""
self.progress_bar.setValue(100)
self.transcribe_button.setEnabled(True)
self.message_label.setText("")
self.update_text_output()
def set_progress_value(self, val):
"""Update progress bar value."""
increment = int(math.floor(100*(float(val)/self.td)))
self.progress_bar.setValue(increment)
def process_and_transcribe_audio(self):
"""Process the audio into a textual transcription."""
self.transcribe_button.setEnabled(False)
self.message_label.setText("Converting mp4 to audio (*.wav)...")
self.convert_mp4_to_wav()
def update_text_output(self):
"""Update the text box with the transcribed file."""
f = open(self.output_file, "r")
self.transcribed_text.setText(f.read())
f.close()
def new_project(self):
"""Clear existing fields of data."""
self.message_label.setText("")
self.transcribed_text.setText("")
self.selected_video_label.setText("")
self.output_file_name.document().setPlainText("")
self.progress_bar.setValue(0)
def show_about(self):
"""Show about message box."""
msg = QMessageBox()
msg.setWindowTitle("About video to Speech")
msg.setText("Nelson Petro - CSE 111")
msg.setIcon(QMessageBox.Information)
msg.exec_()
class convertVideoToAudioThread(QThread):
"""Thread to convert mp4 video file to wav file."""
def __init__(self, mp4_file_name, audio_file):
"""Initialization function."""
QThread.__init__(self)
self.mp4_file_name = mp4_file_name
self.audio_file = audio_file
def __del__(self):
"""Destructor."""
self.wait()
def run(self):
"""Run video conversion task."""
audio_clip = AudioFileClip(self.mp4_file_name)
audio_clip.write_audiofile(self.audio_file)
class transcriptionThread(QThread):
"""Thread to transcribe file from audio to text."""
change_value = pyqtSignal(int)
def __init__(self, total_duration, audio_file, output_file):
"""Initialization function."""
QThread.__init__(self)
self.total_duration = total_duration
self.audio_file = audio_file
self.output_file = output_file
def __del__(self):
"""Destructor."""
self.wait()
def run(self):
"""Run transcription, audio to text."""
r = sr.Recognizer()
for i in range(0, self.total_duration):
try:
with sr.AudioFile(self.audio_file) as source:
audio = r.record(source, offset=i*10, duration=10)
f = open(self.output_file, "a")
f.write(r.recognize_google(audio))
f.write(" ")
self.change_value.emit(i)
except:
print("Unknown word detected...")
continue
f.close()
def main():
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
And I have written these two test functions. The first one to test the default values of the MainWindow constructor and the second one to test the correct functionality of the
open_audio_file() function (using the directions provided in a previous question). I am not pretty familiar with OOP, so I would really appreciate if you can help me getting the second function working correct.
These are the two test functions:
def test_MainWindowConstructor():
ui = Ui_MainWindow()
assert ui.mp4_file_name == ""
assert ui.output_file == ""
assert ui.audio_file == "speech.wav"
def test_that_providing_a_raw_file_is_rejected():
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
ui.actionOpen_mp4_video_recording.triggered
QFileDialog.getOpenFileName = "video.raw"
ui.open_audio_file
assert ui.message_label.text == "Please select an *.mp4 file"
Thank you so much before hand.
You wrote:
ui.actionOpen_mp4_video_recording.triggered
...
ui.open_audio_file
As written, that doesn't make sense.
You probably want to invoke a function,
rather than evaluate an expression for
side effects and then discard it.
There's a big difference between fn and fn() --
the latter actually calls the function.
You are successfully verifying that the triggered
attribute exists, and that can be helpful in a unit test.
To verify, try some typo, such as "fliggered",
and note the AttributeError that elicits.
Similarly you are verifying the open_audio_file
attribute exists. That's a simpler situation so
it is probably less useful for a unit test to do that.
Better to call the function, and verify it behaves
as intended.
Try running your tests in this way:
$ pytest --cov --cov-report=term-missing
That will help you to notice that there's two
kinds of audio files you should test:
with & without an ".mp4" suffix.
Note that the target code sometimes mentions
a function without calling it, without
tacking on (). What's going on there?
We name that situation a "callback".
That is, the target code's API accepts
a pointer (ok, a reference) to a function.
It remembers the function, and subsequently calls
it at the appropriate time, after some UI event
has happened.
That syntax was likely confusing you -- it explains
the absence of () in the target code.
Academic integrity
You are clearly learning some things
about unit tests in this assignment,
and have commendable curiosity.
Be sure to include a reference to
this SO question when you hand in your work.
Most schools are happy with use of external
resources as long as you cite them.
I have a QComboBox, cbo_box, nested in a frame, self.ui.frameFilterControls, with: cbo_box.currentTextChanged.connect(self.choice_changed). In self.choice_changed(), I call a function which clears the frames layout, including cbo_box, and then regenerates the layout with appropriate controls (including a new cbo_box):
layout = self.ui.frameFilterControls.layout()
if layout is not None:
#clear layout
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)
The problem I am having is that when self.choice_changed() returns, it throws a SIGSEGV error:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
I am guessing because it has no-where to return to, but I am not sure. I am wondering if it is possible to instead of calling the function that deletes and recreates the controls in the on_change of cbo_box if I could schedule it to be evaluated at the next form update, or something along those lines?
EDIT:
I have produced a minimal example which reproduces the behavior.
from PySide6 import QtCore, QtWidgets
from PySide6.QtWidgets import (QApplication, QListWidgetItem, QGridLayout, QVBoxLayout, QLineEdit, QComboBox)
import sys
#from output of designer -> simple main form with a frame for a starting point
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(381, 357)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.frame = QtWidgets.QFrame(self.centralwidget)
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.pushButton = QtWidgets.QPushButton(self.frame)
self.pushButton.setObjectName("pushButton")
self.verticalLayout_2.addWidget(self.pushButton)
self.verticalLayout.addWidget(self.frame)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 381, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
class MainGUI(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainGUI, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.set_frame_controls()
def set_frame_controls(self):
layout = self.ui.frame.layout()
if layout is not None:
#clear layout
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)
cbo_box = QComboBox()
cbo_box.addItem("test 1")
cbo_box.addItem("test 2")
cbo_box.currentTextChanged.connect(self.choice_changed)
layout.addWidget(cbo_box)
def choice_changed(self):
print("choice_changed")
cbo_box = self.sender()
self.set_frame_controls()
print("end choice changed")
def main():
app = QApplication(sys.argv)
form = MainGUI()
form.show()
app.exit(app.exec_())
if __name__ == '__main__':
main()
The console output of the above program when I run it and switch the combo box from "test 1" to "test 2" is as follows:
choice_changed
end choice changed
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
The behavior I would like is for the frame to be regenerated entirely, without the error. Thanks!
If you want to remove a widget from a layout it is not enough to set a null parent since this does not remove it from the internal list that the layout has but only removes the c++ object causing the SIGSEGV. Instead you must use deleteLater() which if it ensures the correct elimination of the elements (for example the internal list of the layout).
def set_frame_controls(self):
layout = self.ui.frame.layout()
if layout is not None:
for i in reversed(range(layout.count())):
widget = layout.itemAt(i).widget()
if widget is not None:
widget.deleteLater()
cbo_box = QComboBox()
cbo_box.addItem("test 1")
cbo_box.addItem("test 2")
cbo_box.currentTextChanged.connect(self.choice_changed)
layout.addWidget(cbo_box)
I tried to design a very basic GUI app that shows the entered height on a dialog, but after I press the ‘OK’ button on the main window, the whole program crashes and the process finishes with this exit code:
Process finished with exit code -1073740791 (0xC0000409)
Here’s the full code for the app, the UI files are below:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.uic import *
class My_Dialog(QDialog):
def __init__(self):
super(My_Dialog, self).__init__()
loadUi("dialog.ui", self)
self.mid_label.setText(My_Window.mid_label_nexttext)
class My_Window(QMainWindow):
def __init__(self):
super(My_Window, self).__init__()
loadUi("mainwindow.ui", self)
self.mid_label_nexttext = None
self.height_selecter_spinbox.textChanged.connect(lambda x: self.spin_changed(x))
self.pushButton.clicked.connect(self.onMyPushButtonClick)
def onMyPushButtonClick(self):
dlg = My_Dialog()
if dlg.exec_():
print("Success!")
else:
print("Cancel!")
def spin_changed(self, s):
self.mid_label_nexttext = s
self.update()
def main():
app = QApplication(sys.argv)
window = My_Window()
window.show()
app.exec_()
if __name__ == "__main__":
main()
The main window’s UI:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(513, 171)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(310, 80, 75, 23))
self.pushButton.setObjectName("pushButton")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(90, 40, 209, 29))
self.layoutWidget.setObjectName("layoutWidget")
self.layout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setObjectName("layout")
self.label_firstpart = QtWidgets.QLabel(self.layoutWidget)
font = QtGui.QFont()
font.setPointSize(15)
self.label_firstpart.setFont(font)
self.label_firstpart.setObjectName("label_firstpart")
self.layout.addWidget(self.label_firstpart)
self.height_selecter_spinbox = QtWidgets.QSpinBox(self.layoutWidget)
font = QtGui.QFont()
font.setPointSize(13)
self.height_selecter_spinbox.setFont(font)
self.height_selecter_spinbox.setMinimum(100)
self.height_selecter_spinbox.setMaximum(250)
self.height_selecter_spinbox.setProperty("value", 175)
self.height_selecter_spinbox.setObjectName("height_selecter_spinbox")
self.layout.addWidget(self.height_selecter_spinbox)
self.label_lastpart = QtWidgets.QLabel(self.layoutWidget)
font = QtGui.QFont()
font.setPointSize(15)
self.label_lastpart.setFont(font)
self.label_lastpart.setObjectName("label_lastpart")
self.layout.addWidget(self.label_lastpart)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 513, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "OK"))
self.label_firstpart.setText(_translate("MainWindow", "My height is "))
self.label_lastpart.setText(_translate("MainWindow", "cm."))
The dialog’s UI:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
self.dialog_buttonbox = QtWidgets.QDialogButtonBox(Dialog)
self.dialog_buttonbox.setGeometry(QtCore.QRect(30, 240, 341, 32))
self.dialog_buttonbox.setOrientation(QtCore.Qt.Horizontal)
self.dialog_buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.dialog_buttonbox.setObjectName("dialog_buttonbox")
self.widget = QtWidgets.QWidget(Dialog)
self.widget.setGeometry(QtCore.QRect(80, 90, 151, 41))
self.widget.setObjectName("widget")
self.dialog_layout = QtWidgets.QHBoxLayout(self.widget)
self.dialog_layout.setContentsMargins(0, 0, 0, 0)
self.dialog_layout.setObjectName("dialog_layout")
self.left_label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.left_label.setFont(font)
self.left_label.setObjectName("left_label")
self.dialog_layout.addWidget(self.left_label)
self.mid_label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.mid_label.setFont(font)
self.mid_label.setObjectName("mid_label")
self.dialog_layout.addWidget(self.mid_label)
self.right_label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.right_label.setFont(font)
self.right_label.setObjectName("right_label")
self.dialog_layout.addWidget(self.right_label)
self.retranslateUi(Dialog)
self.dialog_buttonbox.accepted.connect(Dialog.accept)
self.dialog_buttonbox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.left_label.setText(_translate("Dialog", "You are"))
self.mid_label.setText(_translate("Dialog", "100"))
self.right_label.setText(_translate("Dialog", "cm tall."))
I would appreciate some help on fixing this error, and on why it occured.
You are trying to access an attribute that does not exist:
self.mid_label.setText(My_Window.mid_label_nexttext)
My_Window is a class, while mid_label_nexttext was assigned to the instance of that class (self is always the reference to the current instance).
If you want to set the text for that label from a "parent" window, you either add an extra argument to the __init__ that allows to get the text, or you set it from the main window.
Use the text as init argument
class My_Dialog(QDialog):
def __init__(self, text):
super(My_Dialog, self).__init__()
loadUi("dialog.ui", self)
# ensure that "text" is a valid string, you can't use setText(None)
if text:
self.mid_label.setText(text)
class My_Window(QMainWindow):
# ...
def onMyPushButtonClick(self):
dlg = My_Dialog(self.mid_label_nexttext)
# ...
Set the text from the parent
class My_Dialog(QDialog):
def __init__(self):
super(My_Dialog, self).__init__()
loadUi("dialog.ui", self)
# NO setText() here!!!
class My_Window(QMainWindow):
# ...
def onMyPushButtonClick(self):
dlg = My_Dialog()
if self.mid_label_nexttext:
dlg.mid_label.setText(self.mid_label_nexttext)
# ...
Note that the first method is usually better, mostly for modularity reasons: let's say that you create that dialog from different classes in different situations, whenever you need to change the object name of the label (or the whole interface structure) you can easily change its reference in the dialog subclass, otherwise you'll need to change every reference in your code.
Note: the call to self.update() is useless; if you want to update the label on the dialog whenever the spinbox value is changed, you need to directly access the label (like in the last example) or use signals. Also, you don't need to use lambda if you're using the same argument parameter, just connect to the function.
I am trying to let a user choose from a GUI a choice of procedures to be executed. Without the GUI section, i can execute the procedures and both appear at the same time on separate windows that i can run independently. Now i want the user to choose from a GUI which procedure to run first to avoid multiple windows open at the same time. While the procedures execute properly when i choose the second pushbutton i have this error message : RuntimeError: wrapped C/C++ object of type QPlainTextEdit has been deleted. I ve seen this issue around and tried what has been proposed but without success. Following sugestions from
[https://stackoverflow.com/questions/63302430/call-different-window-classes-into-a-main-window-in-pqt5-python], I tried to reduce the code as much as possible. Despite some specific packages utilized these are not responsible for the error observed and anyone should be able to run this code. the Mainwindow class is already using Qt for displaying a window but when used standalone i do not have any error. The error is inherent to the Ui_MainWindow(object) class, i also tried with item list the same issue occurs.The error appears each time i press Queue when choosing the Run 2 procedure.
import logging
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
import sys
import random
import pandas as pd
from pymeasure.log import console_log
from pymeasure.display.Qt import QtGui
from PyQt5 import QtCore, QtGui, QtWidgets
from pymeasure.display.windows import ManagedWindow
from pymeasure.experiment import Procedure, Results
from pymeasure.experiment import IntegerParameter, FloatParameter, Parameter
import tempfile
# this class uses the Pymeasure package to run and display experiments here it s just generating a random number. the iterations
#number being the input parameter on the window and one can queue several runs
# execute is the method generating the random number and Mainwindow is the dipsplay and the queue of the runs. the data are
# save in a temp file
class RandomProcedure_1(Procedure):
iterations = IntegerParameter('Loop Iterations', default=10)
DATA_COLUMNS = ['Iteration', 'Random Number']
#
def startup(self):
log.info("Setting the time of the random number generator")
random.seed(self.seed)
def execute(self):
log.info("Starting the loop of %d iterations" % self.iterations)
for i in range(self.iterations):
data = {
'Iteration': i,
'Random Number': random.randint(1, 50)
}
self.emit('results', data)
log.debug("Emitting results: %s" % data)
if self.should_stop():
log.warning("Caught the stop flag in the procedure")
break
class MainWindow_1(ManagedWindow):
# this define the window where the random number vs iteration plot is displayed
def __init__(self):
super(MainWindow_1, self).__init__(
procedure_class=RandomProcedure_1,
inputs=['iterations'],
displays=['iterations'],
x_axis='Iteration',
y_axis='Random Number',
)
self.setWindowTitle('RandomProcedure_1')
def queue(self, *, procedure=None):
if procedure is None:
procedure = self.make_procedure()
filename = tempfile.mktemp()
log.info("Constructing the Results with a data file: %s" % filename)
results = Results(procedure, filename)
experiment = self.new_experiment(results)
self.manager.queue(experiment)
# the GUI with 2 pushbuttons for each procedure (here the same duplicated for simplicity)
# i also tried with a list of item but the same error occurs
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(110, 160, 112, 34))
self.pushButton.setObjectName("EXP1")
self.pushButton.clicked.connect(self.startProcedure_1)
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(580, 170, 112, 34))
self.pushButton_2.setObjectName("EXP2")
self.pushButton_2.clicked.connect(self.startProcedure_1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def startProcedure_1(self):
self.window = MainWindow_1()
self.window.show()
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Procedures"))
self.pushButton.setText(_translate("MainWindow", "Run 1"))
self.pushButton_2.setText(_translate("MainWindow", "Run 2"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I have the following files (Main window/UI):
files
UI:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Sat Apr 23 15:53:12 2011
# by: PyQt4 UI code generator 4.7.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
class Ui_MainWindow(object):
### Presetting Model ###
model = QtGui.QFileSystemModel()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1000, 600)
self.centralWidget = QtGui.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralWidget)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.treeView = QtGui.QTreeView(self.centralWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(2)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.treeView.sizePolicy().hasHeightForWidth())
self.treeView.setSizePolicy(sizePolicy)
self.treeView.setHeaderHidden(True)
self.treeView.setObjectName("treeView")
self.horizontalLayout.addWidget(self.treeView)
self.plainTextEdit = QtGui.QPlainTextEdit(self.centralWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(4)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth())
self.plainTextEdit.setSizePolicy(sizePolicy)
self.plainTextEdit.setObjectName("plainTextEdit")
self.horizontalLayout.addWidget(self.plainTextEdit)
self.horizontalLayout_2.addLayout(self.horizontalLayout)
MainWindow.setCentralWidget(self.centralWidget)
### MENU ###
self.menuBar = QtGui.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 600, 27))
self.menuBar.setObjectName("menuBar")
MainWindow.setMenuBar(self.menuBar)
### Setting up tree view model ###
self.treeView.setModel(self.model)
self.treeView.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
# self.treeView.setRootIndex(self.model.setRootPath(Xmldocument.directorypath))
### Hiding additional info in treeview ###
hHeader = self.treeView.header()
hHeader.hideSection(1)
hHeader.hideSection(2)
hHeader.hideSection(3)
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "VeloCondDB Browser", None, QtGui.QApplication.UnicodeUTF8))
QtCore.QMetaObject.connectSlotsByName(MainWindow)
##############################################################
def __del__(self):
print "DESTRUCTOR"
Main Window:
import sys
import os
from browserclass_UI import Ui_MainWindow
from PyQt4 import QtCore, QtGui
class Browser(QtGui.QMainWindow):
#############################################################################################
def __init__(self, parent=None):
"""Constructor for the main window."""
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
menuitems = ["Open", "Close"]
menu = self.ui.menuBar.addMenu('File')
for item in menuitems:
entry = menu.addAction(item)
self.connect(entry, QtCore.SIGNAL('triggered()'), lambda item=item: self.doStuff(item))
#############################################################################################
def doStuff(self, item):
print item
#############################################################################################
if __name__ == "__main__":
browser = QtGui.QApplication(sys.argv)
myapp = Browser()
myapp.show()
sys.exit(browser.exec_())
When lines from:
menuitems...
to
self.connect...
are not commented, destructor from UI is never being called. If they are commented everything works fine. Any ideas?
You can't rely on __del__ being called for all of your objects, especially when your program is ending (see that other answer)
According to PyQt documentation:
However, if a slot is a lambda function or a partial function then
its reference count is automatically incremented to prevent it from
being immediately garbage collected.
Some circular references to your Browser object might be created when you connect the signal the lambda functions, and that is what keeps it from being destroyed. As ui is referenced by Browser, it doesn't get destroyed either.
So, you have to disconnect the slots manually when these slots are lambda functions. or use another method than lambda to bind an extra parameters to the slot (e.g. QSignalMapper, or the signal QMenu.triggered that has the QAction as parameter):
def __init__(self, parent):
...
for item in menuitems:
entry = menu.addAction(item)
menu.triggered.connect(self.doStuff)
def doStuff(self, entry):
print entry.text()