Reputation: 67
When I run this code. The mainwindow named as the NextWindow is not activated and it is behind the QDialog which calls it. I cannot control the second window. I have worked on similar code before and it is possible to call Qmainwindow from a QDialog. I have followed the same method but I don't know why this cannot work.
Basically the code is just try to use one window to call another. The code is reproduced from the PyQt-Videos-Examples/QtResponsiveGUIThreading/hasher_threaded.py. The original code works well but what I want to do is to add another Qdialog to call the MainWindow. The issue is that about the Qthread method. It works fine with the QMainWindow but if I use QDialog, then the issue of QThread: Destroyed while thread is still running would appear.
The code is shown below:
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc
from pathlib import Path
from hashlib import sha1
def recursive_hashes(path):
"""Generate name and SHA1 hash of all files under the given path"""
if path.is_file():
sha1_obj = sha1()
try:
with open(path, 'rb') as handle:
while True:
data = handle.read(8192)
if not data:
break
sha1_obj.update(data)
sha1_hash = sha1_obj.hexdigest()
except PermissionError:
sha1_hash = 'Permission Denied'
yield (str(path), sha1_hash)
elif path.is_dir():
try:
for child in path.iterdir():
yield from recursive_hashes(child)
except PermissionError:
yield(str(path), 'Permission Denied')
else:
yield (str(path), 'Not a file or dir')
class Worker(qtc.QObject):
hashed = qtc.pyqtSignal(str, str)
@qtc.pyqtSlot(str)
def hash_directory(self, root):
hash_gen = recursive_hashes(Path(root))
for path, sha1_hash in hash_gen:
self.hashed.emit(path, sha1_hash)
class NextWindow(qtw.QMainWindow):
hash_requested = qtc.pyqtSignal(str)
def __init__(self):
"""MainWindow constructor."""
super().__init__()
# Main UI code goes here
form = qtw.QWidget()
self.setCentralWidget(form)
layout = qtw.QFormLayout()
form.setLayout(layout)
self.setWindowTitle('Second Window')
self.file_root = qtw.QLineEdit(returnPressed=self.start_hashing)
self.go_button = qtw.QPushButton('Start Hashing', clicked=self.start_hashing)
self.results = qtw.QTableWidget(0, 2)
self.results.setHorizontalHeaderLabels(['Name', 'SHA1-sum'])
self.results.horizontalHeader().setSectionResizeMode(qtw.QHeaderView.Stretch)
self.results.setSizePolicy(qtw.QSizePolicy.Expanding, qtw.QSizePolicy.Expanding)
layout.addRow(qtw.QLabel('SHA1 Recursive Hasher'))
layout.addRow('File Root', self.file_root)
layout.addRow('', self.go_button)
layout.addRow(self.results)
# Create a worker object and a thread
self.worker = Worker()
self.worker_thread = qtc.QThread()
self.worker.hashed.connect(self.add_hash_to_table)
self.hash_requested.connect(self.worker.hash_directory)
# Assign the worker to the thread and start the thread
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
#self.reject.connect(self.worker_thread.terminate)
# Connect signals & slots AFTER moving the object to the thread
# End main UI code
self.show()
def add_hash_to_table(self, name, sha1_sum):
"""Add the given name and sha1 sum to the table"""
row = self.results.rowCount()
self.results.insertRow(row)
self.results.setItem(row, 0, qtw.QTableWidgetItem(name))
self.results.setItem(row, 1, qtw.QTableWidgetItem(sha1_sum))
print(name, sha1_sum)
def start_hashing(self):
"""Prepare the GUI and emit the hash_requested signal"""
# Clear the table
while self.results.rowCount() > 0:
self.results.removeRow(0)
# Get the file root and validate it
file_root = self.file_root.text()
if not Path(file_root).exists():
qtw.QMessageBox.critical(self, 'Invalid Path', 'The specified file root does not exist.')
return
# Emit the signal
self.hash_requested.emit(file_root)
#self.worker.hash_directory(file_root)
class MainWindow(qtw.QDialog):
def __init__(self):
super().__init__()
layout = qtw.QFormLayout()
self.next_button = qtw.QPushButton('Next', clicked=self.next_button)
layout.addWidget(self.next_button)
self.setLayout(layout)
self.setWindowTitle("First Window")
def next_button(self):
self.dialog = NextWindow()
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
mw = MainWindow()
mw.exec_()
sys.exit(app.exec())
The eventual outlook is as the image below
Where the First Window (QDialog) which calls out Second Window (QMaindwindow) still stays on the top of the screen.
Upvotes: 0
Views: 125
Reputation: 244301
The problem is that when using exec_()
method then QDialog is in modal dialog as indicated in the docs and that it does not allow interaction with other windows until the dialog is closed:
int QDialog::exec()
Shows the dialog as a modal dialog, blocking until the user closes it. The function returns a DialogCode result.If the dialog is application modal, users cannot interact with any other window in the same application until they close the dialog. If the dialog is window modal, only interaction with the parent window is blocked while the dialog is open. By default, the dialog is application modal.
Note: Avoid using this function; instead, use open(). Unlike exec(), open() is asynchronous, and does not spin an additional event loop. This prevents a series of dangerous bugs from happening (e.g. deleting the dialog's parent while the dialog is open via exec()). When using open() you can connect to the finished() signal of QDialog to be notified when the dialog is closed.
(emphasis mine)
Change mw.exec_()
to mw.show()
.
Upvotes: 3