活动介绍

qt tablewidget 某列根据字体设置列宽

时间: 2025-01-12 10:01:23 浏览: 73
在Qt中,TableWidget是一个用于显示表格数据的控件,如果你想根据某一列的数据内容自动调整列宽,特别是根据文本的内容长度设置列宽,你可以使用`setColumnWidth()`函数结合自定义槽函数或信号连接。这里是一种常见的做法: 1. **动态计算列宽**: - 首先,在`QTableWidgetItem`对象创建后,获取其文本长度,例如使用`textLength()`函数。 - 然后,计算出最大长度对应的宽度,比如加上一些固定的缓冲值(如字体大小加边距)。 ```cpp int textLength = item->text().length(); int calculatedWidth = fontMetrics.width(item->text()) + buffer; // buffer是你想要的额外宽度 setColumnWidth(columnIndex, calculatedWidth); ``` 2. **处理单元格改变事件**: - 可以监听`QTableWidgetItem`的`textChanged()`信号,每当单元格内容发生变化时,再次计算并调整列宽。 ```cpp connect(item, &QTableWidgetItem::textChanged, this, [this, columnIndex](const QString& newText) { int newLength = newText.length(); int newCalculatedWidth = fontMetrics.width(newText) + buffer; setColumnWidth(columnIndex, newCalculatedWidth); }); ``` 3. **使用布局管理器**: - 如果你希望动态调整整个TableWidget的布局,可以考虑使用`QAbstractItemDelegate`派生类,并重写`sizeHintForColumn()`方法,该方法会返回每一列建议的宽度。 记得在应用程序初始化时设置好fontMetrics和buffer值,然后在合适的地方安装上述策略。
阅读全文

相关推荐

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.last_data_dict = {} # 存储每个CAN ID的上一次数据 self.changed_bytes_dict = {} # 存储每个CAN ID的变化字节索引 self.last_pgn_data_dict = {} # 存储每个PGN ID的上一次数据 self.changed_pgn_bytes_dict = {} # 存储每个PGN 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 = [] # 添加这一行用于存储过滤周期 self.count=0 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) 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:]) # 提取数据部分 PGNdata = ''.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ).upper() try: CanDataSpace = ' '.join( format(Framedata[12 * index + 12 + posindex], '02X') for posindex in range(8) ) except IndexError: continue current_data = CanDataSpace.split() if CanID in self.last_data_dict: last_data = self.last_data_dict[CanID].split() changed_indices = [] for i in range(min(len(last_data), len(current_data))): if last_data[i] != current_data[i]: changed_indices.append(i) self.changed_bytes_dict[CanID] = changed_indices else: self.changed_bytes_dict[CanID] = [] # 新ID无变化 self.last_data_dict[CanID] = CanDataSpace CanItemData = [Cantime, CanID, CanFramType, Len, CanDataSpace] # if CanID == "18FEBD00": # self.count=self.count+1 # print(CanDataSpace,self.count) # ✅ 只有在白名单内的PGNID才处理PGN信号 # 获取当前PGN数据 current_pgn_data = PGNdata # 使用上面生成的两位格式数据 # 检查数据变化 if PGNID in self.last_pgn_data_dict: last_data = self.last_pgn_data_dict[PGNID] changed_indices = [] for i in range(min(len(last_data), len(current_pgn_data) // 2)): start_idx = i * 2 end_idx = start_idx + 2 if last_data[start_idx:end_idx] != current_pgn_data[start_idx:end_idx]: changed_indices.append(i) self.changed_pgn_bytes_dict[PGNID] = changed_indices else: self.changed_pgn_bytes_dict[PGNID] = [] # 新PGN ID无变化 self.last_pgn_data_dict[PGNID] = current_pgn_data if PGNID in self.allowed_pgn_ids: PGNSignl = self.Frame_analoy_PGN_Signal(PGNID, Framedata[12 * index + 12:]) SignalItemData = [hex(PGNID)[2:].upper(), CanID, PGNdata, 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_ms = (current_time - last_time) * 1000 # 有效周期过滤 # if 10 < cycle_ms < 10000: # 10ms-10s合理范围 self.cycle_dict[CanID] = cycle_ms else: # 新ID初始化 self.cycle_dict[CanID] = 0 # 更新最后出现时间 self.last_frame_time[CanID] = current_time filtered_cycles = getattr(self, 'filter_cycles', []) is_filtered = False if filtered_cycles: for filtered_cycle in filtered_cycles: if filtered_cycle == 20: if cycle_ms < 50: is_filtered = True break elif filtered_cycle == 50: if 50 <= cycle_ms <= 100: is_filtered = True break elif filtered_cycle == 100: if 100 <= cycle_ms <= 190: is_filtered = True break elif filtered_cycle == 200: if 200 <= cycle_ms <= 400: is_filtered = True break elif filtered_cycle == 500: if 500 <= cycle_ms <= 600: is_filtered = True break elif filtered_cycle == 1000: if 600 <= cycle_ms <= 1000: 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() # 解析到有效帧时更新时间 if "0CF00400" in self.cycle_dict: print(self.cycle_dict["0CF00400"]) 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): # 确保数据是整数列表(0-255) if not all(isinstance(x, int) for x in Framedata): Framedata = [int(x) for x in Framedata] # 根据J1939规范解析 if PGNID == 0xFEF1: # 车速 (CCVS1) # 位置2-3 (索引1-2),大端序,单位1/256 km/h raw_val = (Framedata[1] << 8) | Framedata[2] return raw_val / 256.0 elif PGNID == 0xFE6C: # 车速 (TCO1) - 新增 # 位置7-8 (索引6-7),大端序,单位1/256 km/h raw_val = (Framedata[6] << 8) | Framedata[7] return raw_val / 256.0 elif PGNID == 0xF004: # 发动机转速+负载 # 负载:位置3 (索引2),单位1% engine_load = Framedata[2] & 0x7F # 取7位 # 转速:位置4-5 (索引3-4),大端序,单位0.125 RPM raw_rpm = (Framedata[3] << 8) | Framedata[4] rpm = raw_rpm * 0.125 return f'{engine_load}|{rpm}' elif PGNID == 0xFEC1: # 里程表 (VDHR) # 位置1-4 (索引0-3),大端序,单位0.125米 raw_val = int(Framedata[3] * 0x1000000) + int(Framedata[2] * 0x10000) + int(Framedata[1] * 0x100) + int(Framedata[0]) return raw_val * 0.125 # 转换为米 elif PGNID == 0xFEE5: # 发动机小时数 # 位置1-4 (索引0-3),大端序,单位0.05小时 raw_val = (Framedata[0] << 24) | (Framedata[1] << 16) | (Framedata[2] << 8) | Framedata[3] return raw_val * 0.05 elif PGNID == 0xFEF2: # 平均油耗 # 位置1-2 (索引0-1),大端序,单位0.05 L/h raw_val = (Framedata[0] << 8) | Framedata[1] return raw_val * 0.05 elif PGNID == 0xFEEE: # 冷却液温度 # 位置1 (索引0),单位1°C,偏移-40 return Framedata[0] - 40 elif PGNID == 0xFE56: # 燃油液位 # 位置1 (索引0),单位0.4% return Framedata[0] * 0.4 elif PGNID == 0xF005: # 档位 # 位置4 (索引3),直接返回值 return Framedata[3] return None 这是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 RichTextDelegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): if index.column() == 4: text = index.data(QtCore.Qt.DisplayRole) if not text: return # 创建 QTextDocument 并设置 HTML 内容 doc = QtGui.QTextDocument() doc.setHtml(text) # 设置基础字体(可以显式调大字体) font = option.font font.setPointSize(font.pointSize() + 2) # 调大字体 2pt,比如原来是 10pt → 12pt doc.setDefaultFont(font) # 调整绘制区域(不影响字体大小,仅调整边距) rect = option.rect.adjusted(2, 0, -2, 0) # 左右各留出 2 像素的空白 # 绘制背景 style = QtWidgets.QApplication.style() style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter) # 绘制文本 painter.save() painter.translate(rect.topLeft()) doc.drawContents(painter, QtCore.QRectF(0, 0, rect.width(), rect.height())) painter.restore() else: super().paint(painter, option, index) class RichTextTreeDelegate(QtWidgets.QStyledItemDelegate): def paint(self, painter, option, index): if index.column() == 2: # 只处理数据列 text = index.data(QtCore.Qt.DisplayRole) if not text: return super().paint(painter, option, index) # 创建QTextDocument并设置HTML内容 doc = QtGui.QTextDocument() doc.setHtml(text) # 设置基础字体 font = option.font font.setPointSize(font.pointSize()) doc.setDefaultFont(font) # 调整绘制区域 rect = option.rect.adjusted(2, 0, -2, 0) # 绘制背景 style = QtWidgets.QApplication.style() style.drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter) # 绘制文本 painter.save() painter.translate(rect.topLeft()) doc.drawContents(painter, QtCore.QRectF(0, 0, rect.width(), rect.height())) painter.restore() else: super().paint(painter, option, index) 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) #富文本委托 self.tableWidget.setItemDelegate(RichTextDelegate()) 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) self.mSpeedTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mRPMTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mVDHRTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mHoursTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mEECTreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mET1TreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mAT1T1ITreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mLFETreeWidget.setItemDelegate(RichTextTreeDelegate()) self.mETC2TreeWidget.setItemDelegate(RichTextTreeDelegate()) 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 信息")) self.radioLeftREvent.toggled.connect(self.on_radio_left_r_event_toggled) 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 on_radio_left_r_event_toggled(self, checked): if checked: # 勾选所有周期过滤项 self.mcheck20ms.setChecked(True) self.mcheck50ms.setChecked(True) self.mcheck100ms.setChecked(True) self.mcheck200ms.setChecked(True) self.mcheck500ms.setChecked(True) self.mcheck1000ms.setChecked(True) 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: if cycle <= 50: return True elif filtered_cycle == 50: if 50 <= cycle <= 100: return True elif filtered_cycle == 100: if 100 <= cycle <= 200: return True elif filtered_cycle == 200: if 200 <= cycle <= 400: return True elif filtered_cycle == 500: if 500 <= cycle <= 600: return True elif filtered_cycle == 1000: if 600 <= cycle <= 1000: 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): """更新表格的指定行,标记变化字节""" # 前4列正常显示 for col in range(4): self.tableWidget.setItem(row_index, col, QtWidgets.QTableWidgetItem(str(item_data[col]))) # 第4列(数据)特殊处理:标记变化字节 can_id = item_data[1] changed_indices = [] if hasattr(self, 'LSerial') and self.LSerial is not None: changed_indices = self.LSerial.changed_bytes_dict.get(can_id, []) data_bytes = item_data[4].split() rich_text = "" for idx, byte_str in enumerate(data_bytes): if idx in changed_indices: rich_text += f'{byte_str} ' else: rich_text += f'{byte_str} ' # 创建富文本显示项 item = QtWidgets.QTableWidgetItem() item.setData(QtCore.Qt.DisplayRole, item_data[4]) # 原始数据用于排序 item.setData(QtCore.Qt.EditRole, rich_text) # 富文本用于显示 self.tableWidget.setItem(row_index, 4, item) 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): """清除行内容时重置富文本显示""" for col in range(5): item = self.tableWidget.item(row_index, col) if item: item.setText("") # 清除富文本格式 item.setData(QtCore.Qt.EditRole, "") else: self.tableWidget.setItem(row_index, col, QtWidgets.QTableWidgetItem("")) 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): """更新树形部件,添加空格并标记变化字节,确保0值显示为00""" num_top_items = tree_widget.topLevelItemCount() bfindflag = False can_id = str(signal_data[1]) pgn_id = signal_data[0].lower() # 确保数据是16位长度(8个字节) data_str = signal_data[2] if len(data_str) != 16: # 如果不是16位,可能是0值被省略 # 补齐到16位 data_str = data_str.zfill(16) # 获取变化字节索引 changed_indices = [] if hasattr(self, 'LSerial') and self.LSerial is not None: try: pgn_id_int = int(pgn_id, 16) changed_indices = self.LSerial.changed_pgn_bytes_dict.get(pgn_id_int, []) except ValueError: pass # 格式化数据字符串(添加空格并确保两位显示) formatted_data = "" for i in range(0, len(data_str), 2): byte_str = data_str[i:i + 2] # 确保每个字节都是两位显示 if len(byte_str) == 1: byte_str = '0' + byte_str if i // 2 in changed_indices: formatted_data += f'{byte_str} ' else: formatted_data += f'{byte_str} ' formatted_data = formatted_data.strip() # 检查是否已存在该CanID的条目 for index in range(num_top_items): if tree_widget.topLevelItem(index).text(1) == can_id: tree_widget.topLevelItem(index).setText(0, signal_name) tree_widget.topLevelItem(index).setText(1, can_id) tree_widget.topLevelItem(index).setText(2, formatted_data) 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, can_id) item.setText(2, formatted_data) 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.5) self.update_signal.emit(1) # 发射信号,传递更新信息 这是canobd文件,现在这个程序需要追加一项功能,分辨事件帧和周期帧,周期帧定时发送,事件帧的出现则没有规则,现在要求制定策略识别,现有的思路是每个帧id定下一定的接收次数,每一次数内都记录当前算出的周期值,如果指定次数达到,开始判断周期的波动,如果超出误差,则定为事件帧。注意20ms以下周期的信号测量会存在10ms的误差,接收次数不能太多,因为存在10s的周期信号

最新推荐

recommend-type

C# Socket通信源码:多连接支持与断线重连功能的物联网解决方案

内容概要:本文介绍了一套基于C#编写的Socket服务器与客户端通信源码,源自商业级物联网项目。这套代码实现了双Socket机制、多连接支持以及断线重连功能,适用于各类C#项目(如MVC、Winform、控制台、Webform)。它通过简单的静态类调用即可获取客户端传输的数据,并内置了接收和发送数据缓冲队列,确保数据传输的稳定性。此外,代码提供了数据读取接口,但不涉及具体的数据处理逻辑。文中详细展示了服务端和客户端的基本配置与使用方法,强调了在实际应用中需要注意的问题,如避免主线程执行耗时操作以防内存膨胀。 适合人群:具备基本C#编程能力的研发人员,尤其是对Socket通信有一定了解并希望快速集成相关功能到现有项目中的开发者。 使用场景及目标:① 需要在短时间内为C#项目增加稳定的Socket通信功能;② 实现多设备间的数据交换,特别是对于智能家居、工业传感器等物联网应用场景。 其他说明:虽然该代码能够满足大多数中小型项目的通信需求,但对于需要高性能、低延迟的金融级交易系统则不太合适。同时,代码并未采用异步技术,因此在面对海量连接时可能需要进一步优化。
recommend-type

掌握XFireSpring整合技术:HELLOworld原代码使用教程

标题:“xfirespring整合使用原代码”中提到的“xfirespring”是指将XFire和Spring框架进行整合使用。XFire是一个基于SOAP的Web服务框架,而Spring是一个轻量级的Java/Java EE全功能栈的应用程序框架。在Web服务开发中,将XFire与Spring整合能够发挥两者的优势,例如Spring的依赖注入、事务管理等特性,与XFire的简洁的Web服务开发模型相结合。 描述:“xfirespring整合使用HELLOworld原代码”说明了在这个整合过程中实现了一个非常基本的Web服务示例,即“HELLOworld”。这通常意味着创建了一个能够返回"HELLO world"字符串作为响应的Web服务方法。这个简单的例子用来展示如何设置环境、编写服务类、定义Web服务接口以及部署和测试整合后的应用程序。 标签:“xfirespring”表明文档、代码示例或者讨论集中于XFire和Spring的整合技术。 文件列表中的“index.jsp”通常是一个Web应用程序的入口点,它可能用于提供一个用户界面,通过这个界面调用Web服务或者展示Web服务的调用结果。“WEB-INF”是Java Web应用中的一个特殊目录,它存放了应用服务器加载的Servlet类文件和相关的配置文件,例如web.xml。web.xml文件中定义了Web应用程序的配置信息,如Servlet映射、初始化参数、安全约束等。“META-INF”目录包含了元数据信息,这些信息通常由部署工具使用,用于描述应用的元数据,如manifest文件,它记录了归档文件中的包信息以及相关的依赖关系。 整合XFire和Spring框架,具体知识点可以分为以下几个部分: 1. XFire框架概述 XFire是一个开源的Web服务框架,它是基于SOAP协议的,提供了一种简化的方式来创建、部署和调用Web服务。XFire支持多种数据绑定,包括XML、JSON和Java数据对象等。开发人员可以使用注解或者基于XML的配置来定义服务接口和服务实现。 2. Spring框架概述 Spring是一个全面的企业应用开发框架,它提供了丰富的功能,包括但不限于依赖注入、面向切面编程(AOP)、数据访问/集成、消息传递、事务管理等。Spring的核心特性是依赖注入,通过依赖注入能够将应用程序的组件解耦合,从而提高应用程序的灵活性和可测试性。 3. XFire和Spring整合的目的 整合这两个框架的目的是为了利用各自的优势。XFire可以用来创建Web服务,而Spring可以管理这些Web服务的生命周期,提供企业级服务,如事务管理、安全性、数据访问等。整合后,开发者可以享受Spring的依赖注入、事务管理等企业级功能,同时利用XFire的简洁的Web服务开发模型。 4. XFire与Spring整合的基本步骤 整合的基本步骤可能包括添加必要的依赖到项目中,配置Spring的applicationContext.xml,以包括XFire特定的bean配置。比如,需要配置XFire的ServiceExporter和ServicePublisher beans,使得Spring可以管理XFire的Web服务。同时,需要定义服务接口以及服务实现类,并通过注解或者XML配置将其关联起来。 5. Web服务实现示例:“HELLOworld” 实现一个Web服务通常涉及到定义服务接口和服务实现类。服务接口定义了服务的方法,而服务实现类则提供了这些方法的具体实现。在XFire和Spring整合的上下文中,“HELLOworld”示例可能包含一个接口定义,比如`HelloWorldService`,和一个实现类`HelloWorldServiceImpl`,该类有一个`sayHello`方法返回"HELLO world"字符串。 6. 部署和测试 部署Web服务时,需要将应用程序打包成WAR文件,并部署到支持Servlet 2.3及以上版本的Web应用服务器上。部署后,可以通过客户端或浏览器测试Web服务的功能,例如通过访问XFire提供的服务描述页面(WSDL)来了解如何调用服务。 7. JSP与Web服务交互 如果在应用程序中使用了JSP页面,那么JSP可以用来作为用户与Web服务交互的界面。例如,JSP可以包含JavaScript代码来发送异步的AJAX请求到Web服务,并展示返回的结果给用户。在这个过程中,JSP页面可能使用XMLHttpRequest对象或者现代的Fetch API与Web服务进行通信。 8. 项目配置文件说明 项目配置文件如web.xml和applicationContext.xml分别在Web应用和服务配置中扮演关键角色。web.xml负责定义Web组件,比如Servlet、过滤器和监听器,而applicationContext.xml则负责定义Spring容器中的bean,包括数据源、事务管理器、业务逻辑组件和服务访问器等。 总之,通过上述整合使用原代码的知识点,可以深入理解XFire与Spring框架的结合使用,以及如何开发和部署基本的Web服务。这些技术知识有助于进行更高层次的Web服务开发,以及在复杂的IT环境中灵活运用各种框架和工具。
recommend-type

【Unity2018汉化大揭秘】:一步到位优化中文用户体验

# 摘要 本论文详细介绍了Unity2018汉化项目的实施过程,从理论准备到实践步骤,再到后期的测试与优化,最终分享了汉化经验和心得。首先,强调了汉化项目对于用户体验提升和产品国际化的重要性,并分析了汉化资源的分类、识别与管理方法。接着,详细阐述了汉化工具的选择、脚本编写、资源手动调整等实践步骤,以及汉化质量测试、体验优化和项目维护更新的重要性。论文最后通过案例研究,分析了汉化中的挑战和应对策略,并预测了汉化技术未来的发展方向。整个汉化过程不仅涉及技术层面的操作,还包含了深入的理论研究和实践心得分享,对提升汉化项目的效率和质量具有指导意义。 # 关键字 Unity汉化;用户体验;国际化;资源
recommend-type

iPhone

<think>我们注意到用户意图不明确,但提到了“照片提取”和“其他功能帮助”。因此,我们需要通过搜索来获取关于iPhone照片提取的常见方法以及其他可能的功能帮助。由于用户问题比较宽泛,我们将重点放在照片提取上,因为这是明确提到的关键词。同时,我们也会考虑一些其他常用功能的帮助。首先,针对照片提取,可能涉及从iPhone导出照片、从备份中提取照片、或者从损坏的设备中恢复照片等。我们将搜索这些方面的信息。其次,关于其他功能帮助,我们可以提供一些常见问题的快速指南,如电池优化、屏幕时间管理等。根据要求,我们需要将答案组织为多个方法或步骤,并在每个步骤间换行。同时,避免使用第一人称和步骤词汇。由于
recommend-type

驾校一点通软件:提升驾驶证考试通过率

标题“驾校一点通”指向的是一款专门为学员考取驾驶证提供帮助的软件,该软件强调其辅助性质,旨在为学员提供便捷的学习方式和复习资料。从描述中可以推断出,“驾校一点通”是一个与驾驶考试相关的应用软件,这类软件一般包含驾驶理论学习、模拟考试、交通法规解释等内容。 文件标题中的“2007”这个年份标签很可能意味着软件的最初发布时间或版本更新年份,这说明了软件具有一定的历史背景和可能经过了多次更新,以适应不断变化的驾驶考试要求。 压缩包子文件的文件名称列表中,有以下几个文件类型值得关注: 1. images.dat:这个文件名表明,这是一个包含图像数据的文件,很可能包含了用于软件界面展示的图片,如各种标志、道路场景等图形。在驾照学习软件中,这类图片通常用于帮助用户认识和记忆不同交通标志、信号灯以及驾驶过程中需要注意的各种道路情况。 2. library.dat:这个文件名暗示它是一个包含了大量信息的库文件,可能包含了法规、驾驶知识、考试题库等数据。这类文件是提供给用户学习驾驶理论知识和准备科目一理论考试的重要资源。 3. 驾校一点通小型汽车专用.exe:这是一个可执行文件,是软件的主要安装程序。根据标题推测,这款软件主要是针对小型汽车驾照考试的学员设计的。通常,小型汽车(C1类驾照)需要学习包括车辆构造、基础驾驶技能、安全行车常识、交通法规等内容。 4. 使用说明.html:这个文件是软件使用说明的文档,通常以网页格式存在,用户可以通过浏览器阅读。使用说明应该会详细介绍软件的安装流程、功能介绍、如何使用软件的各种模块以及如何通过软件来帮助自己更好地准备考试。 综合以上信息,我们可以挖掘出以下几个相关知识点: - 软件类型:辅助学习软件,专门针对驾驶考试设计。 - 应用领域:主要用于帮助驾考学员准备理论和实践考试。 - 文件类型:包括图片文件(images.dat)、库文件(library.dat)、可执行文件(.exe)和网页格式的说明文件(.html)。 - 功能内容:可能包含交通法规知识学习、交通标志识别、驾驶理论学习、模拟考试、考试题库练习等功能。 - 版本信息:软件很可能最早发布于2007年,后续可能有多个版本更新。 - 用户群体:主要面向小型汽车驾照考生,即C1类驾照学员。 - 使用方式:用户需要将.exe安装文件进行安装,然后根据.html格式的使用说明来熟悉软件操作,从而利用images.dat和library.dat中的资源来辅助学习。 以上知识点为从给定文件信息中提炼出来的重点,这些内容对于了解“驾校一点通”这款软件的功能、作用、使用方法以及它的发展历史都有重要的指导意义。
recommend-type

【DFLauncher自动化教程】:简化游戏启动流程,让游戏体验更流畅

# 摘要 DFLauncher是一个功能丰富的游戏启动和管理平台,本论文将介绍其安装、基础使用、高级设置、社区互动以及插件开发等方面。通过对配置文件的解析、界面定制、自动化功能的实现、高级配置选项、安全性和性能监控的详细讨论,本文阐述了DFLauncher如何帮助用户更高效地管理和优化游戏环境。此外,本文还探讨了DFLauncher社区的资源分享、教育教程和插件开发等内容,
recommend-type

自适应卡尔曼滤波是什么意思

<think>嗯,用户这次想深入了解自适应卡尔曼滤波(AKF)的概念和原理。从对话历史看,用户之前研究过相机自动对焦系统的降噪技术,其中提到过自适应卡尔曼滤波的应用场景。用户可能是个工程师或研究者,正在探索信号处理在具体场景的实现细节。 用户提供的三篇参考文献很有价值:[1]是基础理论综述,[2]聚焦多传感器场景,[3]讨论噪声协方差自适应方法。需要特别注意相机AF系统的特殊需求——实时性要求高(每秒数十次对焦计算)、噪声环境复杂(机械振动/弱光干扰),这些在解释原理时要结合具体案例。 技术要点需要分层解析:先明确标准卡尔曼滤波的局限(固定噪声参数),再展开自适应机制。对于相机AF场景,重
recommend-type

EIA-CEA 861B标准深入解析:时间与EDID技术

EIA-CEA 861B标准是美国电子工业联盟(Electronic Industries Alliance, EIA)和消费电子协会(Consumer Electronics Association, CEA)联合制定的一个技术规范,该规范详细规定了视频显示设备和系统之间的通信协议,特别是关于视频显示设备的时间信息(timing)和扩展显示识别数据(Extended Display Identification Data,简称EDID)的结构与内容。 在视频显示技术领域,确保不同品牌、不同型号的显示设备之间能够正确交换信息是至关重要的,而这正是EIA-CEA 861B标准所解决的问题。它为制造商提供了一个统一的标准,以便设备能够互相识别和兼容。该标准对于确保设备能够正确配置分辨率、刷新率等参数至关重要。 ### 知识点详解 #### EIA-CEA 861B标准的历史和重要性 EIA-CEA 861B标准是随着数字视频接口(Digital Visual Interface,DVI)和后来的高带宽数字内容保护(High-bandwidth Digital Content Protection,HDCP)等技术的发展而出现的。该标准之所以重要,是因为它定义了电视、显示器和其他显示设备之间如何交互时间参数和显示能力信息。这有助于避免兼容性问题,并确保消费者能有较好的体验。 #### Timing信息 Timing信息指的是关于视频信号时序的信息,包括分辨率、水平频率、垂直频率、像素时钟频率等。这些参数决定了视频信号的同步性和刷新率。正确配置这些参数对于视频播放的稳定性和清晰度至关重要。EIA-CEA 861B标准规定了多种推荐的视频模式(如VESA标准模式)和特定的时序信息格式,使得设备制造商可以参照这些标准来设计产品。 #### EDID EDID是显示设备向计算机或其他视频源发送的数据结构,包含了关于显示设备能力的信息,如制造商、型号、支持的分辨率列表、支持的视频格式、屏幕尺寸等。这种信息交流机制允许视频源设备能够“了解”连接的显示设备,并自动设置最佳的输出分辨率和刷新率,实现即插即用(plug and play)功能。 EDID的结构包含了一系列的块(block),其中定义了包括基本显示参数、色彩特性、名称和序列号等在内的信息。该标准确保了这些信息能以一种标准的方式被传输和解释,从而简化了显示设置的过程。 #### EIA-CEA 861B标准的应用 EIA-CEA 861B标准不仅适用于DVI接口,还适用于HDMI(High-Definition Multimedia Interface)和DisplayPort等数字视频接口。这些接口技术都必须遵循EDID的通信协议,以保证设备间正确交换信息。由于标准的广泛采用,它已经成为现代视频信号传输和显示设备设计的基础。 #### EIA-CEA 861B标准的更新 随着技术的进步,EIA-CEA 861B标准也在不断地更新和修订。例如,随着4K分辨率和更高刷新率的显示技术的发展,该标准已经扩展以包括支持这些新技术的时序和EDID信息。任何显示设备制造商在设计新产品时,都必须考虑最新的EIA-CEA 861B标准,以确保兼容性。 #### 结论 EIA-CEA 861B标准是电子显示领域的一个重要规范,它详细定义了视频显示设备在通信时所使用的信号时序和设备信息的格式。该标准的存在,使得不同厂商生产的显示设备可以无缝连接和集成,极大地增强了用户体验。对于IT专业人士而言,了解和遵守EIA-CEA 861B标准是进行视频系统设计、故障诊断及设备兼容性测试的重要基础。
recommend-type

【DFLauncher应用实战】:如何将DFLauncher融入矮人要塞并提升效率

# 摘要 DFLauncher是一款功能全面的游戏管理工具,旨在简化游戏安装、启动和维护过程。本文介绍了DFLauncher的基本使用方法,详细解析了其核心功能,包括游戏库管理、游戏配置优化、更新机制等。同时,文章探讨了DFLauncher在特定游戏“矮人要塞”中的集成应用,以及通过插件和脚本进行的高级定制。故障诊断与系统优化章节提供了实用的诊断方法和性能提升技巧。最后,本文展望了DFLauncher的未来发展方向,并鼓励社区贡献和用户反馈,以满足不断变化的用户需求。 # 关键字 DFLauncher;游戏管理工具;安装配置;性能优化;故障诊断;社区贡献;定制化扩展;网络功能集成 参考资源
recommend-type

银河麒麟系统打开屏保

<think>嗯,用户的问题是关于银河麒麟操作系统如何开启屏幕保护程序。这个问题其实挺实用的,毕竟长时间盯着电脑屏幕容易造成视觉疲劳,屏幕保护能起到一定的保护作用。 从用户提供的引用内容来看,引用[2]提到了银河麒麟系统的常见问题处理,包括重启输入法和双屏配置问题,虽然不直接相关,但说明用户对系统设置有一定了解。引用[3]是关于安装cmake的,可能用户之前尝试过其他系统配置操作。 用户可能是在办公环境中使用银河麒麟系统,需要保护视力。考虑到银河麒麟是基于Linux开发的国产操作系统,它的屏幕保护设置应该和主流Linux发行版类似,但可能有自己的特色功能。 我回忆了一下Linux系统的屏