Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WebViewUI

WebViewUI 是一个基于 PyWebView 的桌面窗口 UI 层,用来快速创建带自绘标题栏、窗口控制 API、前端桥接和基础配置持久化的本地 WebView 应用。

它最初从 PicScanner 项目中拆出,当前重点支持 Windows + WebView2。非 Windows 平台会保留基础 PyWebView 能力,Windows 原生标题栏、拖动、缩放、Aero snap 和导航遮罩能力由 wintitle.py 提供。

特性

  • 自绘标题栏,由 Python 注入到页面,不需要业务 HTML 手写标题栏。
  • 原生窗口行为,保留 Windows 拖动、缩放、最大化、最小化和 Aero snap。
  • WindowApi 基类,统一暴露最小化、最大化、关闭、拖动、缩放等窗口动作。
  • Python 后端对象通过 window.pywebview.api 暴露给前端。
  • 支持主窗口和多子窗口,每个窗口可使用独立 api_prefix
  • 支持 bootstrap 启动页、WebView 深色背景、原生导航遮罩,减少首帧白闪。
  • 内置简单配置持久化,默认写入应用根目录下的 data/config.json
  • 支持 WebView2 DevTools 调试开关。

目录结构

WebViewUI/
  WebViewUI/
    __init__.py       对外导出 WebViewApp、WindowApi、config 等入口
    app.py            WebViewApp 主窗口装配流程
    window_api.py     暴露给 JS 的窗口控制 API 基类
    titlebar.py       标题栏 JS、启动页 HTML、页面补丁 JS 构建器
    wintitle.py       Windows 原生窗口行为层
    config.py         WebViewUI 框架配置读写
  examples/
    minimal/
      app.py          最小运行示例
      index.html      示例前端页面
  README.md
  requirements.txt
  pyproject.toml
  CHANGELOG.md
  LICENSE

环境要求

  • Python 3.9 或更高版本。
  • Windows 推荐安装 Microsoft Edge WebView2 Runtime。
  • Python 依赖见 requirements.txt,当前核心依赖为 pywebview>=5.0

安装依赖:

python -m pip install -r requirements.txt

开发模式安装:

python -m pip install -e .

运行示例

从仓库根目录运行:

python examples/minimal/app.py

示例会创建一个本地 WebView 窗口,并演示前端通过 window.pywebview.api 调用 Python 方法。

最小接入

from pathlib import Path

from WebViewUI import WebViewApp, WindowApi


class MyApi(WindowApi):
    def __init__(self):
        super().__init__()

    def get_startup_state(self):
        return {"success": True, "message": "ready"}


def main():
    entry = Path("ui/index.html").resolve()
    app = WebViewApp(
        entry_url=str(entry),
        js_api=MyApi(),
        title="My App",
        width=1280,
        height=820,
        min_size=(1040, 680),
        brand="My App",
        titlebar_height=36,
        use_bootstrap=False,
        use_native_nav_cover=False,
    )
    app.run()


if __name__ == "__main__":
    main()

关键点:

  • entry_url 可以是本地 HTML 文件路径,也可以是 http://https://file://data: URL。
  • js_api 推荐传入 WindowApi 的子类实例。
  • brand 会显示在 WebViewUI 注入的自绘标题栏左侧。
  • titlebar_height 控制标题栏高度,默认是 36
  • app.run() 会创建窗口并进入 PyWebView 事件循环,是阻塞调用。

后端 API 写法

业务 API 继承 WindowApi

from WebViewUI import WindowApi


class MyApi(WindowApi):
    def __init__(self):
        super().__init__()

    def hello(self, name="WebViewUI"):
        return {"success": True, "message": f"Hello, {name}"}

继承 WindowApi 的原因:

  • WindowApi 已提供最小化、最大化、关闭、拖动、边缘缩放等窗口控制方法。
  • WebViewApp 会把同一个 js_api 绑定到主窗口。
  • 自绘标题栏通过 webview_window_action(action, api_prefix, payload) 调用窗口控制。
  • 业务方法会和窗口控制方法一起暴露给 window.pywebview.api

Python 方法返回值应优先使用可 JSON 序列化的数据,例如 dictliststrintboolNone

前端调用方式

前端需要等待 pywebviewready 后再调用 Python API:

function api() {
  return window.pywebview && window.pywebview.api ? window.pywebview.api : null;
}

function call(name, ...args) {
  const bridge = api();
  if (!bridge || !bridge[name]) {
    return Promise.reject(new Error("pywebview bridge not ready: " + name));
  }
  return bridge[name](...args);
}

window.addEventListener("pywebviewready", async () => {
  const state = await call("get_startup_state");
  console.log(state);
});

不要在业务页面里手写窗口按钮逻辑;标题栏按钮由 WebViewUI 注入并自动连接到 WindowApi

主窗口生命周期

WebViewApp.run() 的主流程:

  1. 读取调试配置,必要时设置 WebView2 启动参数。
  2. 调用 _resolve_entry() 把本地 HTML 路径转换为 file:// URL。
  3. 调用 webview.create_window(...) 创建 PyWebView 窗口。
  4. 调用 self.js_api.bind(win, api_prefix="") 绑定主窗口。
  5. 注册 win.events.shown += self._on_shown
  6. 注册 win.events.loaded += self._on_loaded
  7. 调用 webview.start(...) 启动事件循环。

_on_shown() 负责窗口出现后的原生层初始化:

  • 调用 wintitle.install(win, emulate_snap=False) 安装 Windows 原生行为。
  • 设置 WebView 深色背景,减少首帧白屏。
  • 按配置安装原生导航遮罩。
  • 注入标题栏脚本和页面布局补丁作为 startup script。
  • 启动标题栏 keepalive 线程。
  • bootstrap 模式下延迟跳转到真实页面。

_on_loaded() 负责页面加载后的补充注入:

  • 读取当前 location.href 便于日志定位。
  • 页面加载完成后释放原生导航遮罩。
  • 再次注入标题栏脚本和页面布局补丁。
  • 刷新原生边框并同步最大化状态。
  • 对首帧做尺寸 nudge,稳定布局。

标题栏与页面布局

WebViewUI 的标题栏不是写在业务 HTML 里的,而是由 Python 注入 JS 创建:

build_titlebar_js({
    "titlebar_height": self.titlebar_height,
    "api_prefix": "",
    "brand": brand,
})

标题栏包含:

  • 品牌文字
  • 最小化按钮
  • 最大化或还原按钮
  • 关闭按钮
  • 拖动区域
  • 边缘 resize 热区

窗口按钮统一调用:

window.pywebview.api.webview_window_action(action, API_PREFIX, payload || {})

页面布局补丁由 build_page_patch_js() 注入,会给真实页面补充标题栏高度相关样式。业务 CSS 可以使用:

:root {
  --titlebar-h: var(--nc-titlebar-h, 36px);
}

如果修改 titlebar_height,需要同步检查业务 CSS 中与标题栏高度相关的变量和 calc(...)

Bootstrap 启动页

WebViewApp 支持 bootstrap 启动页:

WebViewApp(
    entry_url="ui/index.html",
    js_api=MyApi(),
    use_bootstrap=True,
)

bootstrap 启动页由 build_bootstrap_html() 生成,它只做通用窗口壳:

  • 深色背景
  • 标题栏
  • 加载文案
  • 最小化、最大化、关闭按钮

本地 HTML 页面如果加载很快,可以设置 use_bootstrap=False,让真实页面直接负责启动体验。

原生导航遮罩

use_native_nav_cover 控制是否启用 Windows 原生导航遮罩:

WebViewApp(
    entry_url="ui/index.html",
    js_api=MyApi(),
    use_native_nav_cover=True,
)

遮罩由 wintitle.install_navigation_cover() 安装,用于减少 WebView 页面跳转过程中的白闪。如果应用要加载远程页面、频繁导航或首屏资源较重,可以开启它,并观察 _on_shown()_on_loaded() 中的日志。

DevTools 调试

WebViewApp 支持通过参数或配置启用 DevTools:

WebViewApp(
    entry_url="ui/index.html",
    js_api=MyApi(),
    devtools=True,
)

启用后会设置 WebView2 参数:

--remote-debugging-port=<devtools_port>
--auto-open-devtools-for-tabs

对应配置在 data/config.json,默认值来自 WebViewUI/config.py

{
  "devtools_enabled": false,
  "devtools_auto_open": false,
  "devtools_port": 9222
}

运行时日志会输出:

[WebViewUI] devtools enabled, inspect: http://127.0.0.1:9222

配置文件

WebViewUI/config.py 会在应用根目录下创建:

data/config.json

默认配置包括:

window_width
window_height
window_min_width
window_min_height
native_navigation_cover
use_bootstrap_shell
devtools_enabled
devtools_auto_open
devtools_port
message.bootstrap
app_id

使用方式:

from WebViewUI import config

width = config.get("window_width", 960)
config.set("devtools_enabled", True)
snapshot = config.snapshot()

注意:

  • Config 只管理 WebViewUI 框架配置。
  • 业务配置、用户数据、文件权限、数据库等应放在业务模块中维护。
  • app_id 首次运行自动生成并持久化。

多窗口

WindowApi.create_child_window(opts) 可以创建带自绘标题栏的子窗口。所有窗口共享同一个 js_api 实例,每个窗口用 api_prefix 区分。

示例:

from pathlib import Path

from WebViewUI import WindowApi


class MyApi(WindowApi):
    def open_help_window(self):
        return self.create_child_window({
            "url": Path("ui/help.html").resolve().as_uri(),
            "title": "Help",
            "width": 720,
            "height": 520,
            "min_size": (520, 360),
            "brand": "Help",
            "titlebar_height": 36,
            "api_prefix": "help_",
            "use_bootstrap": True,
            "bootstrap_msg": "加载中",
        })

opts 支持字段:

url 或 html              子窗口入口,二选一
title                   窗口标题
width, height            初始尺寸
min_size                 最小尺寸
brand                    标题栏品牌
titlebar_height          标题栏高度
use_bootstrap            是否使用启动页
bootstrap_msg            启动页文案
use_native_nav_cover     是否启用原生导航遮罩
api_prefix               子窗口 API 前缀

返回值:

{"success": True, "api_prefix": "help_", "reused": False}

如果相同 api_prefix 的窗口已经存在,会激活已有窗口并返回:

{"success": True, "api_prefix": "help_", "reused": True}

WindowApi 提供的窗口动作

WindowApi.webview_window_action(action, api_prefix="", payload=None) 是标题栏统一入口。

支持动作:

minimize_window
maximize_window
close_window
start_window_drag
start_window_resize
is_window_maximized
sync_window_state
titlebar_double_click

主窗口也暴露了同名便捷方法:

minimize_window()
maximize_window()
close_window()
start_window_drag()
start_window_resize(edge="right")
is_window_maximized()
sync_window_state()
titlebar_double_click()

前端业务代码通常不需要直接调用这些方法,因为标题栏注入脚本已经处理了按钮、拖动、双击和边缘缩放。

Windows 原生行为层

wintitle.py 是 Windows 专用层,非 Windows 平台相关函数为空操作。

它负责:

  • 安装 custom chrome 行为。
  • 保留 Windows 原生拖动、缩放、最大化动画和 Aero snap。
  • 同步最大化状态到标题栏按钮。
  • 设置 WebView2 深色背景。
  • 安装和释放导航遮罩。
  • 刷新窗口边框,稳定首帧布局。

业务代码通常不直接调用 wintitle.py。需要扩展窗口行为时,优先在 WindowApiWebViewApp 层加清晰的公开方法。

维护检查清单

修改或发布新版本时建议确认:

  1. 更新 CHANGELOG.md 中的版本号和日期。
  2. 运行 python -m compileall -q WebViewUI examples
  3. 运行 python examples/minimal/app.py 确认可视窗口和前端桥接正常。
  4. 如需发布到 PyPI,先执行 python -m build 检查打包产物。

函数与流程总结

核心函数:

  • WebViewApp.__init__():保存窗口参数、构建标题栏脚本、准备配置。
  • WebViewApp._resolve_entry():把本地文件路径转换为 file:// URL。
  • WebViewApp.run():创建主窗口、绑定 API、注册事件并启动 PyWebView。
  • WebViewApp._on_shown():安装原生窗口行为、注入 startup script、启动 keepalive。
  • WebViewApp._on_loaded():真实页面加载后再次注入标题栏和页面补丁。
  • WebViewApp.create_child():委托 WindowApi.create_child_window() 创建子窗口。
  • WindowApi.bind():把窗口实例绑定到指定 api_prefix
  • WindowApi.webview_window_action():标题栏 JS 调用窗口动作的统一入口。
  • WindowApi.create_child_window():创建带自绘标题栏的子窗口。
  • build_titlebar_js():生成自绘标题栏注入脚本。
  • build_page_patch_js():生成页面布局补丁脚本。
  • build_bootstrap_html():生成 bootstrap 启动页 HTML。

主流程:

python app.py
  -> 创建 WindowApi 子类实例
  -> 创建 WebViewApp(entry_url=..., js_api=...)
  -> WebViewApp.run()
  -> webview.create_window(js_api=...)
  -> WindowApi.bind(win, api_prefix="")
  -> webview.start()
  -> 窗口 shown: 安装原生行为、注入标题栏、启动 keepalive
  -> 页面 loaded: 再次注入标题栏和页面补丁、刷新窗口状态
  -> 前端 pywebviewready
  -> 前端通过 window.pywebview.api 调用 Python API

License

WebViewUI 使用 GNU General Public License v3.0 许可证。详见 LICENSE

About

Reusable PyWebView desktop window shell with custom titlebar and window APIs.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages