You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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接收逻辑放到独立线程中运行。

其他问题与修复

  1. 全局变量滥用:使用全局列表l1,l2,l3会导致代码耦合度高、状态混乱,改为MainWindow的类成员变量。
  2. JSON解析逻辑脆弱:用split('}{')分割JSON可能出错,改为将连续JSON对象转换为合法数组的方式解析,同时跳过心跳包。
  3. 数据重复问题:每次接收数据重复添加EPC和名称,改为先去重再处理数据。
  4. 资源释放不完整:关闭窗口时未正确停止线程,补充线程停止和等待逻辑。

修复后完整代码

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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 23:40:41