diff --git a/Static/Area.qss b/Static/Area.qss index c59896c..092f634 100644 --- a/Static/Area.qss +++ b/Static/Area.qss @@ -132,7 +132,6 @@ QPushButton#initAddDeviceButton:hover, QPushButton#initAreaAddButton:hover, QPus - QLabel#wirteDIDOareaMessLabel, QLabel#wirteDIDOareaValueLabel{ font-size: 16px; @@ -189,6 +188,13 @@ QLabel#areaValueLabel{ } +QLabel#deviceNameLabel, QLabel#pvUpperLimitLabel, QLabel#pvLowerLimitLabel, QLabel#pvUnitLabel{ + + font:28px; + + +} + QLineEdit#byteLineEdit{ font-size: 20px; @@ -245,7 +251,19 @@ QLineEdit#areaLineEdit { } +QLineEdit#deviceName, QLineEdit#pvUpperLimit, QLineEdit#pvLowerLimit, QLineEdit#pvUnit{ + + font: 28px; + + border-top: none; + + border-left: none; + + border-right: none; + + border-bottom: 2px solid gary; +} QComboBox#dataTypeCombox, QComboBox#orderCombox{ diff --git a/Static/Main.qss b/Static/Main.qss index 4e22a06..b9c0488 100644 --- a/Static/Main.qss +++ b/Static/Main.qss @@ -25,6 +25,8 @@ QTabBar{ background-color: #D3E3FD; + font:28px; + } @@ -74,7 +76,7 @@ QTabBar#areaTabBar::tab:selected { QDockWidget{ - font: 18px; + font: 28px; font: bold; @@ -82,6 +84,7 @@ QDockWidget{ border: none; + color: white; } @@ -95,13 +98,12 @@ QDockWidget::title{ text-align: center; - } QPushButton#startProtocolBtn, QPushButton#switchBtn, QPushButton#switchTouchBtn{ - font-size: 20px; + font-size: 25px; border: none; @@ -111,7 +113,7 @@ QPushButton#startProtocolBtn, QPushButton#switchBtn, QPushButton#switchTouchBtn{ QPushButton#startProtocolBtn:hover, QPushButton#switchBtn:hover, QPushButton#switchTouchBtn::hover{ - font-size: 20px; + font-size: 25px; font: bold; @@ -125,12 +127,89 @@ QPushButton#startProtocolBtn:hover, QPushButton#switchBtn:hover, QPushButton#swi QPushButton#startProtocolBtn:checked, QPushButton#switchBtn:checked, QPushButton#switchTouchBtn:checked{ + font-size: 25px; + + border: none; + +} + +QPushButton#okButton{ + + width: 54.75px; + + height: 40px; + + background-color: #09F738; + + border-radius: 5px; + + color: #FFFFFF; + + font-family: ".SFNSDisplay-Medium"; + + font-size: 20px; + + font-weight: 520; + +} + +QPushButton#cancelButton{ + + width: 54.75px; + + height: 40px; + + background-color: #EE1169; + + border-radius: 5px; + + color: white; + + font-family: ".SFNSDisplay-Medium"; + font-size: 20px; + + font-weight: 520; + +} + +QPushButton#okButton:hover, QPushButton#cancelButton:hover{ + font: bold; + + border: none; + + margin-bottom: -1px; + +} + +QPushButton#closeButton, QPushButton#minButton{ + border: none; } +QPushButton#confirmButton, QPushButton#exitButton{ + + width: 54.75px; + + height: 40px; + + background-color: #5b8cff; + + border-radius: 5px; + + color: #FFFFFF; + + font-family: ".SFNSDisplay-Medium"; + + font-size: 17px; + + font-weight: 520; + +} + + QWidget#MainWindow{ @@ -138,16 +217,25 @@ QWidget#MainWindow{ border: none; + font: 20px; } QWidget#deviceWidget{ - background-color: white; + background-color: white; + +} + - + + +QDialog#deviceDialog{ + + background-color: #FFFFFF; } + QTabWidget#areaTabWidget{ border: none; @@ -159,7 +247,7 @@ QTabWidget#areaTabWidget{ QLabel#batteryLabel{ - font-size: 20px; + font-size: 23px; font: bold; @@ -222,4 +310,28 @@ QRadioButton#valueRadio::indicator:disabled:checked{ image: url(Static/radioDisableOn.png); +} + +QLineEdit#deviceMesEdit{ + + background-color: #f5f8f9; + + border: none; + + width: 239px; + + height: 40px; + + border-radius: 5px; + + padding-left : 10px; + + color: #889399; + + font-family: ".SFNSDisplay-Regular"; + + font-size: 20px; + + font-weight: 520; + } \ No newline at end of file diff --git a/Static/close.png b/Static/close.png index 4374906..8f3c552 100644 Binary files a/Static/close.png and b/Static/close.png differ diff --git a/Static/min.png b/Static/min.png new file mode 100644 index 0000000..7665d0b Binary files /dev/null and b/Static/min.png differ diff --git a/UI/AreaTabWidget.py b/UI/AreaTabWidget.py index 96a9dd7..890f683 100644 --- a/UI/AreaTabWidget.py +++ b/UI/AreaTabWidget.py @@ -1,10 +1,8 @@ import re -from socket import AI_ADDRCONFIG import sys import json import qtawesome -from tkinter import N -from functools import partial + from PyQt5.QtWidgets import QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QLabel, QPushButton, QLayout, \ QHBoxLayout, QComboBox, QLineEdit, QSpacerItem, QSizePolicy, QGridLayout, QMessageBox, QSplitter, QFrame diff --git a/UI/DeviceDialogWidget.py b/UI/DeviceDialogWidget.py index 5f599fa..6f64af5 100644 --- a/UI/DeviceDialogWidget.py +++ b/UI/DeviceDialogWidget.py @@ -1,62 +1,143 @@ + + +import sys import re -from PyQt5.QtWidgets import QDialog, QFormLayout, QLineEdit, QDialogButtonBox, QMessageBox -from PyQt5.QtGui import QIcon -from PyQt5.QtCore import Qt +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtCore import QSize, Qt +from PyQt5.Qt import * +from PyQt5.QtGui import QPixmap, QIcon +from PyQt5.QtWidgets import QHBoxLayout, QAbstractItemView, QVBoxLayout, QSplitter, \ + QApplication, QLabel, QGridLayout, QLineEdit, QComboBox, QTextEdit, QCheckBox,QVBoxLayout,QListView, QMainWindow, QStackedWidget, QMessageBox +from PyQt5.QtWidgets import QSizePolicy, QWidget + +from ctypes import POINTER, cast +from ctypes.wintypes import MSG + + +from PyQt5.QtWinExtras import QtWin + +from win32 import win32api, win32gui +from win32.lib import win32con +from windoweffect.window_effect import WindowEffect +from windoweffect.c_structures import MINMAXINFO, NCCALCSIZE_PARAMS from utils.DBModels.DeviceModels import DeviceDB + + class DeviceDialog(QDialog): + BORDER_WIDTH = 5 #设圆角 + def __init__(self,dataTypeAndModel,parent=None): super().__init__(parent) self.dataTypeAndModel = dataTypeAndModel self.initUI() def initUI(self): - layout = QFormLayout() + self.resize(306, 451) - deviceName = QLineEdit() - deviceName.setObjectName('deviceName') - - pvUpperLimit = QLineEdit() - pvUpperLimit.setObjectName('pvUpperLimit') + self.setObjectName('deviceDialog') + self.deviceNameEdit = QLineEdit() + self.deviceNameEdit.setPlaceholderText("设备名称") + self.deviceNameEdit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + self.deviceNameEdit.setObjectName('deviceMesEdit') + + self.pvUpperEdit = QLineEdit() + self.pvUpperEdit.setPlaceholderText("量程上限") + self.pvUpperEdit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + self.pvUpperEdit.setObjectName('deviceMesEdit') + + self.pvLowerEdit = QLineEdit() + self.pvLowerEdit.setPlaceholderText("量程下限") + self.pvLowerEdit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + self.pvLowerEdit.setObjectName('deviceMesEdit') - pvLowerLimit = QLineEdit() - pvLowerLimit.setObjectName('pvLowerLimit') + self.pvUnitEdit = QLineEdit() + self.pvUnitEdit.setPlaceholderText("单位") + self.pvUnitEdit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + self.pvUnitEdit.setObjectName('deviceMesEdit') - pvUnit = QLineEdit() - pvUnit.setObjectName('pvUnit') - layout.addRow("设备名:", deviceName) - layout.addRow("量程上限:", pvUpperLimit) - layout.addRow("量程下限:", pvLowerLimit) - layout.addRow("单位:", pvUnit) - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - ok_button = button_box.button(QDialogButtonBox.Ok) - ok_button.setText("确定") # 设置Ok按钮的文本 + self.confirmButton = QPushButton('确定') + self.confirmButton.setObjectName('confirmButton') + self.confirmButton.clicked.connect(self.checkInput) + enterShortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Return), self) + enterShortcut.activated.connect(self.confirmButton.click) - cancel_button = button_box.button(QDialogButtonBox.Cancel) - cancel_button.setText("取消") # 设置Cancel按钮的文本 - button_box.accepted.connect(self.checkInput) - button_box.rejected.connect(self.reject) + self.exitButton = QPushButton('取消') + self.exitButton.clicked.connect(self.close) + self.exitButton.setObjectName('exitButton') - layout.addRow(button_box) + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.addWidget(self.confirmButton) + self.horizontalLayout.addWidget(self.exitButton) + self.horizontalLayout.setSpacing(10) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.setWindowIcon(QIcon('Static/zhjt.ico')) - self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) # 去掉标题栏的问号 - self.setLayout(layout) - self.setWindowTitle("设备信息") + self.verticalLayout = QVBoxLayout(self) + + self.conVerLayout = QVBoxLayout() + self.conVerLayout.addWidget(QWidget(), 1) + self.conVerLayout.addWidget(self.deviceNameEdit, 1) + self.conVerLayout.addWidget(self.pvUpperEdit, 1) + self.conVerLayout.addWidget(self.pvLowerEdit, 1) + self.conVerLayout.addWidget(self.pvUnitEdit, 1) + self.conVerLayout.addLayout(self.horizontalLayout, 1) + self.conVerLayout.setSpacing(32) + self.conVerLayout.setContentsMargins(34, 26, 34, 50) + + # self.verticalLayout.addWidget(self.picLabel) + self.verticalLayout.addLayout(self.conVerLayout) + + self.verticalLayout.setSpacing(0) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + + self.setLayout(self.verticalLayout) + + # 设置模态窗口 + self.setWindowFlags(self.windowFlags() | Qt.Window) + self.setWindowModality(Qt.ApplicationModal) + + 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(451) #窗体最小高度 + self.setMinimumWidth(306) #窗体最小宽度 + self.dir = self.Numbers.NONE #初始鼠标状态 + + #开启鼠标追踪 + self.setMouseTracking(True) + + #初始化接口 + self.windowEffect = WindowEffect() + + # 添加DWM阴影效果 + self.windowEffect.addWindowAnimation(self.winId()) + self.windowEffect.addShadowEffect(self.winId()) + + # 解决报错 + self.windowHandle().screenChanged.connect(self.__onScreenChanged) + self.setWindowOpacity(0.995) # 设置窗口透明度 + self.setWindowFlag(Qt.FramelessWindowHint) def getParameters(self): - deviceName = self.findChild(QLineEdit, "deviceName").text() - pvUpperLimit = self.findChild(QLineEdit, "pvUpperLimit").text() - pvLowerLimit = self.findChild(QLineEdit, "pvLowerLimit").text() - pvUnit = self.findChild(QLineEdit, "pvUnit").text() + deviceName = self.deviceNameEdit.text() + self.dataTypeAndModel + pvUpperLimit = self.pvUpperEdit.text() + pvLowerLimit = self.pvLowerEdit.text() + pvUnit = self.pvUnitEdit.text() return deviceName, pvUpperLimit, pvLowerLimit, pvUnit + + + def checkInput(self): - titleName, pvUpperLimit, pvLowerLimit, pvUnit = self.getParameters() - deviceName = titleName + self.dataTypeAndModel + + deviceName, pvUpperLimit, pvLowerLimit, pvUnit = self.getParameters() + if not pvUpperLimit and not pvLowerLimit and not pvUnit: pass else: @@ -79,4 +160,229 @@ class DeviceDialog(QDialog): QMessageBox.warning(self, '警告', '设备名重复') return else: - self.accept() # 所有输入都是数字且不为空时接受对话框 \ No newline at end of file + self.accept() # 所有输入都是数字且不为空时接受对话框 + + + + 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(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): + self.isLeftPressDown=True + if(self.dir != self.Numbers.NONE): + self.mouseGrabber() + else: + self.dragPosition = event.globalPos() - self.frameGeometry().topLeft() + + 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: + self.move(event.globalPos() - self.dragPosition) + event.accept() + + def nativeEvent(self, eventType, message): + """ 接受windows发送的信息 """ + msg = MSG.from_address(message.__int__()) + if msg.message == win32con.WM_NCHITTEST: + # 解决 issue #2 and issue #7 + r = self.devicePixelRatioF() + xPos = (win32api.LOWORD(msg.lParam) - + self.frameGeometry().x()*r) % 65536 + yPos = win32api.HIWORD(msg.lParam) - self.frameGeometry().y()*r + w, h = self.width()*r, self.height()*r + lx = xPos < self.BORDER_WIDTH + rx = xPos + 9 > w - self.BORDER_WIDTH + ty = yPos < self.BORDER_WIDTH + by = yPos > h - self.BORDER_WIDTH + if lx and ty: + return True, win32con.HTTOPLEFT + elif rx and by: + return True, win32con.HTBOTTOMRIGHT + elif rx and ty: + return True, win32con.HTTOPRIGHT + elif lx and by: + return True, win32con.HTBOTTOMLEFT + elif ty: + return True, win32con.HTTOP + elif by: + return True, win32con.HTBOTTOM + elif lx: + return True, win32con.HTLEFT + elif rx: + return True, win32con.HTRIGHT + elif msg.message == win32con.WM_NCCALCSIZE: + if self.__isWindowMaximized(msg.hWnd): + self.__monitorNCCALCSIZE(msg) + return True, 0 + elif msg.message == win32con.WM_GETMINMAXINFO: + if self.__isWindowMaximized(msg.hWnd): + window_rect = win32gui.GetWindowRect(msg.hWnd) + if not window_rect: + return False, 0 + + # get the monitor handle + monitor = win32api.MonitorFromRect(window_rect) + if not monitor: + return False, 0 + + # get the monitor info + __monitorInfo = win32api.GetMonitorInfo(monitor) + monitor_rect = __monitorInfo['Monitor'] + work_area = __monitorInfo['Work'] + + # convert lParam to MINMAXINFO pointer + info = cast(msg.lParam, POINTER(MINMAXINFO)).contents + + # adjust the size of window + info.ptMaxSize.x = work_area[2] - work_area[0] + info.ptMaxSize.y = work_area[3] - work_area[1] + info.ptMaxTrackSize.x = info.ptMaxSize.x + info.ptMaxTrackSize.y = info.ptMaxSize.y + + # modify the upper left coordinate + info.ptMaxPosition.x = abs(window_rect[0] - monitor_rect[0]) + info.ptMaxPosition.y = abs(window_rect[1] - monitor_rect[1]) + return True, 1 + + return QWidget.nativeEvent(self, eventType, message) + + + 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) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + app.setStyle(QtWidgets.QStyleFactory.create('Fusion')) + # print(QtWidgets.QStyleFactory.keys()) + ex = LoginWidget() + ex.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/UI/DeviceWidget.py b/UI/DeviceWidget.py index ea5855b..34fdf9c 100644 --- a/UI/DeviceWidget.py +++ b/UI/DeviceWidget.py @@ -254,14 +254,13 @@ class DeviceTab(QTabWidget): if deviceName is None: dialog = DeviceDialog(self.dataTypeAndModel) if dialog.exec_() == QDialog.Accepted: - self.titleName, pvUpperLimit, pvLowerLimit, pvUnit = dialog.getParameters() - self.deviceName = self.titleName + self.dataTypeAndModel + self.deviceName, pvUpperLimit, pvLowerLimit, pvUnit = dialog.getParameters() DeviceDB().addDevice(deviceName = self.deviceName, proType = self.proType , masterSlaveModel = self.masterSlaveModel, pvUpperLimit=pvUpperLimit, pvLowerLimit=pvLowerLimit, pvUnit=pvUnit) if init: self.removeTab(0) tabIndex = self.count() self.tabBar().setHidden(False) - self.addTab(AreaTabWidget(self),str(self.titleName)) + self.addTab(AreaTabWidget(self),str(self.deviceName[:-4])) self.setCurrentIndex(tabIndex) self.devicesManange.addDevice(proType=self.proType, masterSlaveModel = self.masterSlaveModel, deviceName = self.deviceName) else: diff --git a/UI/MainWindow.py b/UI/MainWindow.py index 707d4be..94e7617 100644 --- a/UI/MainWindow.py +++ b/UI/MainWindow.py @@ -10,7 +10,7 @@ import qtawesome from PyQt5.QtWidgets import QApplication, QPushButton, QMainWindow, QDockWidget, QToolBar, QAction, QTabWidget, QGridLayout, QWidget, QHBoxLayout, QStackedWidget\ ,QVBoxLayout, QProgressBar, QLabel from PyQt5.QtCore import Qt, QTimer, QSize -from PyQt5.QtGui import QIcon, QWindow +from PyQt5.QtGui import QIcon, QWindow, QPixmap from utils.DBModels.BaseModel import * from model.ClientModel.Client import Client from UI.DeviceWidget import DeviceTab @@ -99,14 +99,14 @@ class MainWindow(QWidget): # self.startProtocolBtn.setIcon(QIcon('Static/startProtocol.png')) self.startProtocolBtn.setText('开始通讯') self.startProtocolBtn.setIcon(QIcon('Static/start.png')) - self.startProtocolBtn.setIconSize(QSize(18, 18)) + self.startProtocolBtn.setIconSize(QSize(23, 23)) self.startProtocolBtn.setCheckable(True) self.startProtocolBtn.clicked.connect(self.startProtocol) self.switchBtn = QPushButton('通讯组态') self.switchBtn.setObjectName("switchBtn") self.switchBtn.setIcon(qtawesome.icon('fa.exchange', color='#1fbb6f')) - self.switchBtn.setIconSize(QSize(20, 20)) + self.switchBtn.setIconSize(QSize(25, 25)) self.switchBtn.setCheckable(True) self.switchBtn.clicked.connect(self.switchWidget) @@ -117,12 +117,33 @@ class MainWindow(QWidget): self.batteryStateLabel.setObjectName('batteryLabel') self.switchTouchBtn = QPushButton("触控模式") - self.switchTouchBtn.setIconSize(QSize(20, 20)) + self.switchTouchBtn.setIconSize(QSize(25, 25)) self.switchTouchBtn.setObjectName('switchTouchBtn') self.switchTouchBtn.setIcon(qtawesome.icon('fa.keyboard-o', color='#1fbb6f')) self.switchTouchBtn.clicked.connect(self.switchTouchMode) self.switchTouchBtn.setCheckable(True) + # 创建按钮 + self.minimizeButton = QPushButton(QIcon('Static/min.png'), "") + self.minimizeButton.setObjectName('minButton') + self.closeButton = QPushButton(QIcon('Static/close.png'), "") + self.closeButton.setObjectName('closeButton') + + # 按钮点击事件连接 + self.minimizeButton.clicked.connect(self.showMinimized) + self.closeButton.clicked.connect(self.close) + hLayout = QHBoxLayout() + hLayout.addWidget(self.minimizeButton) + hLayout.addWidget(self.closeButton) + + iconLabel = QLabel() + pix = QPixmap('Static/Hicent.png') + scaledPixmap = pix.scaled(168, 28, Qt.KeepAspectRatio) + iconLabel.setPixmap(scaledPixmap) + iconLabel.setScaledContents(True) + + toolbarLayout.addWidget(iconLabel, 1) + toolbarLayout.addWidget(QWidget(), 1) toolbarLayout.addWidget(self.startProtocolBtn, 1) toolbarLayout.addWidget(self.switchBtn, 1) toolbarLayout.addWidget(self.switchTouchBtn, 1) @@ -130,6 +151,9 @@ class MainWindow(QWidget): # toolbarLayout.addWidget(QLabel('电量:'), 1) toolbarLayout.addWidget(self.batteryProBar, 1) toolbarLayout.addWidget(self.batteryStateLabel, 1) + toolbarLayout.addWidget(QWidget(), 1) + toolbarLayout.addLayout(hLayout, 1) + toolbarLayout.setSpacing(20) toolbarLayout.setContentsMargins(0, 0, 0, 0) @@ -167,12 +191,15 @@ class MainWindow(QWidget): self.setLayout(self.mainLayout) # self.setCentralWidget(self.stackWidget) - self.setWindowIcon(QIcon('Static/zhjt.ico')) - self.setWindowTitle("PROFIBUS") + self.setWindowIcon(QIcon('Static/Hicent.jpg')) + + self.setWindowTitle("PROFIBUS总线测试工具") self.refreshProgressBar() self.devicesManange.connect() + + self.setWindowFlags(Qt.FramelessWindowHint) # self.resize(800, 600) # self.showMaximized() @@ -295,6 +322,13 @@ class MainWindow(QWidget): Globals.setValue('_touchMode', 0) self.switchTouchBtn.setText('触控模式') + def toggleMaximize(self): + if self.isMaximized(): + self.showNormal() + self.maxRestoreButton.setIcon(QIcon('maximizeIcon.png')) + else: + self.showMaximized() + self.maxRestoreButton.setIcon(QIcon('restoreIcon.png')) # 替换为合适的还原图标路径 diff --git a/UI/__init__.py b/UI/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin.py b/bin.py index 4c044ab..0e0320d 100644 --- a/bin.py +++ b/bin.py @@ -15,7 +15,7 @@ if __name__ == '__main__': Globals._init() Client.initDB() window = MainWindow() - window.showMaximized() + window.showFullScreen() Globals.setValue('MainWindow', window) # window.show() sys.exit(app.exec_()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a57157f..47a2129 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ modbus_tk==1.1.3 -numpy==1.26.4 peewee==3.17.1 PyQt5==5.15.10 +qtawesome diff --git a/utils/DBModels/__init__.py b/utils/DBModels/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/windoweffect/__init__ .py b/windoweffect/__init__ .py new file mode 100644 index 0000000..a3fb4ed --- /dev/null +++ b/windoweffect/__init__ .py @@ -0,0 +1,2 @@ +from .window_effect import WindowEffect +from .c_structures import * \ No newline at end of file diff --git a/windoweffect/c_structures.py b/windoweffect/c_structures.py new file mode 100644 index 0000000..1553383 --- /dev/null +++ b/windoweffect/c_structures.py @@ -0,0 +1,135 @@ +# coding:utf-8 + +from ctypes import POINTER, Structure, c_int +from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT +from enum import Enum + + +class WINDOWCOMPOSITIONATTRIB(Enum): + WCA_UNDEFINED = 0 + WCA_NCRENDERING_ENABLED = 1 + WCA_NCRENDERING_POLICY = 2 + WCA_TRANSITIONS_FORCEDISABLED = 3 + WCA_ALLOW_NCPAINT = 4 + WCA_CAPTION_BUTTON_BOUNDS = 5 + WCA_NONCLIENT_RTL_LAYOUT = 6 + WCA_FORCE_ICONIC_REPRESENTATION = 7 + WCA_EXTENDED_FRAME_BOUNDS = 8 + WCA_HAS_ICONIC_BITMAP = 9 + WCA_THEME_ATTRIBUTES = 10 + WCA_NCRENDERING_EXILED = 11 + WCA_NCADORNMENTINFO = 12 + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13 + WCA_VIDEO_OVERLAY_ACTIVE = 14 + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15 + WCA_DISALLOW_PEEK = 16 + WCA_CLOAK = 17 + WCA_CLOAKED = 18 + WCA_ACCENT_POLICY = 19 + WCA_FREEZE_REPRESENTATION = 20 + WCA_EVER_UNCLOAKED = 21 + WCA_VISUAL_OWNER = 22 + WCA_LAST = 23 + + +class ACCENT_STATE(Enum): + """ Client area status enumeration class """ + ACCENT_DISABLED = 0 + ACCENT_ENABLE_GRADIENT = 1 + ACCENT_ENABLE_TRANSPARENTGRADIENT = 2 + ACCENT_ENABLE_BLURBEHIND = 3 # Aero effect + ACCENT_ENABLE_ACRYLICBLURBEHIND = 4 # Acrylic effect + ACCENT_ENABLE_HOSTBACKDROP = 5 # Mica effect + ACCENT_INVALID_STATE = 6 + + +class ACCENT_POLICY(Structure): + """ Specific attributes of client area """ + + _fields_ = [ + ("AccentState", DWORD), + ("AccentFlags", DWORD), + ("GradientColor", DWORD), + ("AnimationId", DWORD), + ] + + +class WINDOWCOMPOSITIONATTRIBDATA(Structure): + _fields_ = [ + ("Attribute", DWORD), + # Pointer() receives any ctypes type and returns a pointer type + ("Data", POINTER(ACCENT_POLICY)), + ("SizeOfData", ULONG), + ] + + +class DWMNCRENDERINGPOLICY(Enum): + DWMNCRP_USEWINDOWSTYLE = 0 + DWMNCRP_DISABLED = 1 + DWMNCRP_ENABLED = 2 + DWMNCRP_LAS = 3 + + +class DWMWINDOWATTRIBUTE(Enum): + DWMWA_NCRENDERING_ENABLED = 1 + DWMWA_NCRENDERING_POLICY = 2 + DWMWA_TRANSITIONS_FORCEDISABLED = 3 + DWMWA_ALLOW_NCPAINT = 4 + DWMWA_CAPTION_BUTTON_BOUNDS = 5 + DWMWA_NONCLIENT_RTL_LAYOUT = 6 + DWMWA_FORCE_ICONIC_REPRESENTATION = 7 + DWMWA_FLIP3D_POLICY = 8 + DWMWA_EXTENDED_FRAME_BOUNDS = 9 + DWMWA_HAS_ICONIC_BITMAP = 10 + DWMWA_DISALLOW_PEEK = 11 + DWMWA_EXCLUDED_FROM_PEEK = 12 + DWMWA_CLOAK = 13 + DWMWA_CLOAKED = 14 + DWMWA_FREEZE_REPRESENTATION = 15 + DWMWA_PASSIVE_UPDATE_MODE = 16 + DWMWA_USE_HOSTBACKDROPBRUSH = 17 + DWMWA_USE_IMMERSIVE_DARK_MODE = 18 + DWMWA_WINDOW_CORNER_PREFERENCE = 19 + DWMWA_BORDER_COLOR = 20 + DWMWA_CAPTION_COLOR = 21 + DWMWA_TEXT_COLOR = 22 + DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 23 + DWMWA_LAST = 24 + + +class MARGINS(Structure): + _fields_ = [ + ("cxLeftWidth", c_int), + ("cxRightWidth", c_int), + ("cyTopHeight", c_int), + ("cyBottomHeight", c_int), + ] + + +class MINMAXINFO(Structure): + _fields_ = [ + ("ptReserved", POINT), + ("ptMaxSize", POINT), + ("ptMaxPosition", POINT), + ("ptMinTrackSize", POINT), + ("ptMaxTrackSize", POINT), + ] + + +class PWINDOWPOS(Structure): + _fields_ = [ + ('hWnd', HWND), + ('hwndInsertAfter', HWND), + ('x', c_int), + ('y', c_int), + ('cx', c_int), + ('cy', c_int), + ('flags', UINT) + ] + + +class NCCALCSIZE_PARAMS(Structure): + _fields_ = [ + ('rgrc', RECT*3), + ('lppos', POINTER(PWINDOWPOS)) + ] diff --git a/windoweffect/window_effect.py b/windoweffect/window_effect.py new file mode 100644 index 0000000..aa8537c --- /dev/null +++ b/windoweffect/window_effect.py @@ -0,0 +1,222 @@ +# coding:utf-8 +import sys + +from ctypes import POINTER, c_bool, c_int, pointer, sizeof, WinDLL, byref +from ctypes.wintypes import DWORD, LONG, LPCVOID + +from win32 import win32api, win32gui +from win32.lib import win32con + +from .c_structures import ( + ACCENT_POLICY, + ACCENT_STATE, + MARGINS, + DWMNCRENDERINGPOLICY, + DWMWINDOWATTRIBUTE, + WINDOWCOMPOSITIONATTRIB, + WINDOWCOMPOSITIONATTRIBDATA, +) + + +class WindowEffect: + """ A class that calls Windows API to realize window effect """ + + def __init__(self): + # Declare the function signature of the API + self.user32 = WinDLL("user32") + self.dwmapi = WinDLL("dwmapi") + self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute + self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea + self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute + self.SetWindowCompositionAttribute.restype = c_bool + self.DwmExtendFrameIntoClientArea.restype = LONG + self.DwmSetWindowAttribute.restype = LONG + self.SetWindowCompositionAttribute.argtypes = [ + c_int, + POINTER(WINDOWCOMPOSITIONATTRIBDATA), + ] + self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD] + self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)] + + # Initialize structure + self.accentPolicy = ACCENT_POLICY() + self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA() + self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value + self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy) + self.winCompAttrData.Data = pointer(self.accentPolicy) + + def setAcrylicEffect(self, hWnd, gradientColor: str = "F2F2F299", isEnableShadow: bool = True, animationId: int = 0): + """ Add the acrylic effect to the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + + gradientColor: str + Hexadecimal acrylic mixed color, corresponding to four RGBA channels + + isEnableShadow: bool + Enable window shadows + + animationId: int + Turn on matte animation + """ + hWnd = int(hWnd) + # Acrylic mixed color + gradientColor = ( + gradientColor[6:] + + gradientColor[4:6] + + gradientColor[2:4] + + gradientColor[:2] + ) + gradientColor = DWORD(int(gradientColor, base=16)) + # matte animation + animationId = DWORD(animationId) + # window shadow + accentFlags = DWORD(0x20 | 0x40 | 0x80 | + 0x100) if isEnableShadow else DWORD(0) + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_ACRYLICBLURBEHIND.value + self.accentPolicy.GradientColor = gradientColor + self.accentPolicy.AccentFlags = accentFlags + self.accentPolicy.AnimationId = animationId + # enable acrylic effect + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + def setMicaEffect(self, hWnd): + """ Add the mica effect to the window (Win11 only) + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + if sys.getwindowsversion().build < 22000: + raise Exception("The mica effect is only available on Win11") + + hWnd = int(hWnd) + margins = MARGINS(-1, -1, -1, -1) + self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) + self.DwmSetWindowAttribute(hWnd, 1029, byref(c_int(1)), 4) + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_HOSTBACKDROP.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + def setAeroEffect(self, hWnd): + """ Add the aero effect to the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + def removeBackgroundEffect(self, hWnd): + """ Remove background effect + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_DISABLED.value + self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) + + @staticmethod + def moveWindow(hWnd): + """ Move the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + win32gui.ReleaseCapture() + win32api.SendMessage( + hWnd, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0 + ) + + def addShadowEffect(self, hWnd): + """ Add DWM shadow to window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + margins = MARGINS(-1, -1, -1, -1) + self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) + + def addMenuShadowEffect(self, hWnd): + """ Add DWM shadow to menu + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.DwmSetWindowAttribute( + hWnd, + DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, + byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)), + 4, + ) + margins = MARGINS(-1, -1, -1, -1) + self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) + + def removeShadowEffect(self, hWnd): + """ Remove DWM shadow from the window + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + self.DwmSetWindowAttribute( + hWnd, + DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, + byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_DISABLED.value)), + 4, + ) + + @staticmethod + def removeMenuShadowEffect(hWnd): + """ Remove shadow from pop-up menu + + Parameters + ---------- + hWnd: int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + style = win32gui.GetClassLong(hWnd, win32con.GCL_STYLE) + style &= ~0x00020000 # CS_DROPSHADOW + win32api.SetClassLong(hWnd, win32con.GCL_STYLE, style) + + @staticmethod + def addWindowAnimation(hWnd): + """ Enables the maximize and minimize animation of the window + + Parameters + ---------- + hWnd : int or `sip.voidptr` + Window handle + """ + hWnd = int(hWnd) + style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE) + win32gui.SetWindowLong( + hWnd, + win32con.GWL_STYLE, + style + | win32con.WS_MAXIMIZEBOX + | win32con.WS_CAPTION + | win32con.CS_DBLCLKS + | win32con.WS_THICKFRAME, + )