import sys import pandas as pd from PyQt5 import QtWidgets from PyQt5.QtCore import Qt from PyQt5.Qt import * from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QMessageBox, QStackedWidget, QTabWidget, QFileDialog, QPushButton, QGraphicsDropShadowEffect from PyQt5.QtCore import QSize from ctypes import POINTER, cast from ctypes.wintypes import MSG from win32 import win32api, win32gui from win32.lib import win32con from model.ProjectModel.VarManage import GlobalVarManager from UI.Main.MainLeft import MainLeft from UI.Main.MainTop import MainTop from ..ProjectManages.ProjectWidget import ProjectWidgets from ..VarManages.VarWidget import VarWidgets, HartWidgets, TcRtdWidgets, AnalogWidgets, HartSimulateWidgets from UI.VarManages.VarTable import RpcVarTableView from ..UserManage.UserWidget import UserWidgets from ..TrendManage.TrendWidget import TrendWidgets from ..Setting.Setting import SettingWidget from model.ClientModel.Client import Client from utils import Globals from utils.DBModels.InitParameterDB import InitParameterDB from UI.ProfibusWidgets.ProfibusWindow import ProfibusWidgets from UI.ProcedureManager.ProcedureManager import ProcedureManager from UI.ControlManage.ControlWidget import getControlWidget import qtawesome class CommonHelper: def __init__(self): pass @staticmethod def readQss(style): with open(style, "r", encoding='utf-8') as f: return f.read() class MainWindow(QMainWindow): BORDER_WIDTH = 5 #设圆角 def __init__(self): super(MainWindow, self).__init__() InitParameterDB() self.setupUi() # self.setStyleSheet(CommonHelper.readQss('Static/main.qss')) self.setWindowFlags(Qt.FramelessWindowHint|Qt.WindowSystemMenuHint) self.SHADOW_WIDTH = 0 #边框距离 self.isLeftPressDown = False #鼠标左键是否按下 self.dragPosition = 0 #拖动时坐标 self.padding = 4 self.Numbers = self.enum(UP=0, DOWN=1, LEFT=2, RIGHT=3, LEFTTOP=4, LEFTBOTTOM=5, RIGHTBOTTOM=6, RIGHTTOP=7, NONE=8) #枚举参数 self.setMinimumHeight(900) #窗体最小高度 self.setMinimumWidth(1600) #窗体最小宽度 self.dir = self.Numbers.NONE #初始鼠标状态 #开启鼠标追踪 self.setMouseTracking(True) self.centralwidget.setMouseTracking(True) self.leftWidget.setMouseTracking(True) self.rightWidget.setMouseTracking(True) self.topWidget.setMouseTracking(True) Globals.setValue('MainWindows', self) #设置全局变量 #初始化接口 # self.windowEffect = WindowEffect() # # 添加DWM阴影效果 # self.windowEffect.addWindowAnimation(self.winId()) # self.windowEffect.addShadowEffect(self.winId()) # 解决报错 self.windowHandle().screenChanged.connect(self.__onScreenChanged) def setupUi(self): self.showMaximized() self.centralwidget = QtWidgets.QWidget(self) self.centralwidget.setObjectName("centralwidget") self.leftWidget = MainLeft() self.leftWidget.setObjectName("leftWidget") self.projectWidget = ProjectWidgets() self.rightWidget = QStackedWidget() self.rightWidget.setObjectName("rightWidget") self.rightWidget.addWidget(self.projectWidget) #添加导入变量按钮 self.importVarButton = QPushButton('导入变量') self.importVarButton.setIcon(qtawesome.icon('fa5s.file-import', color = '#059669')) self.importVarButton.setObjectName('importBtn') self.importVarButton.setIconSize(QSize(22, 22)) self.importVarButton.setFlat(True) self.importVarButton.clicked.connect(self.loadVar) self.createWidgets() self.setCentralWidget(self.centralwidget) self.setWindowOpacity(0.995) # 设置窗口透明度 self.topWidget = MainTop(self) self.topWidget.setObjectName('topWidget') # 初始化搜索框状态(在topWidget创建后) self.topWidget.updateSearchPlaceholder() self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout.addWidget(self.leftWidget) self.horizontalLayout.addWidget(self.rightWidget) self.horizontalLayout.setStretch(0, 1) self.horizontalLayout.setStretch(1, 10) self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setSpacing(1) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.addWidget(self.topWidget) self.verticalLayout.addLayout(self.horizontalLayout) self.verticalLayout.setStretch(0, 1) self.verticalLayout.setStretch(1, 18) self.verticalLayout.setSpacing(0) # self.setAttribute(Qt.WA_TranslucentBackground) # 设置窗口背景透明 self.setWindowFlag(Qt.FramelessWindowHint) # 添加阴影效果(接近Windows原生) # 添加圆角(QSS) # self.centralwidget.setStyleSheet("") def createWidgets(self): self.ModbusTcpMasterWidget = VarWidgets('ModbusTcpMaster') self.ModbusTcpSlaveWidget = VarWidgets('ModbusTcpSlave') self.ModbusRtuMasterWidget = VarWidgets('ModbusRtuMaster') self.ModbusRtuSlaveWidget = VarWidgets('ModbusRtuSlave') self.userWidget = UserWidgets() self.procedureManagerWidget = ProcedureManager() self.controlWidget = getControlWidget() modbusWidgetList = [self.ModbusTcpMasterWidget, self.ModbusTcpSlaveWidget, self.ModbusRtuMasterWidget, self.ModbusRtuSlaveWidget] modbusNameList = ['MODBUSTCP主站', 'MODBUSTCP从站', 'MODBUSRTU主站', 'MODBUSRTU从站',] self.profibusWidget = ProfibusWidgets() self.trendWidget = TrendWidgets() self.SettingWidget = SettingWidget() self.hartWidget = HartWidgets() self.tcrtdWidget = TcRtdWidgets() self.analogWidget = AnalogWidgets() self.hartsimulateWidget = HartSimulateWidgets() self.rpcVarTableWidget = RpcVarTableView() self.userWidget.setObjectName('userWidget') self.projectWidget.setObjectName('projectWidget') self.trendWidget.setObjectName('trendWidget') # self.ModBusWidget.setObjectName('varWidget') self.analogWidget.setObjectName('analogWidget') self.hartsimulateWidget.setObjectName('hartsimulateWidget') self.varManageTabWidget = QTabWidget() self.varManageTabWidget.setObjectName("varManageTabWidget") self.varManageTabWidget.tabBar().setObjectName('varManageTabBar') for widget, name in zip(modbusWidgetList, modbusNameList): widget.setObjectName('varWidget') self.varManageTabWidget.addTab(widget, name) self.varManageTabWidget.addTab(self.analogWidget,'IO') self.varManageTabWidget.addTab(self.tcrtdWidget,'TCRTD') self.varManageTabWidget.addTab(self.hartWidget,'HART读取') self.varManageTabWidget.addTab(self.hartsimulateWidget,'HART模拟') self.varManageTabWidget.addTab(self.profibusWidget,'PROFIBUS') self.varManageTabWidget.addTab(self.rpcVarTableWidget, '远程通讯') self.varManageTabWidget.setCornerWidget(self.importVarButton) self.rightWidget.addWidget(self.varManageTabWidget) self.rightWidget.addWidget(self.trendWidget) self.rightWidget.addWidget(self.userWidget) self.rightWidget.addWidget(self.SettingWidget) self.rightWidget.addWidget(self.procedureManagerWidget) self.rightWidget.addWidget(self.controlWidget) self.rightWidget.widget(1) self.leftWidget.createProject.clicked.connect(lambda: self.exButtonClicked(0)) self.leftWidget.varMag.clicked.connect(self.varShow) self.leftWidget.trendMag.clicked.connect(self.reFreshTrendWidget) self.leftWidget.userMag.clicked.connect(lambda: self.exButtonClicked(3)) self.leftWidget.protocolMag.clicked.connect(self.showSetting) self.leftWidget.procedureMag.clicked.connect(lambda: self.initProcedureDB()) self.leftWidget.controlMag.clicked.connect(lambda: self.showControlSystem()) def delecteWidget(self): for i in reversed(range(1, self.rightWidget.count())): widget = self.rightWidget.widget(i) if widget: self.rightWidget.removeWidget(widget) widget.deleteLater() def exButtonClicked(self, index): # print(Globals.getValue('currentPro')) if Globals.getValue('currentPro') == -1 and index not in [0, 3]: return -1 self.rightWidget.setCurrentIndex(index) def showSetting(self): if Globals.getValue('currentPro') == -1: return self.SettingWidget.setupUI() self.rightWidget.setCurrentIndex(4) def varShow(self): self.exButtonClicked(1) Globals.setValue('SearchWidget', 1) def reFreshTrendWidget(self): if Globals.getValue('currentPro') == -1: return # self.trendWidget.getMems() self.exButtonClicked(2) def initProcedureDB(self): if Globals.getValue('currentPro') == -1: return # self.procedureManagerWidget.initDB() # self.procedureManagerWidget.initUI() self.exButtonClicked(5) def showControlSystem(self): """显示控制系统界面""" if Globals.getValue('currentPro') == -1: return # 重新加载控制系统(确保规则正确加载) self.controlWidget.reloadControlSystem() self.exButtonClicked(6) def loadVar(self): file_path, _ = QFileDialog.getOpenFileName(self, '选择Excel文件', '', 'Excel Files (*.xlsx *.xls)') if not file_path: return excelData = pd.read_excel(file_path, sheet_name=None) allVarNames = [] for sheet_name, data in excelData.items(): if '变量名' in data.columns: # 确保所有变量名转换为字符串 allVarNames.extend(data['变量名'].astype(str).tolist()) if len(allVarNames) != len(set(allVarNames)): # 确保重复名称列表中的元素都是字符串 repeated_names = [str(name) for name in set(allVarNames) if allVarNames.count(name) > 1] QMessageBox.warning(self, '警告', f'发现重复的变量名: {", ".join(repeated_names)}') return allErrInfo = [] for sheet_name, data in excelData.items(): if '变量名' in data.columns: importInfo = GlobalVarManager.importVar(sheet_name, data) allErrInfo.append(importInfo) if allErrInfo: # 过滤掉空字符串 allErrInfo = [info for info in allErrInfo if info] if not allErrInfo: QMessageBox.information(self, '导入信息', '所有变量导入成功') else: errorMessages = '\n'.join(allErrInfo) QMessageBox.information(self, '导入信息', errorMessages) def enum(self,**enums): return type('Enum', (), enums) def region(self,cursorGlobalPoint): #获取窗体在屏幕上的位置区域,tl为topleft点,rb为rightbottom点 rect = self.rect() tl = self.mapToGlobal(rect.topLeft()) rb = self.mapToGlobal(rect.bottomRight()) x = cursorGlobalPoint.x() y = cursorGlobalPoint.y() # 检查是否在标题栏区域,如果不在则不允许调整大小 if not self._isInTitleBar(cursorGlobalPoint): self.dir = self.Numbers.NONE self.setCursor(QCursor(Qt.ArrowCursor)) return if(tl.x() + self.padding >= x and tl.x() <= x and tl.y() + self.padding >= y and tl.y() <= y): #左上角 self.dir = self.Numbers.LEFTTOP self.setCursor(QCursor(Qt.SizeFDiagCursor)) #设置鼠标形状 elif(x >= rb.x() - self.padding and x <= rb.x() and y >= rb.y() - self.padding and y <= rb.y()): #右下角 self.dir = self.Numbers.RIGHTBOTTOM self.setCursor(QCursor(Qt.SizeFDiagCursor)) elif(x <= tl.x() + self.padding and x >= tl.x() and y >= rb.y() - self.padding and y <= rb.y()): #左下角 self.dir = self.Numbers.LEFTBOTTOM self.setCursor(QCursor(Qt.SizeBDiagCursor)) elif(x <= rb.x() and x >= rb.x() - self.padding and y >= tl.y() and y <= tl.y() + self.padding): #右上角 self.dir = self.Numbers.RIGHTTOP self.setCursor(QCursor(Qt.SizeBDiagCursor)) elif(x <= tl.x() + self.padding and x >= tl.x()): #左边 self.dir = self.Numbers.LEFT self.setCursor(QCursor(Qt.SizeHorCursor)) elif( x <= rb.x() and x >= rb.x() - self.padding): #右边 self.dir = self.Numbers.RIGHT self.setCursor(QCursor(Qt.SizeHorCursor)) elif(y >= tl.y() and y <= tl.y() + self.padding): #上边 self.dir = self.Numbers.UP self.setCursor(QCursor(Qt.SizeVerCursor)) elif(y <= rb.y() and y >= rb.y() - self.padding): #下边 self.dir = self.Numbers.DOWN self.setCursor(QCursor(Qt.SizeVerCursor)) else: #默认 self.dir = self.Numbers.NONE self.setCursor(QCursor(Qt.ArrowCursor)) def mouseReleaseEvent(self,event): if(event.button() == Qt.LeftButton): self.isLeftPressDown = False if(self.dir != self.Numbers.NONE): self.releaseMouse() self.setCursor(QCursor(Qt.ArrowCursor)) def mousePressEvent(self,event): if(event.button()==Qt.LeftButton): # 检查鼠标点击是否在标题栏区域 if self._isInTitleBar(event.globalPos()): self.isLeftPressDown=True if(self.dir != self.Numbers.NONE): self.mouseGrabber() else: self.dragPosition = event.globalPos() - self.frameGeometry().topLeft() else: # 不在标题栏区域,不处理拖拽 self.isLeftPressDown = False self.dir = self.Numbers.NONE def mouseMoveEvent(self,event): gloPoint = event.globalPos() rect = self.rect() tl = self.mapToGlobal(rect.topLeft()) rb = self.mapToGlobal(rect.bottomRight()) if(not self.isLeftPressDown): self.region(gloPoint) else: if(self.dir != self.Numbers.NONE): rmove=QRect(tl, rb) if(self.dir==self.Numbers.LEFT): if(rb.x() - gloPoint.x() <= self.minimumWidth()): rmove.setX(tl.x()) else: rmove.setX(gloPoint.x()) elif(self.dir==self.Numbers.RIGHT): rmove.setWidth(gloPoint.x() - tl.x()) elif(self.dir==self.Numbers.UP): if(rb.y() - gloPoint.y() <= self.minimumHeight()): rmove.setY(tl.y()) else: rmove.setY(gloPoint.y()) elif(self.dir==self.Numbers.DOWN): rmove.setHeight(gloPoint.y() - tl.y()) elif(self.dir==self.Numbers.LEFTTOP): if(rb.x() - gloPoint.x() <= self.minimumWidth()): rmove.setX(tl.x()) else: rmove.setX(gloPoint.x()) if(rb.y() - gloPoint.y() <= self.minimumHeight()): rmove.setY(tl.y()) else: rmove.setY(gloPoint.y()) elif(self.dir==self.Numbers.RIGHTTOP): rmove.setWidth(gloPoint.x() - tl.x()) rmove.setY(gloPoint.y()) elif(self.dir==self.Numbers.LEFTBOTTOM): rmove.setX(gloPoint.x()) rmove.setHeight(gloPoint.y() - tl.y()) elif(self.dir==self.Numbers.RIGHTBOTTOM): rmove.setWidth(gloPoint.x() - tl.x()) rmove.setHeight(gloPoint.y() - tl.y()) else: pass self.setGeometry(rmove) else: # 只有在标题栏区域才能拖拽移动窗口 if self._isInTitleBar(event.globalPos()): self.move(event.globalPos() - self.dragPosition) event.accept() def _isInTitleBar(self, globalPos): """ 检查鼠标位置是否在标题栏区域 :param globalPos: 鼠标全局位置 :return: 是否在标题栏区域 """ # 获取标题栏的全局位置 titleBarRect = self.topWidget.geometry() titleBarGlobalTopLeft = self.topWidget.mapToGlobal(titleBarRect.topLeft()) titleBarGlobalBottomRight = self.topWidget.mapToGlobal(titleBarRect.bottomRight()) # 创建全局矩形区域 titleBarGlobalRect = QRect(titleBarGlobalTopLeft, titleBarGlobalBottomRight) # 检查鼠标是否在标题栏区域内 return titleBarGlobalRect.contains(globalPos) def __isWindowMaximized(self, hWnd) -> bool: """ Determine whether the window is maximized """ # GetWindowPlacement() returns the display state of the window and the restored, # maximized and minimized window position. The return value is tuple windowPlacement = win32gui.GetWindowPlacement(hWnd) if not windowPlacement: return False return windowPlacement[1] == win32con.SW_MAXIMIZE def __monitorNCCALCSIZE(self, msg: MSG): """ 调整窗口大小 """ monitor = win32api.MonitorFromWindow(msg.hWnd) # If the display information is not saved, return directly if monitor is None and not self.__monitorInfo: return elif monitor is not None: self.__monitorInfo = win32api.GetMonitorInfo(monitor) # adjust the size of window params = cast(msg.lParam, POINTER(NCCALCSIZE_PARAMS)).contents params.rgrc[0].left = self.__monitorInfo['Work'][0] params.rgrc[0].top = self.__monitorInfo['Work'][1] params.rgrc[0].right = self.__monitorInfo['Work'][2] params.rgrc[0].bottom = self.__monitorInfo['Work'][3] def __onScreenChanged(self): hWnd = int(self.windowHandle().winId()) win32gui.SetWindowPos(hWnd, None, 0, 0, 0, 0, win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_FRAMECHANGED) def closeEvent(self, event): reply = QMessageBox.question(self, 'Quit', "是否要退出程序?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: try: # 保存控制系统配置 if hasattr(self, 'controlWidget') and self.controlWidget: self.controlWidget.controlManager.shutdown() Client.close() except Exception as e: print(f"关闭程序时出错: {e}") event.accept() else: event.ignore() if __name__ == '__main__': app = QApplication(sys.argv) app.setStyle(QtWidgets.QStyleFactory.create('Fusion')) app.setStyleSheet(CommonHelper.readQss('Static/main.qss')) # print(QtWidgets.QStyleFactory.keys()) ex = MainWindow() ex.show() sys.exit(app.exec_())