PyQt Messagebox is crashing - python

I'm working on a file transfer application including its server. When I try to send a file between two clients, I want to ensure that the receiving client will get a message box like "x user wants to send a file. Do you accept?". I accomplished this so far but when I clicked the "Yes" button for testing purposes, the Yes button disappear and the receiving client collapses. When I tried to view the error on console, I saw that it's "QObject::setParent: Cannot set parent, new parent is in a different thread" on the receiving client. I searched the error on the site but couldn't make sense out of the solutions. Could you explain to me how I can solve this?
Code:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import socket
import json
import base64
from threading import Thread
from time import sleep
username = "admin"
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("144.122.86.204",5000)) #Fill here later
s.send(username.encode("utf-8"))
app = QApplication(sys.argv)
window = QWidget()
window.setWindowTitle("File Sharing")
window.setGeometry(0,0,500,350)
contactlist= QListWidget(window)
contactlist.setGeometry(5,5,100,250)
def add():
user, ok = QInputDialog.getText(window, "Add a contact","Enter a
username:")
if ok:
contactlist.addItem(str(user))
def send():
target_user=contactlist.currentItem().text()
name = QFileDialog.getOpenFileName(window, 'Open File')
file = open(name[0], 'rb')
base64_bytes = base64.b64encode(file.read())
base64_string = base64_bytes.decode('utf-8')
data= {"FRM": username, "TO": target_user, "DATA": base64_string}
encoded_data = json.dumps(data).encode()
s.sendall(encoded_data)
def receive():
while True:
data = s.recv(1024)
if data:
if 'wants to send you a file. Do you accept that?' in
data.decode('utf-8'):
choice = QMessageBox.question(window, 'FileTransfer',
data.decode('utf-8'),QMessageBox. Yes|QMessageBox. No)
if choice == QMessageBox.Yes:
s.send("CONFIRMED".encode('utf-8'))
t = Thread(target=receive)
t.start()
add_contact = QPushButton("Add a Contact", window)
add_contact.setGeometry(5,270,100,50)
add_contact.clicked.connect(add)
send_file = QPushButton("Send a File",window)
send_file.setGeometry(110,270,200,50)
send_file.clicked.connect(send)
window.show()
sys.exit(app.exec())

The problem in your case is that QMessageBox is being created in another thread which Qt prohibits, and its parent window lives in the main thread what Qt also forbids. The general approach in this case is to send the information of the secondary thread to the main thread through signals, events, metaobjects, etc. that are thread-safe and in the main thread is to create the GUI (in your case QMessageBox).
In this case it can be complicated so instead of using the above it is best to use the QtNetwork module of Qt that will send you the information received through signals making the while loop unnecessary and therefore the use of threads.
import json
import base64
from PyQt5 import QtCore, QtGui, QtWidgets, QtNetwork
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.initUi()
self.m_username = "admin"
self.m_socket = QtNetwork.QTcpSocket(self)
self.m_socket.connected.connect(self.onConnected)
self.m_socket.readyRead.connect(self.onReadyRead)
self.m_socket.connectToHost("144.122.86.204", 5000)
#QtCore.pyqtSlot()
def onConnected(self):
username = "admin"
self.m_socket.write(self.m_username.encode("utf-8"))
self.m_socket.flush()
#QtCore.pyqtSlot()
def onReadyRead(self):
data = self.m_socket.readAll().data()
text = data.decode("utf-8")
if "wants to send you a file. Do you accept that?" in text:
choice = QtWidgets.QMessageBox.question(
self,
"FileTransfer",
text,
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
)
if choice == QtWidgets.QMessageBox.Yes:
self.m_socket.write("CONFIRMED".encode("utf-8"))
#QtCore.pyqtSlot()
def add_contant(self):
user, ok = QtWidgets.QInputDialog.getText(
self, "Add a contact", "Enter a username:"
)
if ok:
self.m_listwidget.addItem(user)
#QtCore.pyqtSlot()
def send_file(self):
if self.m_socket.state() != QtNetwork.QAbstractSocket.ConnectedState:
print("Socket not connected")
return
item = self.m_listwidget.currentItem()
if item is None:
print("Not current item")
return
target_user = item.text()
filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
with open(filename, "rb") as file:
base64_bytes = base64.b64encode(file.read())
base64_string = base64_bytes.decode("utf-8")
data = {"FRM": self.m_username, "TO": target_user, "DATA": base64_string}
encoded_data = json.dumps(data).encode()
self.m_socket.write(encoded_data)
self.m_socket.flush()
def initUi(self):
self.m_listwidget = QtWidgets.QListWidget()
self.m_listwidget.setFixedWidth(100)
self.m_add_button = QtWidgets.QPushButton("Add a Contact")
self.m_add_button.clicked.connect(self.add_contant)
self.m_add_button.setFixedSize(100, 50)
self.m_send_file = QtWidgets.QPushButton("Send a File")
self.m_send_file.clicked.connect(self.send_file)
self.m_send_file.setFixedSize(200, 50)
hlay = QtWidgets.QHBoxLayout()
hlay.addWidget(self.m_listwidget)
hlay.addStretch()
hlay2 = QtWidgets.QHBoxLayout()
hlay2.addWidget(self.m_add_button)
hlay2.addWidget(self.m_send_file)
hlay2.addStretch()
vlay = QtWidgets.QVBoxLayout(self)
vlay.addLayout(hlay)
vlay.addLayout(hlay2)
self.resize(500, 350)
def closeEvent(self, event):
if self.m_socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
self.m_socket.disconnectFromHost()
super(Widget, self).closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec())

Related

how to insert data from one function into multiple widgets in PyQt5

I have a device connected with my interface and want to insert the data into QlineEdit widgets
This function def getdevice_data(self): receaves the data from the device and returns it as string
with self.get_output__button.clicked.connect(self.getdevice_data) I "start" the function
and with self.custom_attribute.connect(self.device_input1.setText) I send the output to the QLineEdit widget
How can I keep the function running and insert the new data from the function into empty line edit widgets, without adding multiple buttons to start the function again and again ?
full code
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
import serial
import time
class CustmClass(qtw.QWidget):
'''
description einfügen
'''
# Attribut Signal
custom_attribute = qtc.pyqtSignal(str)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# your code will go here
# Interface
self.resize(300, 210)
# button
self.get_output__button = qtw.QPushButton("start function ?")
# lineEdit
self.device_input1 = qtw.QLineEdit()
self.device_input2 = qtw.QLineEdit()
# Layout
vboxlaout = qtw.QVBoxLayout()
vboxlaout.addWidget(self.get_output__button)
vboxlaout.addWidget(self.device_input1)
vboxlaout.addWidget(self.device_input2)
self.setLayout(vboxlaout)
self.show()
# Funktionalität
self.get_output__button.clicked.connect(self.getdevice_data)
self.custom_attribute.connect(self.device_input1.setText)
# self.custom_attribute.connect(self.device_input2.setText)
def getdevice_data(self):
try:
# Serial() opens a serial port
my_serial = serial.Serial(port='COM6', baudrate=2400, bytesize=7,
parity=serial.PARITY_NONE, timeout=None, stopbits=1)
if my_serial.is_open:
print("port open")
# log einfügen
while my_serial.is_open:
data = my_serial.read() # wait forever till data arives
time.sleep(1) # delay
data_left = my_serial.inWaiting()
data += my_serial.read(data_left)
data = data.decode("utf-8", "strict")
if type(data) == str:
print(data)
return self.custom_attribute.emit(data)
else:
print("zu")
except serial.serialutil.SerialException:
print("not open")
# logger hinzufügen
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
w = CustmClass()
sys.exit(app.exec_())
You should not execute time-consuming or time-consuming loops in the main thread since they block the event loop. What you must do is execute it on a secondary thread and send the information through signals. To obtain the data sequentially you can create an iterator and access each element through the next() function
import sys
import threading
import time
import serial
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class SerialWorker(qtw.QObject):
dataChanged = qtw.pyqtSignal(str)
def start(self):
threading.Thread(target=self._execute, daemon=True).start()
def _execute(self):
try:
my_serial = serial.Serial(
port="COM6",
baudrate=2400,
bytesize=7,
parity=serial.PARITY_NONE,
timeout=None,
stopbits=1,
)
while my_serial.is_open:
data = my_serial.read() # wait forever till data arives
time.sleep(1) # delay
data_left = my_serial.inWaiting()
data += my_serial.read(data_left)
data = data.decode("utf-8", "strict")
print(data)
self.dataChanged.emit(data)
except serial.serialutil.SerialException:
print("not open")
class Widget(qtw.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(300, 210)
self.get_output__button = qtw.QPushButton("start function ?")
self.device_input1 = qtw.QLineEdit()
self.device_input2 = qtw.QLineEdit()
# Layout
vboxlaout = qtw.QVBoxLayout(self)
vboxlaout.addWidget(self.get_output__button)
vboxlaout.addWidget(self.device_input1)
vboxlaout.addWidget(self.device_input2)
self.serial_worker = SerialWorker()
self.device_iterator = iter([self.device_input1, self.device_input2])
self.get_output__button.clicked.connect(self.serial_worker.start)
self.serial_worker.dataChanged.connect(self.on_data_changed)
#qtw.pyqtSlot(str)
def on_data_changed(self, data):
try:
device = next(self.device_iterator)
device.setText(data)
except StopIteration:
pass
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
w = CustmClass()
w.show()
sys.exit(app.exec_())

Text from QLineEdit not displaying

I am grabbing the user input from the line edit and displaying it on the QMessageBox but it won't show for some reason. I thought maybe I wasn't grabbing the input from QLineEdit at all but when I tried printing it on the terminal (it still wouldn't show there btw) the terminal scrolled down, recognizing that there is new data in it but just not displaying it. Get what I am saying?
import os
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def main():
app = QApplication(sys.argv)
w = MyWindow()
w.show()
sys.exit(app.exec_())
class MyWindow(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
# create objects
label = QLabel(self.tr("enter the data "))
self.le = QLineEdit()
self.te = QTextEdit()
# layout
layout = QVBoxLayout(self)
layout.addWidget(label)
layout.addWidget(self.le)
layout.addWidget(self.te)
self.setLayout(layout)
# create connection
self.mytext = str(self.le.text())
self.connect(self.le, SIGNAL("returnPressed(void)"),
self.display)
def display(self):
QApplication.instance().processEvents()
msg = QMessageBox.about(self, 'msg', '%s' % self.mytext)
print(self.mytext)
self.te.append(self.mytext)
self.le.setText("")
if __name__ == "__main__":
main()
You are currently reading the QLineEdit in the constructor, and at that moment the QLineEdit is empty, you must do it in the slot:
def display(self):
mytext = self.le.text()
msg = QMessageBox.about(self, 'msg', '%s' % mytext)
self.te.append(mytext)
self.le.clear()
Note: use clear() to clean the QLineEdit

PyQt4 Questions and Login WIndow to Main Window in Python

I'm new to PyQt and Python as a whole (more familiar with Java) and I'm trying to make an application where data is inserted and retrieved into the database. For the connection to the database, I'm using mysql connector. I'm able to insert and retrieve data just fine but I'm not sure how to implement the GUI portion of my application. What I want to do is have a login window that connected to the database for the main window to then insert data read from the file into the database and retrieve data based on what was selected by the user. Clicking "Sign in" should close the Login Window and open the Main Windows which shows a progress bar while inserting and should display results which users can sort by ( have not implemented it yet.
What are ways I could improve my program? My program sometimes hangs.
How could I go about my approach?
Is there an equivalent to Java's JFrame.dispose() in Python which closes then Window and clicking on a button?
The Login Window:
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.Qt import QPushButton, QLabel
from PyQt4.QtGui import QPlainTextEdit
from PyQt4.QtGui import QLineEdit
from MainGUI import MainGUI
import time
class LoginGUI(QtGui.QMainWindow):
def __init__(self):
super(LoginGUI, self).__init__()
self.setGeometry(730, 350, 500, 300)
self.setWindowTitle("Login")
self.initGUI()
def initGUI(self):
titleLabel = QLabel("Login", self)
titleLabel.move(200, 20)
titleLabel.setFont(QtGui.QFont("", 20))
loginLabel = QLabel("Username: ", self)
loginLabel.move(135, 120)
passwordLabel = QLabel("Password: ", self)
passwordLabel.move(135, 150)
loginText = QPlainTextEdit("root", self)
loginText.move(195, 120)
passwordText = QLineEdit("",self)
passwordText.move(195, 150)
passwordText.setEchoMode(QtGui.QLineEdit.Password)
loginBtn = QtGui.QPushButton("Sign in", self)
loginBtn.clicked.connect(lambda:
self.connectToDB(loginText.toPlainText(), passwordText.text()))
loginBtn.resize(loginBtn.sizeHint())
loginBtn.move(170, 250)
quitBtn = QtGui.QPushButton("Quit", self)
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
quitBtn.resize(quitBtn.sizeHint())
quitBtn.move(245,250)
self.show()
def connectToDB(self,username,password):
pmg = MainGUI()
pmg.prep("D:\\folder\\data.csv", username, password)
pmg.run()
#logonGUI.close()
#mainApp = QtGui.QApplication(sys.argv)
#mainGUI = MainGUI()
#sys.exit(app.exec_())
#return pmg
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
logonGUI = LoginGUI()
sys.exit(app.exec_())
The Main Window:
other imports
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.Qt import QPushButton, QLabel
from PyQt4.QtGui import QPlainTextEdit
from PyQt4.QtCore import QEventLoop
from viewer.DataSource import DataSource
class MainGUI(QtGui.QMainWindow):
theFile = None
username = None
password = None
source = None
con = None
rowsInserted = 0
progressBar = None
completed = 0
def __init__(self):
#app = QtGui.QApplication(sys.argv)
#mainGUI = MainGUI()
#sys.exit(app.exec_()).
super(MainGUI, self).__init__()
self.setGeometry(730, 350, 1000, 600)
self.setWindowTitle("MainWindow")
self.initGUI()
#self.show()
def prep(self, x, username, password):
try:
self.theFile = open(x, "r")
self.username = username
self.password = password
#Connect to db and pass connection to each class.
#Close connection at the end in a Finally statement.
self.source = DataSource()
self.con = self.source.getConnection(username, password)
except FileNotFoundError:
print("No file of {} found.".format(x))
def initGUI(self):
titleLabel = QLabel("MainWindow", self)
titleLabel.resize(200, 20)
titleLabel.move(450, 30)
titleLabel.setFont(QtGui.QFont("", 20))
quitBtn = QtGui.QPushButton("Quit", self)
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
quitBtn.resize(quitBtn.sizeHint())
quitBtn.move(800,550)
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setGeometry(200, 80, 250, 20)
def run(self):
with self.theFile as data:
lines = data.readlines()[1:]
for line in lines:
QtCore.QCoreApplication.processEvents(flags=QEventLoop.AllEvents)
#Line with QtCore supposed to be indented.
cleanDataFromDB(self.con)
insertData(self.con)
dao.retrieve(userInput)
try:
if self.con != None:
self.con.close()
except:
print("Error closing the database.")
I have found my solution.
I have made my instance of the MainGUI global so that it doesn't close.
def connectToDB(self,username,password):
global pmg
pmg = MainGUI()
pmg.prep("D:\\folder\\data.csv", username, password)
pmg.run()
self.close()
I have made some changes to my MainGUI itself by adding some show() statements to the title and button for before in initGUI() and after the loop of run(), and having repaint() after the show() after the loop.
def initGUI(self):
titleLabel = QLabel("MainWindow", self)
titleLabel.resize(200, 20)
titleLabel.move(450, 30)
titleLabel.setFont(QtGui.QFont("", 20))
titleLabel.hide()
quitBtn = QtGui.QPushButton("Quit", self)
quitBtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
quitBtn.resize(quitBtn.sizeHint())
quitBtn.move(800,550)
quitBtn.hide()
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setGeometry(200, 80, 250, 20)
self.progressBar.show()
def run(self):
with self.theFile as data:
lines = data.readlines()[1:]
for line in lines:
QtCore.QCoreApplication.processEvents()
cleanDataFromDB(self.con)
insertData(self.con)
progressBar.hide()
titleLabel.show()
quitBtn.show()
self.repaint()
Thanks for taking your time to help me. :D

Pyqt5 GUI Still Hangs When Using Thread

I'm new to python and pyqt.
I'm learning how to use threading with GUI.
I followed this tutorial
http://www.xyzlang.com/python/PyQT5/pyqt_multithreading.html
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import threading
from _ast import While
class Communicate(QObject):
signal = pyqtSignal(int, str)
class My_Gui(QWidget):
def __init__(self):
super().__init__()
self.comm = Communicate()
self.comm.signal.connect(self.append_data)
self.initUI()
def initUI(self):
btn_count = QPushButton('Count')
btn_count.clicked.connect(self.start_counting)
self.te = QTextEdit()
vbox = QVBoxLayout()
vbox.addWidget(btn_count)
vbox.addWidget(self.te)
self.setLayout(vbox)
self.setWindowTitle('MultiThreading in PyQT5')
self.setGeometry(400, 400, 400, 400)
self.show()
def count(self, comm):
'''
for i in range(10):
data = "Data "+str(i)
comm.signal.emit(i, data)
'''
i = 0
while True:
data = "Data "+str(i)
comm.signal.emit(i, data)
i+=1
def start_counting(self):
my_Thread = threading.Thread(target=self.count, args=(self.comm,))
my_Thread.start()
def append_data(self, num, data):
self.te.append(str(num) + " " + data)
if __name__ == '__main__':
app = QApplication(sys.argv)
my_gui = My_Gui()
sys.exit(app.exec_())
I changed the for loop to infinite while loop(incrementing the 'i').
If I execute the program, the GUI still hangs but if I remove the emit signal inside the loop, it no longer hangs.
Are there some tricks to make it not hangs?
while True makes an endless loop in the background
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import threading
from _ast import While
class Communicate(QObject):
signal = pyqtSignal(int, str)
class My_Gui(QWidget):
def __init__(self):
super().__init__()
self.comm = Communicate()
self.comm.signal.connect(self.append_data)
self.initUI()
def initUI(self):
btn_count = QPushButton('Count')
btn_count.clicked.connect(self.start_counting)
self.te = QTextEdit()
vbox = QVBoxLayout()
vbox.addWidget(btn_count)
vbox.addWidget(self.te)
self.setLayout(vbox)
self.setWindowTitle('MultiThreading in PyQT5')
self.setGeometry(400, 400, 400, 400)
self.show()
def count(self, comm):
for i in range(10):
data = "Data "+str(i)
comm.signal.emit(i, data)
# While True below will never stop and cause your program to stuck
'''
i = 0
while True:
data = "Data "+str(i)
comm.signal.emit(i, data)
i+=1
'''
def start_counting(self):
my_Thread = threading.Thread(target=self.count, args=(self.comm,))
my_Thread.start()
def append_data(self, num, data):
self.te.append(str(num) + " " + data)
if __name__ == '__main__':
app = QApplication(sys.argv)
my_gui = My_Gui()
sys.exit(app.exec_())
I think you are getting downvoted for two things:
you did only copy and paste the code from the tutorial
you didn't read the tutorial
In the tutorial, the author states:
In the above example, we have created a QPushbutton and QTextEdit. When the button is clicked it creates a new Thread that counts from 0 to 9 and emits the signal in order to append the number and data in the QTextEdit. In class Communicate signal is initialized as pyqtSignal(int, str). This int and str means when a signal will be emitted it will also pass two arguments the first one will be of Integer type and second one will be of String type.
By changing the loop to while true you continuosly emit signals and append the text in the QTextEdit. Probably not what you want.
Also commenting the emit statement internally continues to run the while loop.

threading for tray icon application

I want to have a tray icon to inform me whether or not my COM port is plugged-in. It should change every 5 seconds according to the state of the COM port. I also want the ability to kill the program using the contextual menu of the tray icon. I figured out how to have the refreshing or the menu, but I don't know how to have both.
import sys
import glob
import serial
import time
from PyQt4 import QtGui, QtCore
import sys
import threading
from multiprocessing import Process, Queue
#script needing python 3.4 , pyserial (via pip) and pyqt4 (via .exe available online)
def serial_ports():
if sys.platform.startswith('win'):
ports = ['COM' + str(i + 1) for i in range(256)]
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except (OSError, serial.SerialException):
pass
return result
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
changeicon = menu.addAction("Update")
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
exitAction.triggered.connect(QtGui.qApp.quit)
changeicon.triggered.connect(self.updateIcon)
def updateIcon(self):
resultats = serial_ports()
icone = "red.ico"
for resultat in resultats:
if "COM3" in resultat:
icone = "green.ico"
break
self.setIcon(QtGui.QIcon(icone))
#update the icon (its color) according to the content of "resultat"
#missing code; purpose : wait 5 seconds while having the contextual menu of the tray icon still available
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon("red.ico"), w)
#always starts with red icon
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Found out using QTimer thanks to figs, seems repetitive and didn't understand everything but it works :
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtGui.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtGui.QMenu(parent)
changeicon = menu.addAction("Update")
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
exitAction.triggered.connect(QtGui.qApp.quit)
changeicon.triggered.connect(self.updateIcon)
self.updateIcon()
def updateIcon(self):
try:
timer = QtCore.QTimer()
timer.timeout.connect(self.updateIcon)
timer.start(5000)
resultats = serial_ports()
icone = "red.ico"
for resultat in resultats:
if "COM3" in resultat:
icone = "green.ico"
break
self.setIcon(QtGui.QIcon(icone))
finally:
QtCore.QTimer.singleShot(5000,self.updateIcon)

Categories

Resources