3天速成PyWebView:从零到项目实战的完整攻略

文章目录

3天速成PyWebView:从零到项目实战的完整攻略

概述

PyWebView 是一个轻量级的跨平台库,用于在 Python 中创建桌面 Web 视图应用程序。它使用系统原生 Web 组件(Windows 使用 Edge/IE,macOS 使用 WebKit,Linux 使用 WebKit/GTK),让你能够用 HTML/CSS/JavaScript 构建 GUI。

主要特点

  • 跨平台支持(Windows、macOS、Linux)
  • 无需外部 Web 服务器
  • 支持 Python 与 JavaScript 双向通信
  • 轻量级,依赖少
  • 支持多窗口应用

安装与配置

基础安装

pip install pywebview

平台特定依赖

Windows: 通常无需额外依赖(使用系统自带的 Edge/IE)
macOS: 无需额外依赖(使用系统 WebKit)
Linux: 需要安装 WebKitGTK

# Ubuntu/Debian
sudo apt install webkit2gtk-4.0

# Fedora
sudo dnf install webkit2gtk4.0

# Arch Linux
sudo pacman -S webkit2gtk

基础使用

最简单的示例

import webview

# 创建并显示一个简单的浏览器窗口
webview.create_window("Hello World", "https://pywebview.flowrl.com/")
webview.start()

加载本地 HTML

import webview
import os

# 获取当前目录
current_dir = os.path.dirname(os.path.abspath(__file__))
html_file = os.path.join(current_dir, 'index.html')

webview.create_window("本地应用", html_file)
webview.start()

进阶功能

Python 与 JavaScript 交互

从 Python 调用 JavaScript
import webview

def evaluate_js(window):
    result = window.evaluate_js("""
        document.title = "新标题";
        return "JavaScript 执行完成";
    """)
    print(result)  # 输出: JavaScript 执行完成

window = webview.create_window("JS 交互示例", "https://example.com")
webview.start(evaluate_js, window)
从 JavaScript 调用 Python
import webview

class API:
    def __init__(self):
        self.data = "来自 Python 的数据"
    
    def process_data(self, data):
        print(f"收到来自 JavaScript 的数据: {data}")
        return f"处理后的数据: {data.upper()}"

api = API()
window = webview.create_window("API 示例", "index.html", js_api=api)
webview.start()

在 HTML 中调用 Python 方法:

<!DOCTYPE html>
<html>
<head>
    <title>API 测试</title>
</head>
<body>
    <button onclick="callPython()">调用 Python 方法</button>
    
    <script>
        async function callPython() {
            try {
                const result = await pywebview.api.process_data("hello from js");
                alert(result);
            } catch (e) {
                console.error(e);
            }
        }
    </script>
</body>
</html>

多窗口应用

import webview
import threading

def open_second_window():
    second_window = webview.create_window(
        "第二个窗口", 
        "second.html",
        width=400,
        height=300
    )
    webview.start()

# 主窗口
main_window = webview.create_window(
    "主窗口",
    "main.html",
    width=800,
    height=600
)

# 在按钮点击或其他事件中打开第二个窗口
threading.Thread(target=open_second_window).start()

webview.start()

自定义窗口样式

import webview

window = webview.create_window(
    title="自定义窗口",
    url="index.html",
    width=1024,
    height=768,
    resizable=True,
    fullscreen=False,
    min_size=(400, 300),
    frameless=False,  # 无边框窗口
    easy_drag=True,   # 允许拖动
    on_top=False      # 置顶窗口
)

webview.start()

代码示例

完整的桌面应用示例

import webview
import json
from datetime import datetime

class TodoApp:
    def __init__(self):
        self.tasks = []
    
    def add_task(self, task_text):
        task = {
            'id': len(self.tasks) + 1,
            'text': task_text,
            'created': datetime.now().isoformat(),
            'completed': False
        }
        self.tasks.append(task)
        return task
    
    def get_tasks(self):
        return self.tasks
    
    def complete_task(self, task_id):
        for task in self.tasks:
            if task['id'] == task_id:
                task['completed'] = True
                return True
        return False

# 创建应用实例
todo_app = TodoApp()

# 创建窗口
window = webview.create_window(
    "待办事项应用",
    "todo_app.html",
    js_api=todo_app,
    width=600,
    height=800,
    resizable=True
)

if __name__ == '__main__':
    webview.start(debug=True)  # debug=True 启用开发者工具

对应的 HTML 文件 (todo_app.html):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>待办事项</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 500px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .input-group {
            display: flex;
            margin-bottom: 20px;
        }
        #taskInput {
            flex: 1;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        button {
            padding: 10px 15px;
            background: #007cba;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin-left: 10px;
        }
        button:hover {
            background: #005a87;
        }
        .task-item {
            padding: 10px;
            border-bottom: 1px solid #eee;
            display: flex;
            justify-content: between;
            align-items: center;
        }
        .task-item.completed {
            text-decoration: line-through;
            color: #888;
        }
        .task-text {
            flex: 1;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>待办事项</h1>
        <div class="input-group">
            <input type="text" id="taskInput" placeholder="添加新任务...">
            <button onclick="addTask()">添加</button>
        </div>
        <div id="taskList"></div>
    </div>

    <script>
        async function loadTasks() {
            try {
                const tasks = await pywebview.api.get_tasks();
                renderTasks(tasks);
            } catch (e) {
                console.error('加载任务失败:', e);
            }
        }

        async function addTask() {
            const input = document.getElementById('taskInput');
            const text = input.value.trim();
            
            if (text) {
                try {
                    await pywebview.api.add_task(text);
                    input.value = '';
                    loadTasks();
                } catch (e) {
                    console.error('添加任务失败:', e);
                }
            }
        }

        async function completeTask(taskId) {
            try {
                await pywebview.api.complete_task(taskId);
                loadTasks();
            } catch (e) {
                console.error('完成任务失败:', e);
            }
        }

        function renderTasks(tasks) {
            const taskList = document.getElementById('taskList');
            taskList.innerHTML = '';
            
            tasks.forEach(task => {
                const taskElement = document.createElement('div');
                taskElement.className = `task-item ${task.completed ? 'completed' : ''}`;
                taskElement.innerHTML = `
                    <span class="task-text">${task.text}</span>
                    ${!task.completed ? 
                        `<button onclick="completeTask(${task.id})">完成</button>` : 
                        '<span>✓</span>'
                    }
                `;
                taskList.appendChild(taskElement);
            });
        }

        // 页面加载时获取任务
        document.addEventListener('DOMContentLoaded', loadTasks);
        
        // 支持回车键添加任务
        document.getElementById('taskInput').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                addTask();
            }
        });
    </script>
</body>
</html>

文件操作示例

import webview
import json

class FileManager:
    def save_file(self, content):
        try:
            # 打开文件保存对话框
            file_path = window.create_file_dialog(
                webview.SAVE_DIALOG,
                file_types=('JSON files (*.json)', 'All files (*.*)')
            )
            
            if file_path:
                with open(file_path[0], 'w', encoding='utf-8') as f:
                    json.dump(content, f, ensure_ascii=False, indent=2)
                return True
            return False
        except Exception as e:
            return str(e)
    
    def load_file(self):
        try:
            # 打开文件选择对话框
            file_path = window.create_file_dialog(
                webview.OPEN_DIALOG,
                file_types=('JSON files (*.json)', 'All files (*.*)')
            )
            
            if file_path:
                with open(file_path[0], 'r', encoding='utf-8') as f:
                    return json.load(f)
            return None
        except Exception as e:
            return str(e)

file_manager = FileManager()
window = webview.create_window(
    "文件管理器",
    "file_manager.html",
    js_api=file_manager
)

webview.start()

注意事项

1. 跨平台兼容性

import webview
import platform

# 根据平台调整设置
if platform.system() == 'Windows':
    # Windows 特定配置
    pass
elif platform.system() == 'Darwin':  # macOS
    # macOS 特定配置
    pass
elif platform.system() == 'Linux':
    # Linux 特定配置
    pass

2. 安全性考虑

import webview

class SecureAPI:
    def __init__(self):
        self.allowed_methods = ['get_data', 'process_data']
    
    def __getattr__(self, name):
        if name not in self.allowed_methods:
            raise AttributeError(f"方法 {name} 不允许调用")
        return super().__getattribute__(name)
    
    def get_data(self):
        return "安全的数据"
    
    def process_data(self, data):
        # 验证输入数据
        if not isinstance(data, str):
            raise ValueError("数据必须是字符串")
        return f"处理: {data}"

# 使用安全的 API
secure_api = SecureAPI()
window = webview.create_window("安全应用", "index.html", js_api=secure_api)

3. 错误处理

import webview
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)

class RobustAPI:
    def safe_method(self, data):
        try:
            # 业务逻辑
            result = self._process_data(data)
            return {'success': True, 'data': result}
        except Exception as e:
            logging.error(f"方法执行失败: {e}")
            return {'success': False, 'error': str(e)}
    
    def _process_data(self, data):
        # 实际的数据处理
        if not data:
            raise ValueError("数据不能为空")
        return data.upper()

# 创建窗口时的错误处理
try:
    window = webview.create_window("健壮应用", "index.html")
    webview.start()
except Exception as e:
    logging.error(f"应用启动失败: {e}")

4. 性能优化

import webview
import threading
import time

class PerformanceAPI:
    def __init__(self):
        self.cache = {}
    
    def heavy_operation(self, data):
        # 模拟耗时操作
        cache_key = str(hash(data))
        
        if cache_key in self.cache:
            return self.cache[cache_key]
        
        # 模拟处理时间
        time.sleep(2)
        result = f"处理结果: {data}"
        self.cache[cache_key] = result
        
        return result
    
    def background_task(self, callback_name):
        def long_running_task():
            # 模拟后台任务
            for i in range(5):
                time.sleep(1)
                # 通过 JavaScript 回调更新进度
                window.evaluate_js(f"{callback_name}({i + 1})")
        
        threading.Thread(target=long_running_task, daemon=True).start()
        return "后台任务已启动"

api = PerformanceAPI()
window = webview.create_window("性能优化示例", "performance.html", js_api=api)

总结

优点

  1. 简单易用: API 设计直观,学习曲线平缓
  2. 跨平台: 支持主流桌面操作系统
  3. 轻量级: 不依赖复杂的 GUI 框架
  4. 灵活: 可以使用现代 Web 技术构建界面
  5. 功能丰富: 支持文件对话框、自定义窗口等

适用场景

  • 快速原型开发
  • 企业内部工具
  • 数据可视化应用
  • 需要 Web 技术但希望桌面部署的应用

最佳实践

  1. 始终进行错误处理
  2. 验证 JavaScript 传入的数据
  3. 使用类型注解提高代码可读性
  4. 考虑性能,避免阻塞主线程
  5. 测试不同平台的兼容性

扩展资源

  • 官方文档: https://pywebview.flowrl.com/
  • GitHub 仓库: https://github.com/r0x0r/pywebview
  • 示例项目: https://github.com/r0x0r/pywebview/tree/master/examples

PyWebView 是一个强大的工具,特别适合那些希望利用 Web 技术创建桌面应用的开发者。通过合理的架构设计和遵循最佳实践,你可以构建出既美观又功能强大的跨平台桌面应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值