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)