Delegate: What is this? ----什么是Delegate(委托)

本文通过一个简单的示例解释了C#中Delegate的概念及其使用方法。示例中,DataHolder类作为服务端,通过Delegate向客户端(MainClass)提供数据搜索结果的回调。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么要写这篇文章

  • 首先,想找个借口说英语很难,但是,我将尽可能的让这篇文章通俗易懂。

    其次,我想解释一下我写这篇文章的原因。一段时间以前,我和一个朋友讨论过一个问题,他当时正在学习C#,他让我给他解释一下delegates和events。 当时,我非常地吃惊。 现在有那么多的C#书籍,有用的链接,sample源代码等等。但在讨论的过程中,我意识到他已经读过C#编程的书,浏览过很多有用的网络资源,也研究过一些好的代码实例, 但是对于理解delegates来说还是有不少的问题。

    这个代码实例就是我们讨论的结果。这个例子非常地简单,也可以说,非常地无聊。但是,他让我的那位朋友理解了delegates的实质。我希望这个例子以及其中的注释对于其他正在学习C#的朋友也有用。

    正文开始

    我建议你在阅读这篇文章前先下载下来这个demo(演示)项目。是一个C#控制台应用程序。解压缩后,用.NET Studio打开那个.sln文件.

    C# delegate 是一个callback函数。 换言之,delegate是class-server向class-client提供反馈的一种方式。

    C# 的delegate要比一般的(标准的)callback函数更加的灵活,因为它允许你定义一系列(strict list)可以从class-server端到class-client端的参数。

    在我们的这个例子中,class-server有一个名字DataHolder。它包含了一个数据集合。而数据项在DataItem类中进行的定义,每一个数据都有两个属性:Name和Color。而DataHoder类包含一个允许遍历查找数据集合以便找到符合搜索条件数据的方法。这个方法有两个参数,第一个参数定义了搜索的条件,第二个参数定义了一个当符合搜索条件时可以调用的方法。

    正确的理解class-server端遍历所有的数据(from the start to the end)是非常重要的。如果数据项符合搜索条件,那么class-server端就调用一个class-client端定义的方法。返回数目也就和符合搜索条件的那些数据项目数目相同。

    MainClass即是class-client.第一句话就创建了一个DataHolder类的实例(1st),然后创建了CallBack类型逆光的delegate实例。这个delegate实例(procedure01) 包含了下面的信息:用户自定义的class-server段要在class-client端调用的方法(PrintSearchResult),并且class-server必须把这些参数传递给这个用户定义的参数.而传递的这个参数,是在delegate声明中定义的。

    以上就是C# delegate的主要思想。我建议你把这段代码在编译器中执行一遍看看他的执行结果。

    ===================================
    部分代码:

     

    class DataItem
      {
       
    private string m_ObjectName;
       
    private string m_Color;
     
       
    public DataItem(string Name, string Color)
       {
        m_ObjectName 
    = Name;
        m_Color 
    = Color;
      }

      
    public string Name
      {
    get {return m_ObjectName;}}
      
      
    public string Color
      {
    get {return m_Color;}}

     }
    //Class Item

     
    /**//*This is declaration of new data type "CallBack" which is based
      * on C# data type "delegate"
    */
     
    public delegate void CallBack(string s);


     
    /**//*This class represents collection of data items.*/
     
    class DataHolder
     {
      
    private ArrayList m_List;

      
    public DataHolder()
      {
       m_List 
    = new ArrayList();
       m_List.Add(
    new DataItem("Desk""Black"));
       m_List.Add(
    new DataItem("Car""Red"));
       m_List.Add(
    new DataItem("Train""Yellow"));
       m_List.Add(
    new DataItem("Tomato""Red"));
       m_List.Add(
    new DataItem("Cheese""Yellow"));
       m_List.Add(
    new DataItem("Computer","Grey"));
       m_List.Add(
    new DataItem("Sausage""Red"));
       m_List.Add(
    new DataItem("Cat""Black"));
      }

      
    /**//* this method provides search functionality for caller. When an item 
       * meets search condition is found, client defined procedure activates.
    */
      
    public void SearchData(string SearchCondition, CallBack ClientDefinedProcedure)
      {
       
    foreach (DataItem i in this.m_List)
       {
        
    if (i.Color == SearchCondition)
        {
         ClientDefinedProcedure(i.Name);
        }
       }
      }
     }
    //Class DataHolder

     
    class MainClass
     {
      [STAThread]
      
    static void Main(string[] args)
      {
       
    /**//*Create list of Data Items*/
       DataHolder lst 
    = new DataHolder();

       
    /**//* Cteate instance of delegate with type CallBack and bind it
        * to user defined procedure PrintSearchResults()
    */
       CallBack procedure01 
    = new CallBack(PrintSearchResult);
       
       Console.WriteLine(
    "Red color objects:");
       
    /**//*Call search method with search critheria "Red" and user defined
        * procedure which is nesessary to execute when data item meets to search
        * creteria is found.
    */
       lst.SearchData(
    "Red", procedure01);
       Console.WriteLine(
    "---------------");
       Console.ReadLine();

      }

      
    /**//*This procedure is called by DataHolder any time when data item meets to search
       * criteria is found.
    */
      
    private static void PrintSearchResult(string s)
      {
       Console.WriteLine(s);
      }
     }
    //MainClass
    输出的结果是
    Red color objects:
    Car
    Tomato
    Sausage
    -------------
      
import binascii import serial import time import struct import queue import threading from datetime import datetime CanOBDItemList = [] CanPGNItemList = [[0,0,0,0]] filteredCanOBDItemList = [] Frame_start = b'\xFF' Frame_end = b'\x55' Frame_data_style_len = 6 Frame_Data_Len = 0 frame_buffer = bytearray() class CanInfShow_Item: def __int__(self,CanID,CanFramType,Len,CanDataInf): self.SystemCycle = datetime.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3], self.CanID = CanID, self.CanFrame = CanFramType, self.CanDataLen = Len, self.CanData = CanDataInf class CanPGNShow_Item: def __int__(self, PGNID, CanID, CanData, Signal): self.PGNID = PGNID, self.CanID = CanID, self.CanData = CanData, self.Signal = Signal class SerialPort: def __init__(self, port, baudrate): # 初始化串口参数 self.port = port self.baudrate = baudrate self.ser = serial.Serial( port=self.port, baudrate=self.baudrate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE ) # 等待串口连接稳定 self.last_data_time = time.time() # 新增:最后接收数据的时间戳 self.cycle_dict = {} # 存储{帧ID: [上次时间戳, 当前周期]} self.last_frame_time = {} # 存储每个ID的最后出现时间 self.data_updated_ids = set() # 存储数据变化的CAN ID self.new_added_ids = set() # 存储新增的CAN ID self.state = 0 self.current_frame = bytearray() self.expected_length = 0 self.raw_data_queue = queue.Queue(maxsize=10000) self.data_lock = threading.Lock() self.worker_threads = [] self.allowed_pgn_ids = {0xFEF1, 0xF004, 0xFEC1, 0xFEE5, 0xFEEE, 0xFE56, 0xFEF2, 0xF005} self.filter_cycles = [] # 添加这一行用于存储过滤周期 time.sleep(0.2) if not self.ser.isOpen(): print("串口打开失败!") def close(self): # 关闭串口连接 if self.ser.isOpen(): self.ser.close() def send(self, data): # 发送数据 if self.ser.isOpen(): try: self.ser.write(data.encode('utf-8')) except serial.SerialException as e: print(f"发送数据失败: {e}") # def recv(self): # # 接收数据 # if self.ser.isOpen(): # data = self.ser.read(self.ser.inWaiting()) # if data: # 有数据时更新时间戳 # self.last_data_time = time.time() # return data def recv(self, chunk_size=1024): if self.ser.isOpen(): # 每次最多读取1024字节 data = self.ser.read(min(self.ser.inWaiting(), chunk_size)) if data: self.last_data_time = time.time() return data return None def __del__(self): self.close() def SerialIsOpen(self): if self.ser.isOpen(): return 1 else: return 0 def start_reading(self): self.recv_thread = threading.Thread(target=self._recv_worker, daemon=True) self.parse_thread = threading.Thread(target=self._parse_worker, daemon=True) self.recv_thread.start() self.parse_thread.start() self.worker_threads = [self.recv_thread, self.parse_thread] def _recv_worker(self): while self.ser.isOpen(): data = self.recv(chunk_size=4096) # 每次最多读4KB if data: self.raw_data_queue.put(data) # else: # time.sleep(0.001) def _parse_worker(self): while True: try: data = self.raw_data_queue.get(timeout=0.1) for byte in data: self.process_byte(byte) except queue.Empty: continue def process_byte(self, byte): """ 使用状态机逐字节解析帧结构。 """ if self.state == 0: # 等待帧头 FF if byte == 0xFF: self.current_frame.append(byte) self.state = 1 else: # 如果不是帧头,忽略该字节 pass elif self.state == 1: # 等待帧头 55 if byte == 0x55: self.current_frame.append(byte) self.state = 2 else: # 如果第二字节不是55,重置 self.reset_state() elif self.state == 2: # 接收总长度低位 (第2字节) self.current_frame.append(byte) self.state = 3 elif self.state == 3: # 接收总长度高位 (第3字节) self.current_frame.append(byte) # 计算总长度(从第2字节开始) length_high = self.current_frame[2] length_low = byte self.expected_length = (length_high << 8) | length_low self.state = 4 elif self.state == 4: # 接收类型字段 (第4字节) self.current_frame.append(byte) self.state = 5 elif self.state == 5: # 接收保留字段 (第5字节) self.current_frame.append(byte) self.state = 6 elif self.state == 6: # 接收 CAN 通道类型 (第6字节) self.current_frame.append(byte) self.state = 7 elif self.state == 7: # 接收 CAN 报文个数 N (第7字节) self.current_frame.append(byte) self.num_messages = byte self.state = 8 self.messages_received = 0 elif self.state == 8: #接收can报文 self.current_frame.append(byte) self.messages_received += 1 if self.messages_received == self.num_messages * 12: self.state = 9 elif self.state == 9: # 接收校验位 self.current_frame.append(byte) self.state = 10 elif self.state == 10: # 完整帧已接收,验证校验和 if self.verify_checksum(): self.Frame_analoy_process(bytes(self.current_frame)) else: print("校验失败,丢弃当前帧") self.reset_state() def verify_checksum(self): """ 验证校验和:从第2字节到倒数第二个字节之和 & 0xFF """ data_to_check = self.current_frame[2:-1] # 从第2字节到最后一个校验位之前 checksum = sum(data_to_check) & 0xFF return checksum == self.current_frame[-1] def reset_state(self): """ 重置状态机 """ self.state = 0 self.current_frame = bytearray() self.expected_length = 0 self.messages_received = 0 def set_filter_cycles(self, cycles): self.filter_cycles = cycles.copy() if cycles else [] #报文解析 def Frame_analoy_process(self, Framedata): # 检查帧类型 (0x0C 0x98) if len(Framedata) < 8 or Framedata[4] != 0x0C or Framedata[5] != 0x98: return try: FrameNum = int(Framedata[7]) except IndexError: return # 检查是否有足够数据 if len(Framedata) < 12 * FrameNum + 8: return current_time = time.time() # 获取当前精确时间戳 for index in range(0,FrameNum): # 时间戳 Cantime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] try: id_bytes = [ Framedata[12 * index + 11], # LSB Framedata[12 * index + 10], Framedata[12 * index + 9], Framedata[12 * index + 8] # MSB ] except IndexError: continue # 格式化为8位大写十六进制 CanID = ''.join(format(b, '02X') for b in id_bytes) # 提取ID字节 CanFramType = "Cycle" Len = 8 CanDataSpace = '' PGNdata = '' PGNID = int(Framedata[12 * index + 9] ) + int(Framedata[12 * index + 10]* 0x100) PGNSignl = self.Frame_analoy_PGN_Signal(PGNID,Framedata[12 * index + 12:]) # 提取数据部分 for posindex in range(0,8): PGNdata += str(hex(Framedata[12 * index + 12 + posindex])[2:]) try: CanDataSpace = ' '.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ) except IndexError: continue CanItemData = [Cantime, CanID, CanFramType, Len, CanDataSpace] # print(CanDataSpace) # ✅ 只有在白名单内的PGNID才处理PGN信号 if PGNID in self.allowed_pgn_ids: # allowed_pgn_ids 是你预设的白名单集合 PGNSignl = self.Frame_analoy_PGN_Signal(PGNID, Framedata[12 * index + 12:]) PGNdata = ''.join(str(hex(Framedata[12 * index + 12 + posindex])[2:]) for posindex in range(8)).upper() SignalItemData = [hex(PGNID)[2:].upper(), CanID, PGNdata.upper(), PGNSignl] if all(not sublist for sublist in CanPGNItemList) or CanPGNItemList[0][0] == 0: if len(CanPGNItemList): CanPGNItemList.pop(0) CanPGNItemList.insert(0, SignalItemData) else: Listpos = self.find_in_2d_list(CanPGNItemList, CanID) if Listpos is not None: CanPGNItemList[Listpos[0]] = SignalItemData else: CanPGNItemList.append(SignalItemData) if CanID in self.last_frame_time: last_time = self.last_frame_time[CanID] cycle = (current_time - last_time) * 1000 # 转换为毫秒 if cycle > 40: if cycle<self.cycle_dict[CanID] : self.cycle_dict[CanID] = cycle/2 elif self.cycle_dict[CanID] == 0 : self.cycle_dict[CanID] = cycle/2 else: self.cycle_dict[CanID] = 0 # 首次出现,周期设为0 if "18F00F52" in self.cycle_dict: print(self.cycle_dict["18F00F52"]) self.last_frame_time[CanID] = current_time # 更新列表 filtered_cycles = getattr(self, 'filter_cycles', []) is_filtered = False if filtered_cycles: for filter_cycle in filtered_cycles: tolerance = filter_cycle * 0.15 # 允许±15%的容差 if abs(self.cycle_dict[CanID] - filter_cycle) <= tolerance: is_filtered = True break # 根据过滤状态更新相应的列表 if is_filtered: # 更新到filteredCanOBDItemList Listpos = self.find_in_2d_list(filteredCanOBDItemList, CanID) if Listpos is not None: filteredCanOBDItemList[Listpos[0]] = CanItemData self.data_updated_ids.add(CanID) else: filteredCanOBDItemList.append(CanItemData) self.new_added_ids.add(CanID) else: # 更新到CanOBDItemList Listpos = self.find_in_2d_list(CanOBDItemList, CanID) if Listpos is not None: CanOBDItemList[Listpos[0]] = CanItemData self.data_updated_ids.add(CanID) else: CanOBDItemList.append(CanItemData) self.new_added_ids.add(CanID) # self.last_data_time = time.time() # 解析到有效帧时更新时间 def find_in_2d_list(self,matrix, target): for i, row in enumerate(matrix): if any(x == target for x in row): return (i, row.index(target)) # 使用row.index()找到具体列的索引 return None def Frame_analoy_PGN_Signal(self,PGNID, Framedata): #车速 SignalVal = 0 if PGNID == 0xFEF1: SignalVal = float(int(Framedata[2] * 0x100) + int(Framedata[3]) / 256.0) elif PGNID == 0xF004: SignalVal = str(int(Framedata[2] & 0x7F)) + '|'+ str(float(int(Framedata[4] * 0x100) + int(Framedata[3]) / 125.0)) elif PGNID == 0xFCE1: SignalVal = int(Framedata[3] * 0x1000000) + int(Framedata[2] * 0x10000) + int(Framedata[1] * 0x100) + int(Framedata[0]) elif PGNID == 0xFEE5: SignalVal = int(Framedata[3] * 0x1000000) + int(Framedata[2] * 0x10000) + int(Framedata[1] * 0x100) + int(Framedata[0]) elif PGNID == 0xFEE5: SignalVal = int(Framedata[3] * 0x1000000) + int(Framedata[2] * 0x10000) + int(Framedata[1] * 0x100) + int(Framedata[0]) elif PGNID == 0xFEEE: SignalVal = int(Framedata[0] - 40) elif PGNID == 0xFE56: SignalVal = float(Framedata[0] * 0.4) elif PGNID == 0xFEF2: SignalVal = float(int(Framedata[1] * 0x100 + Framedata[0] * 0.05)) elif PGNID == 0xF005: SignalVal = Framedata[3] return SignalVal # def start_reading(self): # self.read_thread = threading.Thread(target=self.Com_read_frame, daemon=True) # self.read_thread.start() 这是serialpro文件 # -*- coding: utf-8 -*- import threading # Form implementation generated from reading ui file 'CanOBD.ui' # # Created by: PyQt5 UI code generator 5.15.9 # # 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 from PyQt5.QtCore import QThread, pyqtSignal,QTimer from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem,QTreeWidget, QTreeWidgetItem from SerialPro import SerialPort as SerialThread from SerialPro import CanOBDItemList,CanPGNItemList,filteredCanOBDItemList import time import serial import serial.tools.list_ports import binascii import struct class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(1220, 940) font = QtGui.QFont() font.setPointSize(12) MainWindow.setFont(font) self.CanOBDHiddenList = [] # 新增隐藏列表 self.last_checked_cycles = [] # 记录上一次的过滤条件 self.filter_changed = False # 过滤条件是否变化 self.filter_cycles = [] # 添加这一行用于存储过滤周期 self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.tabWidget = QtWidgets.QTabWidget(self.centralwidget) self.tabWidget.setGeometry(QtCore.QRect(0, 0, 1041, 940)) self.tabWidget.setObjectName("tabWidget") self.tab = QtWidgets.QWidget() self.tab.setObjectName("tab") self.tableWidget = QtWidgets.QTableWidget(self.tab) self.tableWidget.setGeometry(QtCore.QRect(0, 0, 1031, 940)) self.tableWidget.setObjectName("tableWidget") self.tableWidget.setColumnCount(5) self.tableWidget.setRowCount(150) for num in range(0,150,1): item = QtWidgets.QTableWidgetItem() self.tableWidget.setVerticalHeaderItem(num, item) item = QtWidgets.QTableWidgetItem() font = QtGui.QFont() font.setKerning(False) item.setFont(font) for line in range(0,5,1): self.tableWidget.setHorizontalHeaderItem(line, item) item = QtWidgets.QTableWidgetItem() self.tabWidget.addTab(self.tab, "") self.tab_2 = QtWidgets.QWidget() self.tab_2.setObjectName("CanOBD Cfg Set") self.mSpeedTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mSpeedTreeWidget.setGeometry(QtCore.QRect(10, 0, 1031, 101)) self.mSpeedTreeWidget.setObjectName("mSpeedTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mSpeedTreeWidget.headerItem().setFont(0, font) self.mSpeedTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mSpeedTreeWidget.headerItem().setFont(3, font) self.mRPMTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mRPMTreeWidget.setGeometry(QtCore.QRect(10, 100, 1031, 91)) self.mRPMTreeWidget.setObjectName("mRPMTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mRPMTreeWidget.headerItem().setFont(0, font) self.mRPMTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mRPMTreeWidget.headerItem().setFont(3, font) self.mVDHRTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mVDHRTreeWidget.setGeometry(QtCore.QRect(10, 190, 1031, 91)) self.mVDHRTreeWidget.setObjectName("mVDHRTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mVDHRTreeWidget.headerItem().setFont(0, font) self.mVDHRTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mVDHRTreeWidget.headerItem().setFont(3, font) self.mHoursTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mHoursTreeWidget.setGeometry(QtCore.QRect(10, 280, 1031, 101)) self.mHoursTreeWidget.setObjectName("mHoursTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mHoursTreeWidget.headerItem().setFont(0, font) self.mHoursTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mHoursTreeWidget.headerItem().setFont(3, font) self.mEECTreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mEECTreeWidget.setGeometry(QtCore.QRect(10, 380, 1031, 91)) self.mEECTreeWidget.setObjectName("mEECTreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mEECTreeWidget.headerItem().setFont(0, font) self.mEECTreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mEECTreeWidget.headerItem().setFont(3, font) self.mET1TreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mET1TreeWidget.setGeometry(QtCore.QRect(10, 470, 1031, 101)) self.mET1TreeWidget.setObjectName("mET1TreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mET1TreeWidget.headerItem().setFont(0, font) self.mET1TreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mET1TreeWidget.headerItem().setFont(3, font) self.mAT1T1ITreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mAT1T1ITreeWidget.setGeometry(QtCore.QRect(10, 570, 1031, 91)) self.mAT1T1ITreeWidget.setObjectName("mAT1T1ITreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mAT1T1ITreeWidget.headerItem().setFont(0, font) self.mAT1T1ITreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mAT1T1ITreeWidget.headerItem().setFont(3, font) self.mLFETreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mLFETreeWidget.setGeometry(QtCore.QRect(10, 660, 1031, 101)) self.mLFETreeWidget.setObjectName("mLFETreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mLFETreeWidget.headerItem().setFont(0, font) self.mLFETreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mLFETreeWidget.headerItem().setFont(3, font) self.mETC2TreeWidget = QtWidgets.QTreeWidget(self.tab_2) self.mETC2TreeWidget.setGeometry(QtCore.QRect(10, 760, 1031, 101)) self.mETC2TreeWidget.setObjectName("mETC2TreeWidget") font = QtGui.QFont() font.setPointSize(12) self.mETC2TreeWidget.headerItem().setFont(0, font) self.mETC2TreeWidget.headerItem().setTextAlignment(1, QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) font = QtGui.QFont() font.setKerning(True) self.mETC2TreeWidget.headerItem().setFont(3, font) self.tabWidget.addTab(self.tab_2, "") self.mComCfgBox = QtWidgets.QGroupBox(self.centralwidget) self.mComCfgBox.setGeometry(QtCore.QRect(1040, 10, 191, 231)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mComCfgBox.setFont(font) self.mComCfgBox.setObjectName("mComCfgBox") self.mPortName = QtWidgets.QLabel(self.mComCfgBox) self.mPortName.setGeometry(QtCore.QRect(20, 30, 61, 21)) self.mPortName.setObjectName("mPortName") self.mBpsName = QtWidgets.QLabel(self.mComCfgBox) self.mBpsName.setGeometry(QtCore.QRect(20, 60, 61, 21)) self.mBpsName.setObjectName("mBpsName") self.mDatabitName = QtWidgets.QLabel(self.mComCfgBox) self.mDatabitName.setGeometry(QtCore.QRect(20, 90, 61, 21)) self.mDatabitName.setObjectName("mDatabitName") self.mStopName = QtWidgets.QLabel(self.mComCfgBox) self.mStopName.setGeometry(QtCore.QRect(20, 120, 61, 21)) self.mStopName.setObjectName("mStopName") self.mOddName = QtWidgets.QLabel(self.mComCfgBox) self.mOddName.setGeometry(QtCore.QRect(20, 150, 61, 21)) self.mOddName.setObjectName("mOddName") self.mDatabitVal = QtWidgets.QLabel(self.mComCfgBox) self.mDatabitVal.setGeometry(QtCore.QRect(100, 90, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mDatabitVal.setFont(font) self.mDatabitVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mDatabitVal.setAlignment(QtCore.Qt.AlignCenter) self.mDatabitVal.setObjectName("mDatabitVal") self.mStopBitVal = QtWidgets.QLabel(self.mComCfgBox) self.mStopBitVal.setGeometry(QtCore.QRect(100, 120, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mStopBitVal.setFont(font) self.mStopBitVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mStopBitVal.setAlignment(QtCore.Qt.AlignCenter) self.mStopBitVal.setObjectName("mStopBitVal") self.mOddVal = QtWidgets.QLabel(self.mComCfgBox) self.mOddVal.setGeometry(QtCore.QRect(100, 150, 54, 21)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mOddVal.setFont(font) self.mOddVal.setLayoutDirection(QtCore.Qt.LeftToRight) self.mOddVal.setAlignment(QtCore.Qt.AlignCenter) self.mOddVal.setObjectName("mOddVal") self.mPortVal = QtWidgets.QComboBox(self.mComCfgBox) self.mPortVal.setGeometry(QtCore.QRect(90, 30, 81, 22)) self.mPortVal.setObjectName("mPortVal") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mPortVal.addItem("") self.mBPSVal = QtWidgets.QComboBox(self.mComCfgBox) self.mBPSVal.setGeometry(QtCore.QRect(90, 60, 81, 22)) self.mBPSVal.setObjectName("mBPSVal") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mBPSVal.addItem("") self.mOpenSerial = QtWidgets.QDialogButtonBox(self.mComCfgBox) self.mOpenSerial.setGeometry(QtCore.QRect(20, 190, 156, 31)) self.mOpenSerial.setStandardButtons(QtWidgets.QDialogButtonBox.Close|QtWidgets.QDialogButtonBox.Open) self.mOpenSerial.setObjectName("mOpenSerial") self.mCycleCfgBox = QtWidgets.QGroupBox(self.centralwidget) self.mCycleCfgBox.setGeometry(QtCore.QRect(1040, 260, 191, 221)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mCycleCfgBox.setFont(font) self.mCycleCfgBox.setObjectName("mCycleCfgBox") self.mcheck1000ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck1000ms.setGeometry(QtCore.QRect(20, 180, 141, 31)) self.mcheck1000ms.setObjectName("mcheck1000ms") self.mcheck500ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck500ms.setGeometry(QtCore.QRect(20, 150, 141, 31)) self.mcheck500ms.setObjectName("mcheck500ms") self.mcheck100ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck100ms.setGeometry(QtCore.QRect(20, 90, 141, 31)) self.mcheck100ms.setObjectName("mcheck100ms") self.mcheck50ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck50ms.setGeometry(QtCore.QRect(20, 60, 141, 31)) self.mcheck50ms.setObjectName("mcheck50ms") self.mcheck20ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck20ms.setGeometry(QtCore.QRect(20, 30, 141, 31)) self.mcheck20ms.setObjectName("mcheck20ms") self.mcheck200ms = QtWidgets.QCheckBox(self.mCycleCfgBox) self.mcheck200ms.setGeometry(QtCore.QRect(20, 120, 141, 31)) self.mcheck200ms.setObjectName("mcheck200ms") self.mEventSigBox = QtWidgets.QGroupBox(self.centralwidget) self.mEventSigBox.setGeometry(QtCore.QRect(1050, 490, 191, 151)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mEventSigBox.setFont(font) self.mEventSigBox.setObjectName("mEventSigBox") self.radioLeftREvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioLeftREvent.setGeometry(QtCore.QRect(10, 30, 151, 16)) self.radioLeftREvent.setObjectName("radioLeftREvent") self.radioKiilEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioKiilEvent.setGeometry(QtCore.QRect(10, 90, 151, 16)) self.radioKiilEvent.setObjectName("radioKiilEvent") self.radioPEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioPEvent.setGeometry(QtCore.QRect(10, 120, 151, 16)) self.radioPEvent.setObjectName("radioPEvent") self.radioOpenCloseEvent = QtWidgets.QRadioButton(self.mEventSigBox) self.radioOpenCloseEvent.setGeometry(QtCore.QRect(10, 60, 151, 16)) self.radioOpenCloseEvent.setObjectName("radioOpenCloseEvent") self.mReadOBDinfBox = QtWidgets.QGroupBox(self.centralwidget) self.mReadOBDinfBox.setGeometry(QtCore.QRect(1050, 660, 191, 171)) font = QtGui.QFont() font.setPointSize(14) font.setBold(True) font.setWeight(75) self.mReadOBDinfBox.setFont(font) self.mReadOBDinfBox.setObjectName("mReadOBDinfBox") self.radioVinRead = QtWidgets.QRadioButton(self.mReadOBDinfBox) self.radioVinRead.setGeometry(QtCore.QRect(10, 40, 141, 21)) self.radioVinRead.setObjectName("radioVinRead") self.mVinInfShow = QtWidgets.QTextBrowser(self.mReadOBDinfBox) self.mVinInfShow.setGeometry(QtCore.QRect(10, 70, 171, 91)) self.mVinInfShow.setObjectName("mVinInfShow") MainWindow.setCentralWidget(self.centralwidget) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) for num in range(0, 150, 1): item = self.tableWidget.verticalHeaderItem(num) item.setText(_translate("MainWindow", str(num +1))) item = self.tableWidget.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "时间标识")) item = self.tableWidget.horizontalHeaderItem(1) item.setText(_translate("MainWindow", "帧ID")) item = self.tableWidget.horizontalHeaderItem(2) item.setText(_translate("MainWindow", "帧类型")) item = self.tableWidget.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "长度")) item = self.tableWidget.horizontalHeaderItem(4) item.setText(_translate("MainWindow", "数据 (BIT7--BIT0 大端模式)")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1")) self.mSpeedTreeWidget.headerItem().setText(0, _translate("MainWindow", "速度[CCVS1]")) self.mSpeedTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mSpeedTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mSpeedTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(km/h)")) self.mSpeedTreeWidget.setColumnWidth(0, 150) self.mSpeedTreeWidget.setColumnWidth(1, 150) self.mSpeedTreeWidget.setColumnWidth(2, 550) self.mSpeedTreeWidget.setColumnWidth(3, 150) self.mRPMTreeWidget.headerItem().setText(0, _translate("MainWindow", "转速[EEC1]")) self.mRPMTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mRPMTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mRPMTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(rpm)")) self.mRPMTreeWidget.setColumnWidth(0, 150) self.mRPMTreeWidget.setColumnWidth(1, 150) self.mRPMTreeWidget.setColumnWidth(2, 550) self.mRPMTreeWidget.setColumnWidth(3, 150) self.mVDHRTreeWidget.headerItem().setText(0, _translate("MainWindow", "里程[VDHR]")) self.mVDHRTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mVDHRTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mVDHRTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(km)")) self.mVDHRTreeWidget.setColumnWidth(0, 150) self.mVDHRTreeWidget.setColumnWidth(1, 150) self.mVDHRTreeWidget.setColumnWidth(2, 550) self.mVDHRTreeWidget.setColumnWidth(3, 150) self.mHoursTreeWidget.headerItem().setText(0, _translate("MainWindow", "工作时长[HOURS]")) self.mHoursTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mHoursTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mHoursTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(hours)")) self.mHoursTreeWidget.setColumnWidth(0, 150) self.mHoursTreeWidget.setColumnWidth(1, 150) self.mHoursTreeWidget.setColumnWidth(2, 550) self.mHoursTreeWidget.setColumnWidth(3, 150) self.mEECTreeWidget.headerItem().setText(0, _translate("MainWindow", "发动机负载[EEC1]")) self.mEECTreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mEECTreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mEECTreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(%)")) self.mEECTreeWidget.setColumnWidth(0, 150) self.mEECTreeWidget.setColumnWidth(1, 150) self.mEECTreeWidget.setColumnWidth(2, 550) self.mEECTreeWidget.setColumnWidth(3, 150) self.mET1TreeWidget.headerItem().setText(0, _translate("MainWindow", "冷却液温度[ET1]")) self.mET1TreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mET1TreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mET1TreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(°)")) self.mET1TreeWidget.setColumnWidth(0, 150) self.mET1TreeWidget.setColumnWidth(1, 150) self.mET1TreeWidget.setColumnWidth(2, 550) self.mET1TreeWidget.setColumnWidth(3, 150) self.mAT1T1ITreeWidget.headerItem().setText(0, _translate("MainWindow", "燃油液面[AT1T1I]")) self.mAT1T1ITreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mAT1T1ITreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mAT1T1ITreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(%)")) self.mAT1T1ITreeWidget.setColumnWidth(0, 150) self.mAT1T1ITreeWidget.setColumnWidth(1, 150) self.mAT1T1ITreeWidget.setColumnWidth(2, 550) self.mAT1T1ITreeWidget.setColumnWidth(3, 150) self.mLFETreeWidget.headerItem().setText(0, _translate("MainWindow", "平均油耗[LFE]")) self.mLFETreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mLFETreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mLFETreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal(L/h)")) self.mLFETreeWidget.setColumnWidth(0, 150) self.mLFETreeWidget.setColumnWidth(1, 150) self.mLFETreeWidget.setColumnWidth(2, 550) self.mLFETreeWidget.setColumnWidth(3, 150) self.mETC2TreeWidget.headerItem().setText(0, _translate("MainWindow", "档位[ETC2]")) self.mETC2TreeWidget.headerItem().setText(1, _translate("MainWindow", "CanID")) self.mETC2TreeWidget.headerItem().setText(2, _translate("MainWindow", "Data")) self.mETC2TreeWidget.headerItem().setText(3, _translate("MainWindow", "Signal")) self.mETC2TreeWidget.setColumnWidth(0, 150) self.mETC2TreeWidget.setColumnWidth(1, 150) self.mETC2TreeWidget.setColumnWidth(2, 550) self.mETC2TreeWidget.setColumnWidth(3, 150) self.tableWidget.setColumnWidth(0, 200) self.tableWidget.setColumnWidth(1, 150) self.tableWidget.setColumnWidth(4,450) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "CanOBD Inf Show")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "CanOBD J1939 Show")) self.mComCfgBox.setTitle(_translate("MainWindow", "串口配置")) self.mPortName.setText(_translate("MainWindow", "端口号")) self.mBpsName.setText(_translate("MainWindow", "波特率")) self.mDatabitName.setText(_translate("MainWindow", "数据位")) self.mStopName.setText(_translate("MainWindow", "停止位")) self.mOddName.setText(_translate("MainWindow", "检验位")) self.mDatabitVal.setText(_translate("MainWindow", "8")) self.mStopBitVal.setText(_translate("MainWindow", "1")) self.mOddVal.setText(_translate("MainWindow", "无")) self.mBPSVal.setItemText(0, _translate("MainWindow", "9600")) self.mBPSVal.setItemText(1, _translate("MainWindow", "19200")) self.mBPSVal.setItemText(2, _translate("MainWindow", "115200")) self.mBPSVal.setItemText(3, _translate("MainWindow", "230400")) self.mBPSVal.setItemText(4, _translate("MainWindow", "256000")) self.mBPSVal.setItemText(5, _translate("MainWindow", "460800")) port_list = list(serial.tools.list_ports.comports()) if port_list.__len__() is not 0: for num in range(port_list.__len__()): self.mPortVal.setItemText(num, _translate("MainWindow", str(port_list[num].device))) serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial = SerialThread(serialport, serialbaudrate) self.mCycleCfgBox.setTitle(_translate("MainWindow", "过滤设置(周期)")) self.mcheck1000ms.setText(_translate("MainWindow", "1000ms 周期")) self.mcheck500ms.setText(_translate("MainWindow", "500ms 周期")) self.mcheck100ms.setText(_translate("MainWindow", "100ms 周期")) self.mcheck50ms.setText(_translate("MainWindow", "50ms 周期")) self.mcheck20ms.setText(_translate("MainWindow", "20ms 周期")) self.mcheck200ms.setText(_translate("MainWindow", "200ms 周期")) self.mEventSigBox.setTitle(_translate("MainWindow", "事件信号策略")) self.radioLeftREvent.setText(_translate("MainWindow", "左右转 事件")) self.radioKiilEvent.setText(_translate("MainWindow", "刹车 事件")) self.radioPEvent.setText(_translate("MainWindow", "档位 事件")) self.radioOpenCloseEvent.setText(_translate("MainWindow", "开关门 事件")) self.mReadOBDinfBox.setTitle(_translate("MainWindow", "主动读取信息")) self.radioVinRead.setText(_translate("MainWindow", "VIN 信息")) def OpenSerial(self): if self.LSerial != None: if self.LSerial.SerialIsOpen(): self.LSerial.__del__() port_list = list(serial.tools.list_ports.comports()) if port_list.__len__() != 0: serialport = self.mPortVal.currentText() serialbaudrate = self.mBPSVal.currentText() self.LSerial.__init__(serialport,serialbaudrate) # 开启线程 self.thread = Worker() # 创建线程对象 self.thread.update_signal.connect(self.CanOBDdatarefresh) # 连接信号和槽 self.thread.update_signal.connect(self.CanOBDSignalAnalyPro) # 连接信号和槽 # self.thread.update_signal.connect(self.LSerial.Com_read_frame) # 连接信号和槽 self.thread.start() # 启动线程 #self.LSerial.Com_read_frame() self.LSerial.start_reading() # <-- 在这里启动读取线程 def CloseSerial(self): if self.LSerial.SerialIsOpen(): self.LSerial.close() def Serialconnectslot(self): self.mOpenSerial.accepted.connect(self.OpenSerial) self.mOpenSerial.rejected.connect(self.CloseSerial) def get_checked_cycles(self): """返回用户勾选的所有周期值(毫秒)""" checked_cycles = [] if self.mcheck20ms.isChecked(): checked_cycles.append(20) if self.mcheck50ms.isChecked(): checked_cycles.append(50) if self.mcheck100ms.isChecked(): checked_cycles.append(100) if self.mcheck200ms.isChecked(): checked_cycles.append(200) if self.mcheck500ms.isChecked(): checked_cycles.append(500) if self.mcheck1000ms.isChecked(): checked_cycles.append(1000) return checked_cycles def is_cycle_filtered(self, cycle, filtered_cycles): """检查给定周期是否在过滤范围内,并根据周期大小动态调整容差""" if cycle == 0: # 如果没有记录周期,默认不过滤 return False for filtered_cycle in filtered_cycles: # 动态设置容差 if filtered_cycle == 20: tolerance = filtered_cycle * 0.50 # 50% 容差 elif filtered_cycle == 50: tolerance = filtered_cycle * 0.30 else: tolerance = filtered_cycle * 0.15 # 默认 15% 容差 if abs(cycle - filtered_cycle) <= tolerance: return True return False def refresh_full_table(self): """全表刷新:保留表格结构,只更新内容""" # 获取当前过滤条件 filtered_cycles = self.get_checked_cycles() # 合并所有数据(注意不要提前 clear) all_items = [] all_items.extend(CanOBDItemList) all_items.extend(filteredCanOBDItemList) # 清空原列表 CanOBDItemList.clear() filteredCanOBDItemList.clear() # 用 set 来记录已经加入的数据,防止重复插入 added_can_ids = set() # 重新分配数据到两个列表 for item in all_items: can_id = item[1] if can_id == 0: continue # 跳过无效数据 cycle = self.LSerial.cycle_dict.get(can_id, 0) if self.is_cycle_filtered(cycle, filtered_cycles): if can_id not in added_can_ids: filteredCanOBDItemList.append(item) added_can_ids.add(can_id) else: if can_id not in added_can_ids: CanOBDItemList.append(item) added_can_ids.add(can_id) # 保留表格结构(150行),只清空所有内容 for row in range(self.tableWidget.rowCount()): for col in range(self.tableWidget.columnCount()): item = self.tableWidget.item(row, col) if item: item.setText("") # 填充符合条件的行 for row_index, item_data in enumerate(CanOBDItemList): if item_data[1] == 0: # 跳过无效数据 continue self.update_table_row(row_index, item_data) self.tableWidget.show() def update_table_row(self, row_index, item_data): """更新表格的指定行""" self.tableWidget.setItem(row_index, 0, QtWidgets.QTableWidgetItem(str(item_data[0]))) self.tableWidget.setItem(row_index, 1, QtWidgets.QTableWidgetItem(str(item_data[1]))) self.tableWidget.setItem(row_index, 2, QtWidgets.QTableWidgetItem(str(item_data[2]))) self.tableWidget.setItem(row_index, 3, QtWidgets.QTableWidgetItem(str(item_data[3]))) self.tableWidget.setItem(row_index, 4, QtWidgets.QTableWidgetItem(str(item_data[4]))) def CanOBDdatarefresh(self): filtered_cycles = self.get_checked_cycles() if hasattr(self, 'LSerial'): self.LSerial.set_filter_cycles(filtered_cycles) # 判断是否需要执行全表刷新 current_cycles = self.get_checked_cycles() if sorted(current_cycles) != sorted(self.last_checked_cycles): self.refresh_full_table() self.last_checked_cycles = sorted(current_cycles) return # 获取更新和新增的CAN ID all_update_ids = self.LSerial.data_updated_ids | self.LSerial.new_added_ids # 遍历每个可能变化的ID for can_id in all_update_ids: found_in_main = False found_in_filtered = False # 查找在主列表中的索引 main_index = None for idx, item in enumerate(CanOBDItemList): if item[1] == can_id: main_index = idx found_in_main = True break # 查找在过滤列表中的索引 for idx, item in enumerate(filteredCanOBDItemList): if item[1] == can_id: found_in_filtered = True break cycle = self.LSerial.cycle_dict.get(can_id, 0) should_filter = self.is_cycle_filtered(cycle, filtered_cycles) if found_in_main: if should_filter: # 应该过滤 -> 移动到过滤列表并清空对应行 filtered_item = CanOBDItemList.pop(main_index) filteredCanOBDItemList.append(filtered_item) self.clear_table_row(main_index) else: # 不应过滤 -> 更新行数据 new_data = next((item for item in CanOBDItemList + [filtered_item for filtered_item in filteredCanOBDItemList if filtered_item[1] == can_id]), None) if new_data: self.update_table_row(main_index, new_data) elif found_in_filtered: if not should_filter: # 不应该过滤 -> 从过滤列表移到主列表并恢复行数据 for idx, item in enumerate(filteredCanOBDItemList): if item[1] == can_id: unfiltered_item = filteredCanOBDItemList.pop(idx) CanOBDItemList.append(unfiltered_item) new_row_index = len(CanOBDItemList) - 1 self.update_table_row(new_row_index, unfiltered_item) break else: # 新出现的数据 new_data = next((item for item in SerialPro.CanOBDItemList if item[1] == can_id), None) if new_data: cycle = self.LSerial.cycle_dict.get(can_id, 0) if self.is_cycle_filtered(cycle, filtered_cycles): filteredCanOBDItemList.append(new_data) else: CanOBDItemList.append(new_data) new_row_index = len(CanOBDItemList) - 1 self.update_table_row(new_row_index, new_data) # 统一刷新所有行,确保状态一致 self.sync_table_with_data() self.LSerial.data_updated_ids.clear() self.LSerial.new_added_ids.clear() def sync_table_with_data(self): """ 同步表格与数据源,确保前 len(CanOBDItemList) 行是真实数据, 超出部分保持空白或清除内容。 """ max_rows = self.tableWidget.rowCount() for row in range(max_rows): if row < len(CanOBDItemList): item_data = CanOBDItemList[row] if item_data and item_data[1] != 0: self.update_table_row(row, item_data) else: self.clear_table_row(row) else: # 清除多余行的内容(设置为空白) for col in range(self.tableWidget.columnCount()): item = self.tableWidget.item(row, col) if item: item.setText("") def clear_table_row(self, row_index): """ 将指定行的所有列设为 "0" """ for col in range(self.tableWidget.columnCount()): item = self.tableWidget.item(row_index, col) if item: item.setText("0") else: self.tableWidget.setItem(row_index, col, QtWidgets.QTableWidgetItem("0")) def CanOBDSignalAnalyPro(self): index = 0 bfindflag = 0 if all(not sublist for sublist in CanPGNItemList) or CanPGNItemList[0][0] == 0: if len(CanPGNItemList): CanPGNItemList.pop(0) else: for signalindex in CanPGNItemList: value = ''.join(c for c in signalindex[0].lower() if c in '0123456789abcdef') if len(value) % 2 != 0: value = '0' + value signalindex[0] = value PGNCanID = bytes.fromhex(str(signalindex[0])).hex() # 车速 - CCVS1 if (PGNCanID == bytes.fromhex("FEF1").hex()): self.update_tree_widget(self.mSpeedTreeWidget, signalindex, "速度[CCVS1]") # 发动机转速 - EEC1 发动机负载 elif (PGNCanID == bytes.fromhex("F004").hex()): self.update_tree_widget(self.mRPMTreeWidget, signalindex, "转速[EEC1]") self.update_tree_widget(self.mEECTreeWidget, signalindex, "转速[EEC1]") # 里程表 - VDHR elif (PGNCanID == bytes.fromhex("FEC1").hex()): self.update_tree_widget(self.mVDHRTreeWidget, signalindex, "里程[VDHR]") # 发动机工作小时数 - HOURS elif (PGNCanID == bytes.fromhex("FEE5").hex()): self.update_tree_widget(self.mHoursTreeWidget, signalindex, "工作时长[HOURS]") # 平均油耗 - LFE elif (PGNCanID == bytes.fromhex("FEF2").hex()): self.update_tree_widget(self.mLFETreeWidget, signalindex, "平均油耗[LFE]") # 冷却液温度 - ET1 elif (PGNCanID == bytes.fromhex("FEEE").hex()): self.update_tree_widget(self.mET1TreeWidget, signalindex, "冷却液温度[ET1]") # 发动机负载 - EEC1 elif (PGNCanID == bytes.fromhex("F004").hex()): self.update_tree_widget(self.mEECTreeWidget, signalindex, "发动机负载[EEC1]") # 燃油液位 - AT1T1I elif (PGNCanID == bytes.fromhex("FE56").hex()): self.update_tree_widget(self.mAT1T1ITreeWidget, signalindex, "燃油液面[AT1T1I]") # 档位 - ETC2 elif (PGNCanID == bytes.fromhex("F005").hex()): self.update_tree_widget(self.mETC2TreeWidget, signalindex, "档位[ETC2]") def update_tree_widget(self, tree_widget, signal_data, signal_name): """通用方法更新树形部件""" num_top_items = tree_widget.topLevelItemCount() bfindflag = False # 检查是否已存在该CanID的条目 for index in range(num_top_items): if tree_widget.topLevelItem(index).text(1) == str(signal_data[1]): tree_widget.topLevelItem(index).setText(0, signal_name) tree_widget.topLevelItem(index).setText(1, str(signal_data[1])) tree_widget.topLevelItem(index).setText(2, str(signal_data[2])) tree_widget.topLevelItem(index).setText(3, str(signal_data[3])) bfindflag = True break # 如果不存在则创建新条目 if not bfindflag: item = QTreeWidgetItem(tree_widget) item.setText(0, signal_name) item.setText(1, str(signal_data[1])) item.setText(2, str(signal_data[2])) item.setText(3, str(signal_data[3])) tree_widget.addTopLevelItem(item) tree_widget.expandAll() tree_widget.show() class Worker(QThread): update_signal = pyqtSignal(int) # 定义一个信号,用于传递更新信息到主线程 def run(self): # 模拟耗时操作 while True: time.sleep(0.3) self.update_signal.emit(1) # 发射信号,传递更新信息 这是canobd文件。现在这个程序需要追加一项功能,就是当收到一种帧id数据比起上一次改变时,将改变的几位标红,应该怎样修改代码?
最新发布
07-19
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值