python --pywebview、flaskwebgui开发桌面程序
- pip install pywebview==3.7.2
-
官文
- https://pywebview.flowrl.com/guide/interdomain.html#invoke-python-from-javascript
-
案例
- https://pywebview.flowrl.com/examples/fullscreen.html
-
- import webview
- from flask import Flask, render_template, jsonify, request
- import json
- from functools import wraps
-
- app = Flask(__name__, template_folder='./static', static_folder='./static', static_url_path='')
-
-
- def verify_token(function):
- @wraps(function)
- def wrapper(*args, **kwargs):
- data = json.loads(request.data)
- token = data.get('token')
- if token == webview.token:
- return function(*args, **kwargs)
- else:
- raise Exception('Authentication error')
-
- return wrapper
-
-
- @app.route('/')
- def index():
- return render_template('index.html')
-
-
- @app.route('/backstage/api/login/', methods=['POST'])
- def login():
- print(request.data)
- data = json.loads(request.data)
- user = data.get('username')
- pwd = data.get('password')
-
- if user != 'test' or pwd != 'test':
- print({'code': '4013', 'msg': '用户名或密码错误'}, jsonify({'code': '4013', 'msg': '用户名或密码错误'}))
- return jsonify({'code': '4013', 'msg': '用户名或密码错误'})
-
- groups = {"首页": [], "业务菜单": ["3D模型", "画图展示", "业务3"], "系统设置": ["用户管理", "系统日志"]}
- roles = {"首页": ["读"], "3D模型": ["读", "写"], "业务2": ["读", "写"], "业务3": ["读", "写"],
- "用户管理": ["读", "写"], "系统日志": ["读", "写"]}
-
- return jsonify({'code': '0', 'data': {'groups': groups, 'roles': roles}, 'msg': 'ok'})
-
-
- @app.route('/get_usr_info', methods=['GET'])
- @verify_token
- def get_usr_info():
- return jsonify({'code': '0', 'data': []})
-
-
- if __name__ == '__main__':
- chinese = {
- 'global.quitConfirmation': u'确定关闭?',
- }
-
- window = webview.create_window(
- title='云收单',
- url=app,
- width=900,
- height=620,
- # frameless=True,
- # easy_drag=True,
- # hidden=True,
- transparent=True,
- )
-
- webview.start(localization=chinese, debug=True, http_server=True)
- 注意: 如果要修改flask运行端口为固定则去源码找serving.py 53行修改为指定端口即可;
- 注意2: 如新版本出错
- RuntimeError: cannot call null pointer pointer from cdata 'int(*)(void *, int)'
- pip install pythonnet==2.5.2
-
控制窗口事件
- import json
- import webview
- from flask import Flask, render_template
- from flask_cors import CORS
- import uuid
-
- app = Flask(__name__, template_folder='./static', static_folder='./static', static_url_path='')
- CORS(app, supports_credentials=True)
-
-
- class Windows(object):
- '''窗口事件'''
-
- _window = None
-
- @property
- def w(self):
- return self._window
-
- @w.setter
- def w(self, value):
- self._window = value
-
- def minimize(self):
- print('调用最小化窗口')
- self._window.minimize()
-
- def destroy(self):
- print('销毁窗口')
- self._window.destroy()
-
-
- @app.route('/')
- def index():
- return render_template('index.html')
-
-
- @app.route('/api_minimize', methods=['GET', ])
- def api_minimize():
- _w.minimize()
- return json.dumps({'code': 0})
-
-
- @app.route('/kill', methods=('GET',))
- def kill():
- _w.destroy()
-
-
- window = webview.create_window(
- title='问卷侠题库系统',
- url=app,
- width=1360,
- height=800,
- text_select=True,
- resizable=False,
- frameless=True,
- confirm_close=True
- )
- _w = Windows()
- _w.w = window
- webview.start(debug=True, http_server=True)
-
装包: pip install flaskwebgui
github:
- https://github.com/ClimenteA/flaskwebgui/tree/master/examples/
-
实例:
- from flask import Flask
- from flask import render_template
- from flaskwebgui import FlaskUI
-
- app = Flask(__name__, static_folder='./static', template_folder='./static', static_url_path='')
- ui = FlaskUI(app, width=500, height=500)
-
-
- @app.route("/")
- def hello():
- return render_template('index.html')
-
-
- @app.route("/home", methods=['GET'])
- def home():
- return render_template('index.html')
-
-
- if __name__ == "__main__":
- ui.run()
-
结构:
打包多进程失效或异常问题
重复启动主进程问题
- if __name__ == '__main__':
- multiprocessing.freeze_support()
-
- import logging
- import os
- import signal
- import subprocess as sps
- import tempfile
-
- import win32con
- import win32gui
- import win32print
- from flask import Flask, redirect
- from flask_cors import CORS
- from flaskwebgui import FlaskUI
-
-
- class BaseFlaskUI(FlaskUI):
-
- def find_chrome_win(self):
-
- import winreg as reg
- reg_path = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe'
-
- chrome_path = None
- last_exception = None
-
- for install_type in reg.HKEY_CURRENT_USER, reg.HKEY_LOCAL_MACHINE:
- try:
- reg_key = reg.OpenKey(install_type, reg_path, 0, reg.KEY_READ)
- chrome_path = reg.QueryValue(reg_key, None)
- reg_key.Close()
- except WindowsError as e:
- last_exception = e
- else:
- if chrome_path and len(chrome_path) > 0:
- break
- if not chrome_path:
- edge_path = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
- return edge_path
- logging.info(f'{chrome_path}')
- return chrome_path
-
- def get_real_resolution(self):
- """获取真实的分辨率"""
- hdc = win32gui.GetDC(0)
- return win32print.GetDeviceCaps(hdc, win32con.DESKTOPHORZRES), \
- win32print.GetDeviceCaps(hdc, win32con.DESKTOPVERTRES)
-
- def open_chromium(self):
- logging.info(f"Opening browser at {self.localhost}")
-
- temp_profile_dir = os.path.join(tempfile.gettempdir(), "flaskwebgui")
-
- if self.browser_path:
- width, height = self.get_real_resolution()
- width = (width - 1750) // 2
- height = (height - 850) // 2
- if self.fullscreen:
- launch_options = ["--start-fullscreen"]
- elif self.maximized:
- launch_options = ["--start-maximized"]
- else:
- launch_options = [f"--window-size={self.width},{self.height}"]
-
- options = [
- self.find_chrome_win(),
- f"--user-data-dir={temp_profile_dir}",
- "--new-window",
- "--no-first-run",
- f"--window-position={width},{height}",
- "--disable-desktop-notifications"
- ] + launch_options + [f'--app={self.localhost}']
-
- sps.Popen(options, stdout=sps.PIPE, stderr=sps.PIPE, stdin=sps.PIPE)
-
- else:
- import webbrowser
- webbrowser.open_new(self.localhost)
-
-
- app = Flask(__name__, template_folder='./static', static_folder='./static', static_url_path='')
- CORS(app, supports_credentials=True)
-
- ui = BaseFlaskUI(app, close_server_on_exit=True, width=1750, height=850, port=8001)
-
-
- @app.route('/kill', methods=('GET',))
- def kill():
- print('调用用户退出')
- handle = os.getpid()
- print(f'pid={handle}')
- os.kill(handle, signal.SIGABRT)
-
-
- @app.route('/', methods=('GET',))
- def index():
- return redirect('http://8.140.142.128:8000/static/index.html#/login')
-
-
- if __name__ == '__main__':
- ui.run()
-
- # app = Flask(__name__, template_folder='./static', static_folder='./static', static_url_path='')
- #
- #
- # @app.route('/')
- # def index():
- # return redirect('http://8.140.142.128:8000/static/index.html#/login')
- #
- #
- # @app.route('/kill')
- # def kill():
- # print('调用用户退出')
- # handle = os.getpid()
- # print(f'pid={handle}')
- # os.kill(handle, signal.SIGABRT)
- #
- #
- # def get_real_resolution():
- # """获取真实的分辨率"""
- # hdc = win32gui.GetDC(0)
- # return win32print.GetDeviceCaps(hdc, win32con.DESKTOPHORZRES), \
- # win32print.GetDeviceCaps(hdc, win32con.DESKTOPVERTRES)
- #
- #
- # if __name__ == '__main__':
- # chinese = {
- # 'global.quitConfirmation': u'您确定要关闭吗?',
- # }
- #
- # window = webview.create_window(
- # title='船舶管理',
- # url=app,
- # width=1750,
- # height=850,
- # # transparent=True,
- # text_select=True,
- # confirm_close=True
- # )
- # print(get_real_resolution())
- # window_hwnd: list = []
- # win32gui.EnumWindows(lambda _hwd, param: param.append(_hwd), window_hwnd)
- # status = False
- # for hwd in window_hwnd:
- # if win32gui.GetWindowText(hwd) == '船舶管理':
- # width, height = get_real_resolution()
- # print(f'激活窗口:{width};{height}')
- # win32gui.ShowWindow(hwd, win32con.SW_MAXIMIZE)
- # win32gui.MoveWindow(hwd, (width - 1750) // 2, (height - 850) // 2, 1750, 850, True)
- # status = True
- # break
- #
- # if not status:
- # webview.start(localization=chinese, debug=False, http_server=True)
-