公司动态

基于QtPy (PySide6) 的PLC-HMI工程实战记录(七)创建适合HMI项目的数字输入/显示类部件

📅 2026/8/28 5:09:06
基于QtPy (PySide6) 的PLC-HMI工程实战记录(七)创建适合HMI项目的数字输入/显示类部件
HMI项目的输入/显示类部件比如一个QSpinBox或者QLineEdit用于设定电机转速它的实时更新来自于下位机实体PLC的通信设置完新的数值后再把新数值通信传回下位机实体PLC也就是上位机中常见的“读/写”双向操作。如果只是简单将实体PLC的变量与QSpinBox的设定显示值连接那么有可能数字还没设完就可能被周期更新的下位数据刷新掉或者是只设置了一半半成品的设置值被传递到了下位PLC这显然不符合工程的实际需求。为此自定义数字输入/显示类部件主要具备以下特性1、当部件获得焦点开始设定修改输入值下位通信的内容不再更新部件的显示值直至设置完毕。2、用Esc和回车键来控制本次输入的有效性。3、只有按下回车键后当前的设定值才刷新下位PLC。以QLineEdit为例场景重现的代码from PySide6.QtCore import QTimer, Signal, Qt from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit def add_i(): i int(plc_vari.text()) i 1 plc_vari.setText(str(i)) # --------测试Demo-------- if __name__ __main__: app QApplication([]) w QWidget() lay QVBoxLayout(w) # 模拟下位PLC数据 plc_vari QLineEdit() plc_vari.setText(0) # 上位画面中的输入框 line QLineEdit() line.setText(0) timer QTimer() timer.timeout.connect(add_i) timer.start(1000) lay.addWidget(line) lay.addWidget(plc_vari) w.resize(400, 150) w.show() # 双向连接hmi上位画面对PLC数据的读写 plc_vari.textChanged.connect(line.setText) line.textChanged.connect(plc_vari.setText) app.exec()改进后的代码from PySide6.QtCore import Qt, Signal, QTimer from PySide6.QtWidgets import QLineEdit, QApplication, QWidget, QVBoxLayout, QSpinBox, QDoubleSpinBox class HmiQDoubleSpinBox(QDoubleSpinBox): editConfirmed Signal(str) def __init__(self, bond_variNone, parentNone): super().__init__(parent) self._is_editing False self._original_value 0.0 if bond_vari: bond_vari.textChanged.connect(lambda x:self.setValue(float(x))) self.editConfirmed.connect(bond_vari.setText) def focusInEvent(self, event): 获得焦点开始编辑会话保存原始内容 super().focusInEvent(event) self._original_value self.value() self._is_editing True def setValue(self, val: float): 重写setText 用户正在编辑时外部调用setText直接忽略 内部提交确认时先关闭编辑标记再调用父类setText if self._is_editing: # 处于编辑模式外来信号/代码调用setText不生效 return super().setValue(val) def keyPressEvent(self, event): key event.key() if key Qt.Key_Escape: # Esc放弃修改恢复原始内容结束编辑 self._is_editing False super().setValue(self._original_value) self.clearFocus() event.accept() return elif key in (Qt.Key_Return, Qt.Key_Enter): # 回车确认接受当前输入内容结束编辑触发editConfirmed new_value self.value() self._is_editing False super().setValue(new_value) self.editConfirmed.emit(str(new_value)) self.clearFocus() event.accept() return super().keyPressEvent(event) def focusOutEvent(self, event): 失去焦点回退原始值取消编辑 if self._is_editing: self._is_editing False super().setValue(self._original_value) super().focusOutEvent(event) def add_i(): i float(label.text()) i 0.1 label.setText(str(i)) # --------测试Demo-------- if __name__ __main__: app QApplication([]) w QWidget() lay QVBoxLayout(w) label QLineEdit() label.setText(0) line HmiQDoubleSpinBox(bond_varilabel) line.setValue(0) timer QTimer() timer.timeout.connect(add_i) timer.start(1000) lay.addWidget(line) lay.addWidget(label) w.resize(400, 150) w.show() app.exec()最终的项目代码################################################################################ # 定制的窗口部件 ################################################################################ from PySide6.QtCore import Signal, Qt from PySide6.QtWidgets import QLineEdit, QWidget, QSpinBox, QDoubleSpinBox # 自定义的QLineEdit class HmiLineEdit(QLineEdit): hmi_textChanged Signal(str) def __init__(self, arg1None, parentNone): 兼容Qt Designer提升控件两种调用形式 HmiLineEdit(text:str, parent) HmiLineEdit(parent) if isinstance(arg1, str): # 第一个参数是文本 super().__init__(arg1, parent) else: # 第一个参数是parent控件 super().__init__(parentarg1) def focusInEvent(self, event): 获得焦点开始编辑会话保存原始内容 super().focusInEvent(event) self._original_text self.text() self._is_editing True def setText(self, text: str): 重写setText 用户正在编辑时外部调用setText直接忽略 内部提交确认时先关闭编辑标记再调用父类setText if self._is_editing: # 处于编辑模式外来信号/代码调用setText不生效 return super().setText(text) def keyPressEvent(self, event): key event.key() if key Qt.Key_Escape: # Esc放弃修改恢复原始内容结束编辑 self._is_editing False super().setText(self._original_text) self.clearFocus() event.accept() return elif key in (Qt.Key_Return, Qt.Key_Enter): # 回车确认接受当前输入内容结束编辑触发editConfirmed new_text self.text() self._is_editing False super().setText(new_text) self.hmi_textChanged.emit(new_text) self.clearFocus() event.accept() return super().keyPressEvent(event) def focusOutEvent(self, event): 失去焦点回退原始值取消编辑 if self._is_editing: self._is_editing False super().setText(self._original_text) super().focusOutEvent(event) # 自定义的QSpinBox class HmiSpinBox(QSpinBox): hmi_valueChanged Signal(int) def __init__(self, parentNone): super().__init__(parent) self._is_editing False def focusInEvent(self, event): 获得焦点开始编辑会话保存原始内容 super().focusInEvent(event) self._original_value self.value() self._is_editing True def setValue(self, value: int): 重写setText 用户正在编辑时外部调用setValue直接忽略 内部提交确认时先关闭编辑标记再调用父类setValue if self._is_editing: # 处于编辑模式外来信号/代码调用setValue不生效 return super().setValue(value) def keyPressEvent(self, event): key event.key() if key Qt.Key_Escape: # Esc放弃修改恢复原始内容结束编辑 self._is_editing False super().setValue(self._original_value) self.clearFocus() event.accept() return elif key in (Qt.Key_Return, Qt.Key_Enter): # 回车确认接受当前输入内容结束编辑触发editConfirmed new_text self.text() self._is_editing False super().setValue(int(new_text)) self.hmi_valueChanged.emit(int(new_text)) self.clearFocus() event.accept() return super().keyPressEvent(event) def focusOutEvent(self, event): 失去焦点回退原始值取消编辑 if self._is_editing: self._is_editing False super().setValue(self._original_value) super().focusOutEvent(event) # 自定义的QSpinBox class HmiDoubleSpinBox(QDoubleSpinBox): hmi_valueChanged Signal(float) def __init__(self, parentNone): 兼容Qt Designer提升控件两种调用形式 HmiLineEdit(text:str, parent) HmiLineEdit(parent) super().__init__(parent) self._is_editing False def focusInEvent(self, event): 获得焦点开始编辑会话保存原始内容 super().focusInEvent(event) self._original_value self.value() self._is_editing True def setValue(self, value: float): 重写setValue 用户正在编辑时外部调用setValue直接忽略 内部提交确认时先关闭编辑标记再调用父类setValue if self._is_editing: # 处于编辑模式外来信号/代码调用setValue不生效 return super().setValue(value) def keyPressEvent(self, event): key event.key() if key Qt.Key_Escape: # Esc放弃修改恢复原始内容结束编辑 self._is_editing False super().setValue(self._original_value) self.clearFocus() event.accept() return elif key in (Qt.Key_Return, Qt.Key_Enter): # 回车确认接受当前输入内容结束编辑触发editConfirmed new_text self.text() self._is_editing False super().setValue(float(new_text)) self.hmi_valueChanged.emit(float(new_text)) self.clearFocus() event.accept() return super().keyPressEvent(event) def focusOutEvent(self, event): 失去焦点回退原始值取消编辑 if self._is_editing: self._is_editing False super().setValue(self._original_value) super().focusOutEvent(event)