PyQt5仓库TCP数据GUI窗口加载异常,请求代码审查
问题描述
我并非专业程序员,在类的使用上可能存在错误。我编写的代码旨在通过TCP读取数据、解析其中的EPC码,并在GUI表格中展示。但程序运行后窗口始终处于加载状态,且需要持续接收随时可能发送的TCP数据。恳请帮忙审查我的代码,找出问题所在,谢谢!
问题代码
import socket import json import sys import pandas as pd from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QTableWidget, QTableWidgetItem, QPushButton from PyQt5.QtGui import QPixmap, QFont from PyQt5.QtCore import Qt, QTimer, QObject, pyqtSignal, pyqtSlot df = pd.read_csv('almacen.csv', sep = ';', on_bad_lines='skip') TCP_IP = "192.168.50.129" TCP_PORT = 14150 BUFFER_SIZE = 1024 #data= '{"antennaPort":1,"epc":"000100000058602001000767","firstSeenTimestamp":1683294299497785,"peakRssi":-55,"isHeartBeat":false}{"antennaPort":1,"epc":"000100000058602001001257","firstSeenTimestamp":1683294299497785,"peakRssi":-55,"isHeartBeat":false}' l1,l2,l3 = [],[],[] class TcpClient(QObject): data_received = pyqtSignal(str) def __init__(self, host, port): super().__init__() self.host = host self.port = port self.socket = None def connect(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: self.socket.connect((self.host, self.port)) print("conectado a socket") self.receive_data() except ConnectionRefusedError: print("Connection refused.") def receive_data(self): while True: data = self.socket.recv(BUFFER_SIZE).decode("utf-8") if data: self.data_received.emit(data) def close(self): self.socket.close() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Lectura de salidas de almacén") self.setFixedSize(1360, 768) #self.setStyleSheet("background-color: white;") # Background image background_label = QLabel(self) pixmap = QPixmap("background.png") # Replace "background_image.jpg" with your actual image path background_label.setPixmap(pixmap) background_label.setGeometry(0, 0, 1360, 768) background_label.setScaledContents(True) # Logo images logo_left = QLabel(self) pixmap_left = QPixmap("logo_left.png") # Replace "logo_left.png" with your actual image path logo_left.setPixmap(pixmap_left) logo_left.setGeometry(50, 50, pixmap_left.width(), pixmap_left.height()) logo_left.setScaledContents(True) logo_right = QLabel(self) pixmap_right = QPixmap("logo_right.png") # Replace "logo_right.png" with your actual image path logo_right.setPixmap(pixmap_right) logo_right.setGeometry(1360 - pixmap_right.width() - 50, 50, pixmap_right.width(), pixmap_right.height()) logo_right.setScaledContents(True) # Data table self.title_label = QLabel("<u>Repuestos leidos...</u>", self) #title_label.setStyleSheet("color : red") self.title_label.setGeometry(530, 160, 300, 50) font = QFont("Arial", 18, QFont.Bold) self.title_label.setFont(font) self.table_widget = QTableWidget(self) self.table_widget.setGeometry(200, 230, 960, 400) # Set table font font = QFont("Arial", 12) self.table_widget.setFont(font) # Set table properties self.table_widget.setEditTriggers(QTableWidget.NoEditTriggers) self.table_widget.setFocusPolicy(Qt.NoFocus) self.table_widget.setSelectionMode(QTableWidget.NoSelection) # Insert initial data into table self.generate_data() # Adjust table frame to table size #table_widget.resizeColumnsToContents() #table_widget.resizeRowsToContents() # Buttons # button1 = QPushButton("Limpiar", self) # button1.setGeometry(1140, 670, 150, 50) button2 = QPushButton("LIMPIAR", self) button2.setFont(QFont('Arial', 12)) button2.setGeometry(1000, 670, 150, 50) button2.clicked.connect(self.button_clicked) self.counter = 30 self.label = QLabel("Reiniciando la lista automáticamente en... "+str(self.counter), self) self.label.setGeometry(600, 670, 400, 50) self.label.setFont(QFont('Arial', 12)) self.timer = QTimer() self.timer.timeout.connect(self.update_counter) self.timer.start(1000) # 1 second self.tcp_client = TcpClient(TCP_IP, TCP_PORT) self.tcp_client.data_received.connect(self.handle_data) def button_clicked(self): self.reset_counter() self.update_table() def update_table(self): self.table_widget.clear() # Insert data into table self.table_widget.setColumnCount(len(self.df2.columns)) self.table_widget.setRowCount(len(self.df2.index)) self.table_widget.setHorizontalHeaderLabels(self.df2.columns) for i, row in enumerate(self.df2.values): for j, value in enumerate(row): item = QTableWidgetItem(str(value)) self.table_widget.setItem(i, j, item) def generate_data(self): datos ={'Código de Artículo': l1, 'Nombre de Artículo': l2, 'Cantidad': l3} self.df2 = pd.DataFrame(datos) self.update_table() def update_counter(self): self.counter -= 1 self.label.setText("Reiniciando la lista automáticamente en... "+str(self.counter)) self.label.setFont(QFont('Arial', 12)) if self.counter == 0: self.timer.stop() self.label.setText("Limpiando...") self.reset_counter() def reset_counter(self): global l1, l2, l3 l1,l2,l3 = [],[],[] self.counter = 30 self.timer.start() @pyqtSlot(str) def handle_data(self, data): json_objects = data.split('}{') for json_str in json_objects: if not json_str.startswith('{'): json_str = '{' + json_str if not json_str.endswith('}'): json_str = json_str + '}' pdata = json.loads(json_str) epc_value = pdata['epc'] c = str(epc_value[12:18] + "-" + epc_value[18:24]) l1.append(c) lf= list(set(l1)) lista = df.index[df['codigo_articulo'] == c].tolist() listaf= list(set(lista)) cell_val = df.iloc[lista[0]]["nombre_articulo"] l2.append(cell_val) for j in lf: count = l1.count(j) l3.append(count) self.datos ={'Código de Artículo': lf, 'Nombre de Artículo': l2, 'Cantidad': l3} self.df2 = pd.DataFrame(self.datos) self.update_table() def closeEvent(self, event): try: self.tcp_client.close() except s.error as e: print("Error closing socket:", str(e)) event.accept() if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() window.tcp_client.connect() sys.exit(app.exec_())
问题分析与修复方案
核心问题:主线程阻塞
TcpClient的receive_data方法使用了while True循环,直接在主线程执行会阻塞Qt的事件循环,导致窗口无法响应、一直处于加载状态。必须将TCP接收逻辑放到独立线程中运行。
其他问题与修复
- 全局变量滥用:使用全局列表
l1,l2,l3会导致代码耦合度高、状态混乱,改为MainWindow的类成员变量。 - JSON解析逻辑脆弱:用
split('}{')分割JSON可能出错,改为将连续JSON对象转换为合法数组的方式解析,同时跳过心跳包。 - 数据重复问题:每次接收数据重复添加EPC和名称,改为先去重再处理数据。
- 资源释放不完整:关闭窗口时未正确停止线程,补充线程停止和等待逻辑。
修复后完整代码
import socket import json import sys import pandas as pd from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QTableWidget, QTableWidgetItem, QPushButton from PyQt5.QtGui import QPixmap, QFont from PyQt5.QtCore import Qt, QTimer, QObject, pyqtSignal, pyqtSlot, QThread df = pd.read_csv('almacen.csv', sep = ';', on_bad_lines='skip') TCP_IP = "192.168.50.129" TCP_PORT = 14150 BUFFER_SIZE = 1024 class TcpClient(QObject): data_received = pyqtSignal(str) def __init__(self, host, port): super().__init__() self.host = host self.port = port self.socket = None self.running = False def connect(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: self.socket.connect((self.host, self.port)) print("conectado a socket") self.running = True self.receive_data() except ConnectionRefusedError: print("Connection refused.") def receive_data(self): while self.running: try: data = self.socket.recv(BUFFER_SIZE).decode("utf-8") if data: self.data_received.emit(data) except Exception as e: print(f"接收数据出错: {e}") break def close(self): self.running = False if self.socket: self.socket.close() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Lectura de salidas de almacén") self.setFixedSize(1360, 768) # 背景图与Logo设置 background_label = QLabel(self) pixmap = QPixmap("background.png") background_label.setPixmap(pixmap) background_label.setGeometry(0, 0, 1360, 768) background_label.setScaledContents(True) logo_left = QLabel(self) pixmap_left = QPixmap("logo_left.png") logo_left.setPixmap(pixmap_left) logo_left.setGeometry(50, 50, pixmap_left.width(), pixmap_left.height()) logo_left.setScaledContents(True) logo_right = QLabel(self) pixmap_right = QPixmap("logo_right.png") logo_right.setPixmap(pixmap_right) logo_right.setGeometry(1360 - pixmap_right.width() - 50, 50, pixmap_right.width(), pixmap_right.height()) logo_right.setScaledContents(True) # 表格标题 self.title_label = QLabel("<u>Repuestos leidos...</u>", self) self.title_label.setGeometry(530, 160, 300, 50) self.title_label.setFont(QFont("Arial", 18, QFont.Bold)) # 数据表格 self.table_widget = QTableWidget(self) self.table_widget.setGeometry(200, 230, 960, 400) self.table_widget.setFont(QFont("Arial", 12)) self.table_widget.setEditTriggers(QTableWidget.NoEditTriggers) self.table_widget.setFocusPolicy(Qt.NoFocus) self.table_widget.setSelectionMode(QTableWidget.NoSelection) # 替换全局变量为类成员 self.l1 = [] self.l2 = [] self.l3 = [] self.generate_data() # 按钮设置 button2 = QPushButton("LIMPIAR", self) button2.setFont(QFont('Arial', 12)) button2.setGeometry(1000, 670, 150, 50) button2.clicked.connect(self.button_clicked) # 自动清理计时器 self.counter = 30 self.label = QLabel(f"Reiniciando la lista automáticamente en... {self.counter}", self) self.label.setGeometry(600, 670, 400, 50) self.label.setFont(QFont('Arial', 12)) self.timer = QTimer() self.timer.timeout.connect(self.update_counter) self.timer.start(1000) # TCP客户端与线程设置 self.tcp_client = TcpClient(TCP_IP, TCP_PORT) self.tcp_thread = QThread() self.tcp_client.moveToThread(self.tcp_thread) self.tcp_thread.started.connect(self.tcp_client.connect) self.tcp_client.data_received.connect(self.handle_data) def button_clicked(self): self.reset_counter() self.update_table() def update_table(self): self.table_widget.clear() self.table_widget.setColumnCount(len(self.df2.columns)) self.table_widget.setRowCount(len(self.df2.index)) self.table_widget.setHorizontalHeaderLabels(self.df2.columns) for i, row in enumerate(self.df2.values): for j, value in enumerate(row): item = QTableWidgetItem(str(value)) self.table_widget.setItem(i, j, item) def generate_data(self): datos ={'Código de Artículo': self.l1, 'Nombre de Artículo': self.l2, 'Cantidad': self.l3} self.df2 = pd.DataFrame(datos) self.update_table() def update_counter(self): self.counter -= 1 self.label.setText(f"Reiniciando la lista automáticamente en... {self.counter}") if self.counter == 0: self.timer.stop() self.label.setText("Limpiando...") self.reset_counter() def reset_counter(self): self.l1, self.l2, self.l3 = [], [], [] self.counter = 30 self.timer.start() self.generate_data() @pyqtSlot(str) def handle_data(self, data): # 修复JSON解析逻辑 json_str = data.replace('}{', '},{') if not json_str.startswith('['): json_str = '[' + json_str + ']' try: pdata_list = json.loads(json_str) except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") return epc_set = set() for pdata in pdata_list: if pdata.get('isHeartBeat'): continue epc_value = pdata['epc'] c = epc_value[12:18] + "-" + epc_value[18:24] epc_set.add(c) # 更新数据列表 self.l1 = list(epc_set) self.l2 = [] self.l3 = [] for c in self.l1: match_rows = df[df['codigo_articulo'] == c] self.l2.append(match_rows.iloc[0]['nombre_articulo'] if not match_rows.empty else "未知商品") self.l3.append(1) self.generate_data() def closeEvent(self, event): try: self.t
相关产品推荐
相关产品推荐

