PyQt5 编写 Windows 应用程序要点全记录

目录

一、安装调试和基础配置

(一) 安装Python(3.9)

(二) 安装 Pycharm (IDE)

(三) 配置IDE & 安装库

二、PyQt5相关

(一) PyQt5 去掉标题栏

(二) PyQt5 高分辨率屏幕缩放

(三) PyQt5 打开子窗口

(四) PyQt5 子窗口置顶并锁定父窗口

(五) PyQt5 弹出式对话框

(六) PyQt5 系统托盘相关

(七) PyQt5 将窗口置顶

(八) PyQt5 窗体关闭和最小化事件

(九) PyQt5 定时器

(十) PyQt5 输入框(LineEdit)获取/失去焦点事件

(十一)PyQt5 禁用关闭按钮

三、Pyinstaller 相关

(一)Pyinstaller安装

(二)auto-py-to-exe

(三)Pyinstaller 注意事项

(四) 指定不同版本的解释器

四、PyCharm 相关

(一)快捷键

五、Qt Designer 相关

(一)调整定位点密度

六、Python 编程

(一) 申请管理员权限

(二) 读取硬件信息

(三) 注册表操作

(四) 开机启动

(五) 执行CMD命令

(六)多线程


一、安装调试和基础配置

(一) 安装Python(3.9)

Python3https://www.python.org/downloads/

(二) 安装 Pycharm (IDE)

Pycharmhttps://www.jetbrains.com/zh-cn/pycharm/

(三) 配置IDE & 安装库

        1) PyCharm安装完成后,新创建一个项目,为项目命名、确定项目保存的位置、点选

        2) 依次点选 File(文件) -> Settings (设置)-> Project:(项目:) -> Project Interpreter(Python 解释器), 右侧列表中显示已经安装的软件包,点击 + 号。

        3) 在可用软件包中进行搜索,分别安装软件包:PyQt5、PyQt5-Qt、PyQt5-Qt5、 PyQt5-sip、pyqt5-tools等,具体需要哪些软件包根据自己的项目进行配置。

        4)如果pyqt5-tools无法安装,可以使用pip进行安装。

        5)依次点击File(文件) -> Settings(设置) -> Tools(工具) -> External Tools(外部工具),在右侧列表中点击 + 号,分别配置 QtDesigner(用于设计界面)、PyUic(将ui界面文件转换为Python代码)、PyRcc等,具体需要哪些工具根据自己的项目需求进行配置,这些工具便于在项目中对资源文件进行开发与转换等。

Name(名称): QtDesigner
Program(程序):C:\Python39\Lib\site-packages\qt5_applications\Qt\bin\designer.exe
Arguments(实参):空置
Working directory(工作目录):$FileDir$
Name(名称): PyUic
Program(程序):C:\Python39\Scripts\pyuic5.exe
Arguments(实参):$FileName$ -o $FileNameWithoutExtension$.py
Working directory(工作目录):$FileDir$
Name(名称): PyRcc
Program(程序):C:\Python39\Scripts\pyrcc5.exe
Arguments(实参):$FileName$ -o $FileNameWithoutExtension$.py
Working directory(工作目录):$FileDir$

        6)Windows 中文的编码大部分使用GBK,所以将IDE的编码配置GBK是有必要的,依次点击 File(文件) -> Settings(设置) -> Editor(编辑器) -> File Encodings(文件编码),右侧的 Global Encoding (全局编码)修改为 GBK。

        7)编辑Python 代码时,代码软换行(自动换行):依次点击 File(文件) -> Settings(设置) -> Editor(编辑器) -> General(常规),右侧的Soft Wraps的第一行 Soft-wrap files(对这些文件进行软换行): 中添加自己需要自动换行的文件扩展名,比如 py 文件则在后面添加:;*.py。

二、PyQt5相关

(一) PyQt5 去掉标题栏

         在 Form 的 py 文件中 setupUi 方法中针对 Form 配置的末尾加入(该方法配置的标题栏还残留一条,并且尺寸大小也不能再固定,不是很理想):

Form.setWindowFlags(Qt.Qt.CustomizeWindowHint)

(二) PyQt5 高分辨率屏幕缩放

        操作系统对高分辨率的屏幕实现了缩放与布局的设定,所以会导致默认情况下在高分辨率的屏幕并开启缩放的显示器上出现窗口布局错乱的问题。

        在程序的入口处添加该配置:

QtCore.QCoreApplication.setAttribute(
    QtCore.Qt.AA_EnableHighDpiScaling
)

(三) PyQt5 打开子窗口

        通过窗口类的 show() 函数实现窗口的打开

(四) PyQt5 子窗口置顶并锁定父窗口

        在子窗口.ui文件生成的py文件中 setupUi 方法里针对 Form 配置的末尾加入:

Form.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.MSWindowsFixedSizeDialogHint | QtCore.Qt.Tool)
Form.setWindowModality(QtCore.Qt.ApplicationModal)

(五) PyQt5 弹出式对话框

        以下是 PyQt5 默认的几种弹出式对话框,对话框的按钮都是英文,并且不能直接进行按钮文字的修改,如果有需要可以进行重写(本次开发没有相关需求,后期再进行补充)

QtWidgets.QMessageBox.information(
    self,
    "标题",
    "消息",
    QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No,
    QtWidgets.QMessageBox.Yes
)

QtWidgets.QMessageBox.question(
    self,
    "标题",
    "问答消息",
    QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No,
    QtWidgets.QMessageBox.Yes
)

QtWidgets.QMessageBox.warning(
    self,
    "标题",
    "警告消息",
    QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No,
    QtWidgets.QMessageBox.Yes
)

QtWidgets.QMessageBox.critical(
    self,
    "标题",
    "严重错误消息",
    QtWidgets.QMessageBox.Yes|QtWidgets.QMessageBox.No,
    QtWidgets.QMessageBox.Yes
)

QtWidgets.QMessageBox.about(
    self,
    "标题",
    "关于消息"
)

(六) PyQt5 系统托盘相关

        创建一个托盘类

# -*- coding: utf-8 -*-
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import *


class Tray(QSystemTrayIcon):

    def __init__(self, Window):
        super(Tray, self).__init__()
        self.menu = None
        self.window = Window
        self.init_ui()

    def init_ui(self):
        # 初始化菜单
        self.menu = QMenu()
        # 设定右键菜单
        ma1 = QAction('显示主界面', self, triggered=self.showForm)
        ma2 = QAction('退出', self, triggered=self.quitApp)
        # 添加菜单
        self.menu.addAction(ma1)
        self.menu.addAction(ma2)
        # 激活菜单
        self.setContextMenu(self.menu)
        # 设置托盘图标
        self.setIcon(QIcon('kcwl.ico'))
        # 图标事件
        self.activated.connect(self.iconActivated)

    def showForm(self):
        self.window.show()

    def iconActivated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.window.show()

    @staticmethod
    def quitApp():
        qApp.quit()
0

       引入到主程序,并在运行主程序时显示托盘图标

self.tray = Tray(self)
self.tray.show()

        在主程序初始化时配置退出事件

# 关闭按钮表现为最小化到托盘
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
    a0.ignore()
    self.hide()
    self.tray.show()

(七) PyQt5 将窗口置顶

        1. 临时置顶:该功能在将程序从托盘唤出时非常有用,窗口置顶既将窗口设置为活动窗口

self.activateWindow()
self.showNormal()

        2. 永久置顶:该功能对一些小窗口很有用,在 Form 的 py 文件中 setupUi 方法中针对 Form 配置的末尾加入

Form.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)

(八) PyQt5 窗体关闭和最小化事件

        1. 最小化事件重写:

def changeEvent(self, a0: QtCore.QEvent) -> None:
    if a0.type() == QtCore.QEvent.WindowStateChange:
        if self.windowState() & QtCore.Qt.WindowMinimized:
            a0.ignore()
            # 这里写内容

        2. 关闭事件:

def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
    a0.ignore()
    # 这里写内容

(九) PyQt5 定时器

def __init__(self)

	# 创建定时器
	self.tmr = PyQt5.QtCore.QTimer(self)

	# 定时执行函数
	self.tmr.timeout.connect(self.myFun)

	# 启动和停止定时器(按钮)
	self.sBtn.clicked.connect(self.startTm)
	self.ebtn.clicked.connect(self.endTm)


def startTm(self):
	self.tmr.start(10000)  # 单位毫秒

def endTm(self):
	self.tmr.stop()

def myFun(self):
	pass

(十) PyQt5 输入框(LineEdit)获取/失去焦点事件

        窗口初始化时(在__init__函数中)加入下列代码:

self.ui.line_edit_1.installEventFilter(self)

        在窗口类中重写 eventFilter 函数:

def eventFilter(self, a0: 'QtCore.QObject', a1: 'QtCore.QEvent') -> bool:
    if a1.type() == QtCore.QEvent.FocusIn and a0 is self.ui.line_edit_1:
        # 这里写处理程序
    
    return super(mainWin, self).eventFilter(a0, a1)  # 这里的mainWin 是当前窗口类名

(十一)PyQt5 禁用关闭按钮

        在初始化主窗口函数 __init__() 中 setupUi 后面加入以下代码:

self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False)

三、Pyinstaller 相关

        Pyinstaller 是用来给编写完成的 Python 打包成 EXE 可执行程序的工具

(一)Pyinstaller安装

pip install pyinstaller

(二)auto-py-to-exe

        该应用是一个非常方便的可视化 pyinstaller 工具,可以非常简单地使用该工具进行 Pyinstaller 命令的生成、执行等操作

PyQt5 编写 Windows 应用程序要点全记录_第1张图片

        1. 安装:

pip install auto-py-to-exe

        2. 在CMD或IDE终端中执行命令:

aotu-py-to-exe

(三)Pyinstaller 注意事项

        1. 注意引入程序需用的附加文件,包括图像文件、程序读写使用的文件以及程序图标和窗口图标等

        2. 其它内容待完善

(四) 指定不同版本的解释器

        由于开发过后的软件需要在其它不同的版本系统上使用,所以使用不同的解释器成为了必要,比如分为 64位系统、32位系统,以及 Win7版、Win10版等,对 Python 解释器的版本要求就会不同,所以在生成 exe 时指定不同的解释器成为必要。

        1. 首先在系统中安装不同版本的 Python,比如本文发表时最新的 Python 3.10.4 的 64位和32位,用于Win10、Win11操作系统下的解释器,再安装 Python 3.8.5 的两个版本,用于 Wn7 系统的解释器。

        2. 每次指定不同的解释器前将系统的 Path 变量进行更改,或者针对不同的版本使用不同的命令,比如3.10的版本命令改为 Python310,3.8的改为 Python38,Pip 同理。

        3. 如果不想更改可以在命令行中使用完整路径运行 Python 以及 Pip 程序,安装比如 PyQt5、PyQt5-tools、wmi 等开发的程序需要用到的第三方库,我个人比较倾向于后者,。

        4. 检查有没有早期版本不兼容的代码,如果有要进行替换。一般情况下不会发生这种情况。

        5. 在Pycharm 下新建项目,新建后指定需要的解释器。(该步骤可能对后最的可执行程序生成没有作用,我也还没有做测试,测试后完善)

        6. 使用 auto-py-to-exe 生成运行命令之后,将里面的所有关于解释器的命令全部修改为指定版本的解释器,然后到 CMD 终端上运行,运行CMD终端时要以管理员权限运行CMD,再执行代码,这样可以避免踩坑。

四、PyCharm 相关

(一)快捷键

        1. 整体缩进和反缩进(左移):Tab(缩进) 和 shift + Tab(左移)

        2. 批量注释: Ctrl+/

五、Qt Designer 相关

(一)调整定位点密度

        依次点击菜单栏的窗体,在打开的窗体设定中选中 栅格 和 可见的,然后修改 栅格 X 和 栅格Y,将其数值改小,将 贴齐 选中。(如果不想自动对齐栅格,将贴齐取消选中即可)

六、Python 编程

(一) 申请管理员权限

        该方法运行时会被执行两次,第一次是未经授与管理员权限的,所以后面使用 sys.exti() 对该无管理员权限运行的程序进行退出,随后如果用户通过了授权后,运行的就具备了管理员权限。

        这个代码源自于网络,经过优化,ctypes.windll.shell32.ShellExecuteW 函数的最后一个参数如果是1,则会弹出CMD窗口,如果为零则会隐藏【注,仅对IDE中运行时有效,如果要打包成EXE可执行文件,需要将最后的参数修改回1,并且在pyinstaller设置为窗口运行( --windowed形参)】

import ctypes

def administratorRights():
    if not ctypes.windll.shell32.IsUserAnAdmin():
        if sys.version_info[0] == 3:  # python3.x
            ctypes.windll.shell32.ShellExecuteW(
                None,
                "runas",
                sys.executable,
                __file__,
                None,
                0
            )
        else:  # python2.x
            ctypes.windll.shell32.ShellExecuteW(
                None,
                u"runas",
                unicode(sys.executable),
                unicode(__file__),
                None,
                0
            )
        sys.exit()

        ShellExecute 函数记要和参考

ShellExecute(
	hWnd: HWND; {指定父窗口句柄}
	Operation: PChar; {指定动作, 譬如: open、runas、print、edit、explore、find [2]  }
	FileName: PChar; {指定要打开的文件或程序}
	Parameters: PChar; {给要打开的程序指定参数; 如果打开的是文件这里应该是 nil}
	Directory: PChar; {缺省目录}
	ShowCmd: Integer {打开选项}
)

ShowCmd 参数可选值:SW_HIDE = 0; {隐藏}
SW_SHOWNORMAL = 1; {用最近的大小和位置显示, 激活}
SW_NORMAL = 1; {同 SW_SHOWNORMAL}
SW_SHOWMINIMIZED = 2; {最小化, 激活}
SW_SHOWMAXIMIZED = 3; {最大化, 激活}
SW_MAXIMIZE = 3; {同 SW_SHOWMAXIMIZED}
SW_SHOWNOACTIVATE = 4; {用最近的大小和位置显示, 不激活}
SW_SHOW = 5; {同 SW_SHOWNORMAL}
SW_MINIMIZE = 6; {最小化, 不激活}
SW_SHOWMINNOACTIVE = 7; {同 SW_MINIMIZE}
SW_SHOWNA = 8; {同 SW_SHOWNOACTIVATE}
SW_RESTORE = 9; {同 SW_SHOWNORMAL}
SW_SHOWDEFAULT = 10; {同 SW_SHOWNORMAL}
SW_MAX = 10; {同 SW_SHOWNORMAL}

(二) 读取硬件信息

import wmi


class Hardware:
    @staticmethod
    def get_cpu_sn():
        """
        获取CPU序列号
        :return: CPU序列号
        """
        c = wmi.WMI()
        for cpu in c.Win32_Processor():
            # print(cpu.ProcessorId.strip())
            return cpu.ProcessorId.strip()

    @staticmethod
    def get_baseboard_sn():
        """
        获取主板序列号
        :return: 主板序列号
        """
        c = wmi.WMI()
        for board_id in c.Win32_BaseBoard():
            # print(board_id.SerialNumber)
            return board_id.SerialNumber

    @staticmethod
    def get_bios_sn():
        """
        获取BIOS序列号
        :return: BIOS序列号
        """
        c = wmi.WMI()
        for bios_id in c.Win32_BIOS():
            # print(bios_id.SerialNumber.strip)
            return bios_id.SerialNumber.strip()

    @staticmethod
    def get_disk_sn():
        """
        获取硬盘序列号
        :return: 硬盘序列号列表
        """
        c = wmi.WMI()

        disk_sn_list = []
        for physical_disk in c.Win32_DiskDrive():
            # print(physical_disk.SerialNumber)
            # print(physical_disk.SerialNumber.replace(" ", ""))
            disk_sn_list.append(physical_disk.SerialNumber.replace(" ", ""))
        return disk_sn_list


if __name__ == '__main__':
    print("CPU序列号:{}".format(Hardware.get_cpu_sn()))
    print("主板序列号:{}".format(Hardware.get_baseboard_sn()))
    print("Bios序列号:{}".format(Hardware.get_bios_sn()))
    print("硬盘序列号:{}".format(Hardware.get_disk_sn()))

(三) 注册表操作

import winreg

def getOrCreateFirstRunTime():
    try:
        # 尝试打开已经存在的项
        key = winreg.OpenKeyEx(winreg.HKEY_CURRENT_USER, r'SOFTWARE\aaa')
        try:
            # 尝试读取已经写入的注册表项
            rec = winreg.QueryValueEx(key, r'ti')
            winreg.CloseKey(key)
            return str(rec[0])
        except Exception:
            # 尝试创建注册表键值
            tm = str(time())
            winreg.SetValueEx(key, r'ti', 0, winreg.REG_SZ, tm)
            winreg.CloseKey(key)
            return tm
    except Exception:
        try:
            # 创建注册表项和键值
            key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, r'SOFTWARE\aaa')
            tm = str(time())
            winreg.SetValueEx(key, r'ti', 0, winreg.REG_SZ, tm)
            winreg.CloseKey(key)
            return tm
        except Exception:
            return False

(四) 开机启动

        修改注册表实现开机启动:

        1. 用户级:

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

        2. 设备级:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

        3. 在以上两个位置新建字符串类型的键值,名称设置为软件名,值设置为应用程序位置,设置字符串值时注意要加双引号(双引号是字符串值的一部分):

EXE_PATH = r'"C:\Program Files\Google\Chrome\Application\chrome.exe"'

(五) 执行CMD命令

        方法一:该方法无法返回 CMD 反馈的信息:

import os

os.system(r'ipconfig')

        方法二:该方法可以获取CMD返回的信息,但是命令出错信息却无法获取:

import os

f_bin = os.popen(comm, "r")
rec = f_bin.read()
f_bin.close()
print(rec)

        方法三:使用 subprocess,并通过 io 获取返回的内容,该方法最为理想,既可以获取正常返回信息,也可以返回出错信息:

import io
import subprocess

def sendCMD(comm):
    proc = subprocess.Popen(comm, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
    proc.wait()
    with io.TextIOWrapper(proc.stdout, encoding='gbk') as fOut:
        recOut = fOut.read()
        if recOut:
            return recOut.strip()
    with io.TextIOWrapper(proc.stderr, encoding='gbk') as fErr:
        recErr = fErr.read()
        if recErr:
            return recErr.strip()

(六)多线程

        使用 CMD 命令时,如果命令的运行时长较长,会阻滞窗口的正常运行,导致窗口无法操作等情况,体验非常差,这时需要用到多线程。

        由于主程序需要用到 CMD 命令返回的结果,并对其进行后续响应,所以需要使用回调函数,并进行传参。

# -*- coding: utf-8 -*-
import threading
import subprocess
import io


class Cmd(threading.Thread):

    def __init__(self, comm, callback):
        threading.Thread.__init__(self)
        self.comm = comm
        self.callback = callback

    # 发送CMD命令并返回结果
    def tdCmd(self):
        try:
            proc = subprocess.Popen(self.comm, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
            proc.wait()
            with io.TextIOWrapper(proc.stdout, encoding='gbk') as fOut:
                recOut = fOut.read()
                if recOut:
                    self.callback(recOut.strip(), 0)
            with io.TextIOWrapper(proc.stderr, encoding='gbk') as fErr:
                recErr = fErr.read()
                if recErr:
                    self.callback(recErr.strip(), 1)
        except:
            pass

你可能感兴趣的:(软件应用,Python,Windows,pycharm,ide,python)