python --qt5(webview)/防多开/套壳网页/多次点击激活旧窗口

时间:2024-09-30 07:22:04
pyqtwebengine=5.12
PyQt5==5.12
class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.browser = QWebEngineView(self)  # 如果不写self则新生成一个窗口
        self.browser.setWindowTitle('技术领域占比分析')
        self.browser.setWindowIcon(QIcon(LOGO_PATH))
        self.browser.raise_()
        self.browser.setFixedSize(QSize(1200, 850))
        self.browser.move(0, 0)
        self.browser.setUrl(QUrl('https://www.taobao.com'))  # 网络连接
        # self.browser.load(QUrl.fromLocalFile('D:/1.htmml'))  # 本地h5
        self.browser.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

webview套壳 防多开 多次点击激活旧窗口

文件结构如下:

在这里插入图片描述

依赖

loguru==0.6.0
pyinstaller
pywin32==306
PyQt5==5.12
pyqtwebengine==5.12
Pillow

config.ini

[Section1]
open_loadfile = 1
window_title = 鸿运兑奖系统
window_width = 1580
window_height = 800
prohibit_max_window = 0
window_max = 0
open_url = https://www.taobao.com
min_window_width = 1300
min_window_height = 700

settings.py

import os

BASE_DIR = os.path.dirname(__file__)
STATIC_FILE = os.path.join(BASE_DIR, 'static')     # 静态资源路径
INDEX_FILE = os.path.join(STATIC_FILE, 'index.html')
CONFIG_PATH = os.path.join(BASE_DIR, 'config')     # 图标文件
LOGO_PATH = os.path.join(CONFIG_PATH, "logo.png")  #  图标路径
INI_PATH = os.path.join(CONFIG_PATH, "config.ini") # 配置文件

tools.py

import configparser

class MyINIFile:
    def __init__(self, filename):
        self.filename = filename
        self.config = configparser.ConfigParser()
        self.config.read(self.filename)

    def read_value(self, section, key):
        try:
            return self.config.get(section, key)
        except configparser.Error as e:
            print(f"Error reading value: {e}")
            return None

    def write_value(self, section, key, value):
        try:
            if not self.config.has_section(section):
                self.config.add_section(section)
            self.config.set(section, key, value)
            with open(self.filename, 'w') as configfile:
                self.config.write(configfile)
        except configparser.Error as e:
            print(f"Error writing value: {e}")

    def delete_value(self, section, key):
        try:
            if self.config.has_section(section) and self.config.has_option(section, key):
                self.config.remove_option(section, key)
                with open(self.filename, 'w') as configfile:
                    self.config.write(configfile)
            else:
                print(f"Section '{section}' or option '{key}' does not exist.")
        except configparser.Error as e:
            print(f"Error deleting value: {e}")

    def update_value(self, section, key, value):
        try:
            if self.config.has_section(section) and self.config.has_option(section, key):
                self.config.set(section, key, value)
                with open(self.filename, 'w') as configfile:
                    self.config.write(configfile)
            else:
                print(f"Section '{section}' or option '{key}' does not exist.")
        except configparser.Error as e:
            print(f"Error updating value: {e}")

ui.py

from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QMainWindow, QSystemTrayIcon, QMenu, QAction, \
    QApplication

from settings import LOGO_PATH, INDEX_FILE, INI_PATH
from tools import MyINIFile



class MyWindow(QMainWindow):
    def __init__(self):
        config = MyINIFile(INI_PATH)
        self.open_loadfile = config.read_value('Section1', 'open_loadfile')
        self._window_title = config.read_value('Section1', 'window_title')  # 窗口标题
        self._window_width = int(config.read_value('Section1', 'window_width'))# 窗口默认宽高
        self._window_height = int(config.read_value('Section1', 'window_height'))  # 窗口默认宽高
        self.prohibit_max_window = config.read_value('Section1', 'prohibit_max_window')  # 是否禁止最大化
        self.window_max = config.read_value('Section1', 'window_max')  # 是否默认最大化显示
        self.open_url = config.read_value('Section1', 'open_url')  # 网络链接
        self._min_window_width = int(config.read_value('Section1', 'min_window_width'))  # 最小窗口
        self._min_window_height = int(config.read_value('Section1', 'min_window_height'))

        super(MyWindow, self).__init__()
        self.setWindowTitle(self._window_title)  # 设置窗口标题
        self.setMinimumSize(self._min_window_width, self._min_window_height)
        self.resize(self._window_width, self._window_height)
        self.setWindowIcon(QIcon(LOGO_PATH))  # 设置窗口图标,icon.png 是您的图标文件路径

        if self.prohibit_max_window == '1':
            self.setWindowFlags(self.windowFlags() & ~Qt.WindowMaximizeButtonHint)  # 禁止最大化按钮
        if self.window_max == '1':
            self.showMaximized()  # 最大化窗口

        self.browser = QWebEngineView(self)  # 如果不写self则新生成一个窗口
        if self.open_loadfile == '1':
            self.browser.load(QUrl.fromLocalFile(INDEX_FILE))  # 本地h5
        else:
            self.browser.setUrl(QUrl(self.open_url))  #
        self.browser.show()

    def resizeEvent(self, event):
        # 设置 WebEngineView 的大小
        self.browser.resize(self.size())  # 使用窗口的大小
        super().resizeEvent(event)

    def tray(self):
        '''系统托盘'''
        # 创建系统托盘图标
        self.tray_icon = QSystemTrayIcon(self)
        self.tray_icon.setIcon(QIcon(LOGO_PATH))  # 替换为你的图标路径
        self.tray_icon.setToolTip(self._window_title)  # 这里设置鼠标悬浮时显示的文字
        self.tray_menu = QMenu()          # 创建右键菜单

        open_window_action = QAction("打开主界面", self)  # 添加退出动作
        exit_action = QAction("退出系统", self) # 添加退出动作

        self.tray_menu.addAction(open_window_action)  # 添加到菜单
        self.tray_menu.addAction(exit_action)

        open_window_action.triggered.connect(self.open_window_action)
        exit_action.triggered.connect(self.exit_app)        # 连接托盘图标的点击事件


        self.tray_icon.activated.connect(self.tray_icon_clicked)  # 图标左键被点击
        self.tray_icon.setContextMenu(self.tray_menu)  # 将菜单设置到托盘图标
        self.tray_icon.show()       # 显示托盘图标

    def tray_icon_clicked(self, reason):
        if reason == QSystemTrayIcon.Trigger:  # 单击托盘图标
            if self.isHidden():
                self.show()  # 显示窗口
            if self.isMinimized():
                self.showNormal()  # 还原窗口

            self.activateWindow()  # 激活窗口
            self.raise_()  # 确保窗口在最上层

    def open_window_action(self):
        '''打开主界面'''
        if self.isHidden():
            self.show()  # 显示窗口

        if self.isMinimized():
            self.showNormal()  # 还原窗口

        self.activateWindow()  # 激活窗口
        self.raise_()  # 确保窗口在最上层

    def exit_app(self):
        QApplication.quit()  # 退出应用

    def closeEvent(self, event):
        '''窗口被关闭事件'''
        event.ignore()
        self.hide()

main.py

import socket
import sys
from threading import Thread

from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication
from loguru import logger
from ui import MyWindow

class Communicator(QObject):
    activate_signal = pyqtSignal()

def check_if_running(port):
    """检查端口是否被占用,意味着应用正在运行"""
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        try:
            s.bind(('127.0.0.1', port))
            return False  # 绑定成功,表示没有其他实例在运行
        except OSError:
            return True  # 绑定失败,表示端口被占用

def activate_existing_instance(port):
    """激活已运行的实例"""
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.sendto(b'activate', ('127.0.0.1', port))

def start_server(port, comm):
    """启动服务器以监听激活请求"""
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.bind(('127.0.0.1', port))
        logger.debug('启动成功')
        while True:
            data, addr = s.recvfrom(1024)
            if data == b'activate':
                logger.info("激活请求收到, 启动窗口")
                comm.activate_signal.emit()  # 发射信号以激活窗口

if __name__ == '__main__':
    app = QApplication(sys.argv)
    port = 12345  # 选择一个未被占用的端口

    comm = Communicator()
    main_window = MyWindow()
    comm.activate_signal.connect(main_window.open_window_action)

    if check_if_running(port):
        activate_existing_instance(port)
        logger.error("应用已经在运行,激活窗口。")
        sys.exit(0)

    # 启动服务器监听激活请求
    server_thread = Thread(target=start_server, args=(port, comm))
    server_thread.daemon = True
    server_thread.start()

    main_window.tray()
    # 创建并显示主窗口
    main_window.show()
    sys.exit(app.exec_())

pack.py

# coding=utf-8

# @Time : 2023-08-17 02:04
# @Author : XiaoYi
# @Email: 1206154726@qq.com
import os
import shutil
from loguru import logger

desktop_dist = os.path.join(os.path.join(os.path.expanduser("~"), 'Desktop'), 'dist')
desktop_main = os.path.join(desktop_dist, 'main')
# from urllib.parse import quote as url_quote   py3.8以上

logger.debug(f'----------------------------开始构建程序--------------------------')
os.system('pyinstaller -w main.py -i logo.ico')  # 执行打包指令  pyinstaller -w main.py -i logo.ico 无调试窗口
logger.success(f'------->基础镜像结束,开始打包静态资源')

shutil.move(os.path.join(os.getcwd(), 'dist'), desktop_dist)  # 移动文件到桌面
logger.debug(f'------->【dist】基础镜像结束')

shutil.rmtree(os.path.join(os.getcwd(), 'build'))  # 删除打包构建文件
os.remove(os.path.join(os.getcwd(), 'main.spec'))  # 删除打包构建文件
logger.warning(f'------->【编译文件清除】清除结束')

shutil.copytree(os.path.join(os.getcwd(), 'config'), os.path.join(desktop_main, 'config'))
shutil.copytree(os.path.join(os.getcwd(), 'static'), os.path.join(desktop_main, 'static'))
os.rename(os.path.join(desktop_main, 'main.exe'), os.path.join(desktop_main, 'taoke.exe'))  # 重命名打包后的文件

os.rename(desktop_main, os.path.join(desktop_dist, 'taoke'))  # 重命名打包后的文件
logger.success(f'-------【打包结束】----------')