import typing import sys import qtawesome from PyQt5 import QtGui,QtCore,QtWidgets from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt, QVariant, QSize from PyQt5.QtWidgets import QItemDelegate, QHBoxLayout, QWidget, QMessageBox, QComboBox from UI.VarManages.BaseButtonDelegate import BaseButtonDelegate # 移除 Celery 相关导入,现在使用 ProtocolManage 和 ModbusManager from ..TrendManage.ActualTrendWidget import ActualTrend from model.ProjectModel.VarManage import * from utils import Globals import re sys.path.append('protocol/Celery/MBTCPMaster') class QPushButton(QtWidgets.QPushButton): def __init__(self, *__args: any,clicked = None): super(QPushButton,self).__init__(*__args) self.setCursor(Qt.PointingHandCursor) self.setIconSize(QSize(25, 25)) class VarTableModel(QAbstractTableModel): ''' 变量表模型类''' def __init__(self, header, data: list, modbusType = None, table = None): ''' header : 表头变量 data : 表格内容 table : 缺省参数 ''' QAbstractTableModel.__init__(self, parent=None) self.header = header self.datas = data self.checkList = ['Unchecked'] * len(self.datas) self.supportedDragActions() self.table = table self.editableList = [] # 表格中可编辑项 self.modbusType = modbusType def initTable(self, initVar = None): self.datas = [] self.editableList = [] if self.table: self.table.parent.initIcon() varDatas = ModbusVarManage.getAllVar(self.modbusType) if not varDatas: # self.layoutChanged.emit() self.table.proxy.invalidate() return for x in varDatas: x.insert(1, '') x.insert(2, '') self.datas.append(x) self.checkList = ['Unchecked'] * len(self.datas) # self.layoutChanged.emit() self.table.proxy.invalidate() # self.refreshComboBox(10) def append_data(self, x): self.datas.append(x) self.checkList = ['Unchecked'] * len(self.datas) self.table.proxy.invalidate() # self.layoutChanged.emit() def insert_data(self, x, index): self.datas.insert(index, x) self.checkList = ['Unchecked'] * len(self.datas) self.table.proxy.invalidate() def remove_row(self, row): self.datas.pop(row) self.checkList = ['UnChecked'] * len(self.datas) self.table.proxy.invalidate() def rowCount(self, parent: QModelIndex = ...) -> int: if len(self.datas) > 0: return len(self.datas) return 0 def columnCount(self, parent: QModelIndex = ...) -> int: return len(self.header) def data(self, QModelIndex, role=None): # print(Qt.__dict__.items()) if role == Qt.TextAlignmentRole: return Qt.AlignCenter if not QModelIndex.isValid(): print("行或者列有问题") return QVariant() if role == Qt.BackgroundRole: # 只有强制变量显示绿色背景,其他都是白色 if self.datas[QModelIndex.row()][3] in Globals.getValue('forceVars'): return QtGui.QColor('#00FF7F') if role == Qt.TextColorRole: return QtGui.QColor('#1A1A1A') if role == Qt.CheckStateRole: if QModelIndex.column() == 0: return Qt.Checked if self.checkList[QModelIndex.row()] == 'Checked' else Qt.Unchecked else: return None if role == Qt.ToolTipRole: if QModelIndex.column() == 0: return self.checkList[QModelIndex.row()] if role == Qt.DisplayRole or role == Qt.EditRole: if QModelIndex.row() in self.editableList: return self.datas[QModelIndex.row()][QModelIndex.column()] if role != Qt.DisplayRole: return QVariant() # 获取变量值并插入表格 if QModelIndex.column() == 2: varName = self.datas[QModelIndex.row()][3] if varName != '': try: protocolManage = Globals.getValue('protocolManage') if protocolManage: result = protocolManage.readVariableValue(varName) if result is not None: self.datas[QModelIndex.row()][QModelIndex.column()] = result except Exception as e: pass return QVariant(self.datas[QModelIndex.row()][QModelIndex.column()]) def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any: # 表头 if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return self.header[section] def setData(self, index, value, role): row = index.row() col = index.column() if role == Qt.CheckStateRole and col == 0: self.checkList[row] = 'Checked' if value == Qt.Checked else 'Unchecked' return True if role == Qt.EditRole: self.datas[row][col] = value return True return True def flags(self, index): if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable if index.row() in self.editableList and index.column() or index.column() == 1: return Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsEditable return Qt.ItemIsEnabled def headerClick(self, isOn): self.beginResetModel() if isOn: self.checkList = [] self.checkList = ['Checked'] * len(self.datas) else: self.checkList = [] self.checkList = ['UnChecked'] * len(self.datas) self.endResetModel() def dragMoveEvent(self, event): event.setDropAction(QtCore.Qt.MoveAction) event.accept() def moveRow(self, sourceParent: QModelIndex, sourceRow: int, destinationParent: QModelIndex, destinationChild: int) -> bool: if self.datas[destinationChild] == self.datas[sourceRow]: return self.datas[sourceRow], self.datas[destinationChild] = self.datas[destinationChild], self.datas[sourceRow] self.table.proxy.invalidate() def refreshComboBox(self, comboBoxColumn): #功能类型的index是5,通讯类型index是10 for i in range(len(self.datas)): try: index = self.index(i, int(comboBoxColumn)) # 方法1:通过indexWidget获取已存在的comboBox widget = self.table.indexWidget(index) comboBox = None if widget is not None: # 从容器widget中获取comboBox comboBox = widget.findChild(QComboBox) else: # 方法2:尝试通过getattr获取委托中的comboBox try: delegate = self.table.itemDelegate(index) cbRow = str('cb' + str(i) + str(comboBoxColumn)) comboBox = getattr(delegate, cbRow, None) except (AttributeError, IndexError): continue # 检查comboBox对象是否仍然有效 if (comboBox is None or not hasattr(comboBox, 'setCurrentText')): continue # 尝试设置comboBox的值 try: currentValue = str(self.datas[i][comboBoxColumn]) comboBox.setCurrentText(currentValue) except (RuntimeError, AttributeError, IndexError) as e: # 如果设置值失败,跳过这一行 print(f"设置comboBox值失败 (行 {i}, 列 {comboBoxColumn}): {e}") continue except Exception as e: # 捕获所有异常,确保程序不会崩溃 print(f"刷新comboBox时出错 (行 {i}, 列 {comboBoxColumn}): {e}") continue class ModbusButtonDelegate(BaseButtonDelegate): """该类用于向单元格中添加按钮 任务表格""" trendWidgetDict = {} def __init__(self, parent=None): super(ModbusButtonDelegate, self).__init__(parent) def trendWidget(self, varName): if varName not in self.trendWidgetDict: trendWidget = ActualTrend(varName = varName) self.trendWidgetDict[varName] = trendWidget return self.trendWidgetDict[varName] def paint(self, painter, option, index): # 首先绘制背景颜色,与其他委托保持一致 if index.data(Qt.BackgroundRole): height = option.rect.height() top = option.rect.top() paint_rect = option.rect paint_rect.setHeight(max(46, height - 4)) paint_rect.moveTop(top + 2) painter.fillRect(paint_rect, index.data(Qt.BackgroundRole)) if not self.parent().indexWidget(index): try: button1 = QPushButton() button1.setIcon(qtawesome.icon('fa.play', color='#1fbb6f')) button1.setToolTip("启动强制") button2 = QPushButton() button2.setIcon(qtawesome.icon('fa.pencil', color='#4c8cf2')) button2.setToolTip("编辑变量") button3 = QPushButton() button3.setIcon(qtawesome.icon('fa.trash', color='#ff6d6d')) button3.setToolTip("删除变量") button4 = QPushButton() button4.setIcon(qtawesome.icon('fa.line-chart', color='#393c4e')) button4.setToolTip("趋势图") comboBox = QComboBox(self.parent()) comboBox.addItem('int', 0) comboBox.addItem('ABCD', 1) comboBox.addItem('CDAB', 2) comboBox.addItem('BADC', 3) comboBox.addItem('DCBA', 4) try: comboBox.setCurrentText(str(self.parent().model.datas[index.row()][index.column()])) except (IndexError, RuntimeError): # 如果数据访问失败,设置默认值 comboBox.setCurrentIndex(0) comboBox.setToolTip("字节序") comboBox.currentIndexChanged.connect(self.indexChange) button1.clicked.connect(self.start_action) button2.clicked.connect(self.edit_action) button3.clicked.connect(self.delete_action) button4.clicked.connect(self.trend_action) button2.oldName = False button2.isEdit = True button3.editButton = button2 button1.index = [index.row(), index.column()] button2.index = [index.row(), index.column()] button3.index = [index.row(), index.column()] button4.index = [index.row(), index.column()] comboBox.index = [index.row(), index.column()] data = self.parent().model.datas[index.row()] for x in data[:-3]: if x != '': break else: button2.isEdit = False button2.setIcon(qtawesome.icon('fa.save', color='#1fbb6f')) button2.setToolTip("保存变量") self.parent().model.editableList.append(button2.index[0]) hboxLayout = QHBoxLayout() hboxLayout.addWidget(comboBox) hboxLayout.addWidget(button1) hboxLayout.addWidget(button2) hboxLayout.addWidget(button3) hboxLayout.addWidget(button4) hboxLayout.setContentsMargins(2, 2, 2, 2) hboxLayout.setAlignment(Qt.AlignCenter) hboxLayout.setSpacing(3) widget = QWidget() widget.setLayout(hboxLayout) widget.setStyleSheet("QComboBox { background-color: transparent; }") # 所有下拉框使用统一样式 widget.setObjectName('ModbusButtonWidget') comboBox.setObjectName('ModbusOrderBox') self.parent().setIndexWidget(index, widget) except Exception as e: # 捕获异常,确保paint方法不会崩溃 print(f"创建按钮时出错 (行 {index.row()}, 列 {index.column()}): {e}") pass def indexChange(self): sender = self.sender() modbusType = self.parent().modbusType index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] ModbusVarManage.editOrder(name, text, modbusType) self.parent().model.datas[sender.index[0]][sender.index[1]] = text def start_action(self): sender = self.sender() model = self.parent().model # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return # 获取强制值(从视图)和其他数据(从源模型) value = str(self._get_view_data(view_row, 1)) # 强制值列 varMes = model.datas[source_row] name, des, varType, slaveID, address, min, max, order = str(varMes[3]), str(varMes[4]), str(varMes[5]), str(varMes[6]), str(varMes[7]), str(varMes[8]), str(varMes[9]), str(varMes[10]) pattern = re.compile(r'[^0-9\.-]+') if not value or re.findall(pattern, str(value)): reply = QMessageBox.question(self.parent(), '警告', "请输入强制值或数字", QMessageBox.Yes) return if min and max: if float(value) < float(min) or float(value) > float(max): reply = QMessageBox.question(self.parent(), '警告', "超出量程范围", QMessageBox.Yes) return elif min and not max: if float(value) < float(min): reply = QMessageBox.question(self.parent(), '警告', "超出量程范围", QMessageBox.Yes) return elif max and not min: if float(value) > float(max): reply = QMessageBox.question(self.parent(), '警告', "超出量程范围", QMessageBox.Yes) return # 使用新的 ProtocolManage 进行变量写入 protocolManage = Globals.getValue('protocolManage') if protocolManage: try: result = protocolManage.writeVariableValue(name, value) if result: forceVars = Globals.getValue('forceVars') if forceVars is None: forceVars = set() forceVars.add(name) Globals.setValue('forceVars', forceVars) else: QMessageBox.warning(self.parent(), '错误', f'变量 {name} 写入失败', QMessageBox.Yes) except Exception as e: QMessageBox.warning(self.parent(), '错误', f'写入变量时发生错误: {str(e)}', QMessageBox.Yes) else: QMessageBox.warning(self.parent(), '错误', '协议管理器未初始化', QMessageBox.Yes) def edit_action(self): sender = self.sender() model = self.parent().model modbusType = self.parent().modbusType pattern = re.compile(r'[^0-9\.-]+') # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return fucationCbRow = str('cb' + str(source_row) + str(5)) fucationIndex = self.parent().model.index(source_row, 5) fucationDelegate = self.parent().itemDelegate(fucationIndex) fucationCheckbox = getattr(fucationDelegate, fucationCbRow) if sender.isEdit: sender.setIcon(qtawesome.icon('fa.save', color='#1fbb6f')) sender.isEdit = False sender.oldName = model.datas[source_row][3] model.editableList.append(source_row) fucationCheckbox.setEnabled(True) self.parent().viewport().update() else: varMes = model.datas[source_row] name, des, varType, slaveID, address, min, max, varModel, order = str(varMes[3]), str(varMes[4]), str(varMes[5]), str(varMes[6]), str(varMes[7]), str(varMes[8]), str(varMes[9]), str(varMes[-2]), str(varMes[-1]) if varType == '': varType = 0 if not name or not slaveID or not address: reply = QMessageBox.question(self.parent(), '警告', "有字段为空或输入错误", QMessageBox.Yes) return # 验证slaveID、address、min、max是否为有效数字 # 检查slaveID是否为有效数字 if slaveID and re.findall(pattern, slaveID): reply = QMessageBox.question(self.parent(), '警告', "从站地址必须为数字", QMessageBox.Yes) return # 检查address是否为有效数字 if address and re.findall(pattern, address): reply = QMessageBox.question(self.parent(), '警告', "寄存器地址必须为数字", QMessageBox.Yes) return # 检查min是否为有效数字(如果存在) if min and re.findall(pattern, min): reply = QMessageBox.question(self.parent(), '警告', "工程量下限必须为数字", QMessageBox.Yes) return # 检查max是否为有效数字(如果存在) if max and re.findall(pattern, max): reply = QMessageBox.question(self.parent(), '警告', "工程量上限必须为数字", QMessageBox.Yes) return if sender.oldName and ModbusVarManage.getByName(sender.oldName, modbusType): if sender.oldName == name: # 名称未改变,直接执行修改变量操作 ModbusVarManage.editVar(name=sender.oldName, Nname=name, des=des, varType=varType, slaveID=slaveID, address=address, min=min, max=max, order=order, modbusType=modbusType, varModel=varModel) else: # 名称改变,检查新名称是否已存在 if GlobalVarManager.isVarNameExist(name): QMessageBox.information(self.parent(), '提示', '已有同名变量') return else: # 执行修改变量操作 ModbusVarManage.editVar(name=sender.oldName, Nname=name, des=des, varType=varType, slaveID=slaveID, address=address, min=min, max=max, order=order, modbusType=modbusType, varModel=varModel) else: if GlobalVarManager.isVarNameExist(name): QMessageBox.information(self.parent(), '提示', '已有同名变量') return else: ModbusVarManage.createVar(varName = name, varType = varType, des = des, address = address, slaveID = slaveID, min = min, max = max, order = order, modbusType = modbusType, varModel= varModel) sender.setIcon(qtawesome.icon('fa.pencil', color='#4c8cf2')) varMes = ModbusVarManage.getByName(name, modbusType) varMes.append('本地值') varMes.append('int') varMes.insert(1, '') varMes.insert(2, '') model.insert_data(varMes, source_row) model.remove_row(source_row + 1) sender.isEdit = True fucationCheckbox.setEnabled(False) # varModelCheckbox.setEnabled(False) model.editableList.remove(source_row) self.parent().viewport().update() def delete_action(self): sender = self.sender() model = self.parent().model # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return name = str(model.datas[source_row][3]) modbusType = self.parent().modbusType if sender.editButton.isEdit: ModbusVarManage.deleteVar(name = name, modbusType = modbusType) model.remove_row(source_row) def trend_action(self): model = self.parent().model if model.table.parent._isPopenOpen: sender = self.sender() # 使用基类的验证方法获取行索引 view_row, source_row = self._validate_button_indices(sender) if view_row is None: return name = str(model.datas[source_row][3]) trend = self.trendWidget(varName = name) trend.show() class ModbusTypeBox(QItemDelegate): def __init__(self, parent=None): super(ModbusTypeBox, self).__init__(parent) def paint(self, painter, option, index): # 首先绘制背景颜色,与其他委托保持一致 if index.data(Qt.BackgroundRole): height = option.rect.height() top = option.rect.top() paint_rect = option.rect paint_rect.setHeight(max(46, height - 4)) paint_rect.moveTop(top + 2) painter.fillRect(paint_rect, index.data(Qt.BackgroundRole)) if (index.column() == 5) and index.row() not in self.parent().model.editableList: try: data = self.parent().model.datas[index.row()] comBox = str('cb' + str(index.row()) + str(index.column())) # 创建容器widget containerWidget = QWidget() containerWidget.setStyleSheet("QComboBox { background-color: transparent; }") # 创建新的comboBox,设置正确的父对象 comboBox = QComboBox(containerWidget) comboBox.setObjectName('ModbusTypeBox') comboBox.setEditable(True) comboBox.lineEdit().setAlignment(Qt.AlignCenter) comboBox.index = [index.row(), index.column()] # 添加选项 items = ['Coil Status', 'Input Status', 'Input Register', 'Holding Register'] comboBox.addItems(items) # 设置当前值 try: currentValue = self.parent().model.datas[index.row()][index.column()] if currentValue in [0, 1]: comboBox.setCurrentIndex(currentValue) elif currentValue in [3, 4]: comboBox.setCurrentIndex(currentValue - 1) else: comboBox.setCurrentIndex(0) except (IndexError, RuntimeError, AttributeError): comboBox.setCurrentIndex(0) # 连接信号 comboBox.currentIndexChanged.connect(self.indexChange) # 设置启用状态 if str(data[index.column()]): comboBox.setEnabled(False) comboBox.setProperty('disabled', True) else: comboBox.setEnabled(True) comboBox.setProperty('disabled', False) # 创建布局 hboxLayout = QHBoxLayout() hboxLayout.addWidget(comboBox) hboxLayout.setContentsMargins(2, 2, 2, 2) containerWidget.setLayout(hboxLayout) # 设置索引widget self.parent().setIndexWidget(index, containerWidget) self.parent().openPersistentEditor(index) # 保存comboBox引用到委托对象,以便refreshComboBox能够找到它 setattr(self, comBox, comboBox) except Exception as e: # 捕获异常,确保paint方法不会崩溃 print(f"创建ModbusTypeBox时出错 (行 {index.row()}, 列 {index.column()}): {e}") pass def indexChange(self): sender = self.sender() index = sender.currentIndex() if index in [2, 3]: index += 1 self.parent().model.datas[sender.index[0]][sender.index[1]] = index class ModbusVarModelBox(QItemDelegate): def __init__(self, parent=None, comBoxColumn = 10): super(ModbusVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def paint(self, painter, option, index): # 首先绘制背景颜色,与其他委托保持一致 if index.data(Qt.BackgroundRole): height = option.rect.height() top = option.rect.top() paint_rect = option.rect paint_rect.setHeight(max(46, height - 4)) paint_rect.moveTop(top + 2) painter.fillRect(paint_rect, index.data(Qt.BackgroundRole)) # 本地值、模拟值、远程值 对应 0, 1, 2 if (index.column() == self.comBoxColumn) and not self.parent().indexWidget(index): try: data = self.parent().model.datas[index.row()] comBox = str('cb' + str(index.row()) + str(index.column())) # 创建容器widget containerWidget = QWidget() containerWidget.setStyleSheet("QComboBox { background-color: transparent; }") # 创建新的comboBox,设置正确的父对象 comboBox = QComboBox(containerWidget) comboBox.setObjectName('ModbusVarModelBox') comboBox.setEditable(True) comboBox.lineEdit().setAlignment(Qt.AlignCenter) comboBox.index = [index.row(), index.column()] # 添加选项 items = ['本地值', '模拟值', '远程值'] comboBox.addItems(items) # 连接信号 comboBox.currentIndexChanged.connect(self.indexChange) # 设置当前值 try: currentValue = str(self.parent().model.datas[index.row()][index.column()]) comboBox.setCurrentText(currentValue) except (IndexError, RuntimeError, AttributeError): # 如果设置值失败,使用默认值 comboBox.setCurrentIndex(0) # 创建布局 hboxLayout = QHBoxLayout() hboxLayout.addWidget(comboBox) hboxLayout.setContentsMargins(2, 2, 2, 2) containerWidget.setLayout(hboxLayout) # 设置索引widget self.parent().setIndexWidget(index, containerWidget) self.parent().openPersistentEditor(index) # 保存comboBox引用到委托对象,以便refreshComboBox能够找到它 setattr(self, comBox, comboBox) except Exception as e: # 捕获异常,确保paint方法不会崩溃 print(f"创建comboBox时出错 (行 {index.row()}, 列 {index.column()}): {e}") pass def indexChange(self): sender = self.sender() modbusType = self.parent().modbusType index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] ModbusVarManage.editVarModel(name, text, modbusType) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class HartVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 8): super(HartVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][1] HartVarManage.editVarModel(name, text) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class TcRtdVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 11): super(TcRtdVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] TcRtdManage.editVarModel(name, text) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class AnalogVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 10): super(AnalogVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][3] AnalogManage.editVarModel(name, text, ) self.parent().model.datas[sender.index[0]][sender.index[1]] = text class HartSimulateVarModelBox(ModbusVarModelBox): def __init__(self, parent=None, comBoxColumn = 9): super(HartSimulateVarModelBox, self).__init__(parent) self.comBoxColumn = comBoxColumn def indexChange(self): sender = self.sender() index = sender.currentIndex() text = sender.itemText(index) name = self.parent().model.datas[sender.index[0]][1] HartSimulateVarManage.editVarModel(name, text) self.parent().model.datas[sender.index[0]][sender.index[1]] = text