Skip to content

Himpq/WebViewUI

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

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages