社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

别再为Python GUI发愁了!这个库让你5分钟就能做出炫酷桌面应用

A逍遥之路 • 1 周前 • 20 次点击  

关键词:Python桌面应用开发、Pywebview教程、Python GUI、Web技术桌面化、跨平台应用开发

还在为Python的桌面应用开发而头疼吗?Tkinter太丑,PyQt太复杂?今天给大家介绍一个宝藏库——Pywebview,让你用熟悉的HTML、CSS、JavaScript就能做出专业级的桌面应用!

🚀 什么是Pywebview?为什么这么香?

说白了,Pywebview就是把网页装进一个独立的桌面窗口里,然后让Python和网页可以互相"聊天"。

想象一下这个场景:

  • 你已经会写网页了(HTML、CSS、JS)

  • 你也会Python编程

  • 但是做桌面应用总是卡壳

Pywebview就是来拯救你的!它让你可以:

  • ✅ 用Web技术做界面(再也不用跟Tkinter的丑UI斗争)

  • ✅ 用Python处理业务逻辑(发挥Python的强大生态)

  • ✅ 一套代码跨三个平台(Windows、Mac、Linux)

  • ✅ 打包后就是原生应用(用户根本看不出是网页)

📦 快速上手:第一个Hello World

安装Pywebview

# 基础安装
pip install pywebview

# 如果你想要更好的性能,可以选择安装额外组件
# Windows用户推荐:
pip install pywebview[cef]

# Linux用户可能需要:
pip install pywebview[gtk]

最简单的示例:打开一个网页

# hello_world.pyimport webview
def main():    # 创建一个窗口,显示百度首页    webview.create_window('我的第一个应用''https://www.baidu.com')    webview.start()
if __name__ == '__main__':    main()

运行这段代码,你就会看到一个独立的窗口打开了百度首页。是不是很简单?

🎨 进阶玩法:用本地HTML做界面

创建本地HTML文件

先创建一个简单的HTML文件:

html><html><head>    <meta charset="UTF-8">    <title>计算器应用title>    <style>        body {            font-family'Microsoft YaHei', Arial, sans-serif;            margin0;            padding20px;            backgroundlinear-gradient(135deg#667eea 0%#764ba2 100%);            color: white;            display: flex;            justify-content: center;            align-items: center;            min-height100vh;        }        .calculator {            backgroundrgba(2552552550.1);            padding30px;            border-radius15px;            box-shadow0 15px 35px rgba(0000.3);            text-align: center;            backdrop-filterblur(10px);        }        input {            width200px;            padding15px;            margin10px;            border: none;            border-radius8px;            font-size16px;            text-align: center;        }        button {            background#ff6b6b;            color: white;            border: none;            padding15px 30px;            margin10px;            


    
border-radius8px;            cursor: pointer;            font-size16px;            transition: background 0.3s;        }        button:hover {            background#ff5252;        }        #result {            font-size24px;            margin20px 0;            padding15px;            backgroundrgba(2552552550.2);            border-radius8px;        }    style>head><body>    <div class="calculator">        <h1>🧮 超级计算器h1>        <input type="number" id="num1" placeholder="输入第一个数字">        <input type="number" id="num2" placeholder="输入第二个数字">        <br>        <button onclick="calculate('add')">➕ 加法button>        <button onclick="calculate('subtract')">➖ 减法button>        <button onclick="calculate('multiply')">✖️ 乘法button>        <button onclick="calculate('divide')">➗ 除法button>        <div id="result">结果将在这里显示div>    div>
    <script>        function calculate(operation) {            const num1 = parseFloat(document.getElementById('num1').value);            const num2 =  parseFloat(document.getElementById('num2').value);
            if (isNaN(num1) || isNaN(num2)) {                document.getElementById('result').innerText = '请输入有效的数字!';                return;            }
            // 调用Python函数            pywebview.api.calculate(num1, num2, operation).then(result => {                document.getElementById('result').innerText = `结果:${result}`;            });        }    script>body>html>

Python后端逻辑

# calculator_app.pyimport webviewimport os
class Api:    def calculate(self, num1, num2, operation):        """处理计算逻辑"""        try:            if operation == 'add':                result = num1 + num2            elif operation == 'subtract':                result = num1 - num2            elif operation == 'multiply':                result = num1 * num2            elif operation == 'divide':                if num2 == 0:                    return "错误:除数不能为0"                result = num1 / num2            else:                return "未知操作"
            return round(result, 2)        except Exception as e:            return f"计算错误:{str(e)}"
def main():    # 创建API实例    api = Api()
    # 获取HTML文件的绝对路径    html_path = os.path.abspath('index.html')
    # 创建窗口    webview.create_window(        title='超级计算器',        url=html_path,        js_api=api,        width=500,        height=600,        resizable=False    )
    # 开启开发者工具(调试时使用)    webview.start(debug=True)
if __name__ == '__main__':    main()

运行这个程序,你就有了一个带炫酷界面的计算器应用!

💡 实战项目:文件管理器

让我们做一个更有用的应用——简单的文件管理器。

HTML界面(file_manager.html)

html><html><head>    <meta charset="UTF-8">    <title>文件管理器title>    <style>        * { margin0padding0box-sizing: border-box; }        body {            font-family'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;            background#f5f5f5;        }        .header {            background#2c3e50;            color: white;            padding15px;            display: flex;            align-items: center;            gap10px;        }        .current-path {            background#34495e;            padding8px 12px;            border-radius4px;            font-family: monospace;        }        .toolbar {            background: white;            padding10px;            border-bottom1px solid #ddd;            display: flex;            gap10px;        }        button {            background#3498db;            color: white;            border: none;            padding8px 16px;            border-radius4px;            cursor: pointer;        }        button:hover { background#2980b9; }        .file-list {            padding20px;            display: grid;            grid-template-columnsrepeat(auto-fill, minmax(200px1fr));            gap15px;        }        .file-item {            background: white;            padding15px;            border-radius8px;            box-shadow0 2px 5px rgba(0,0,0,0.1);            cursor: pointer;            transition: all 0.3s;        }        .file-item:hover {            transformtranslateY(-2px);            box-shadow0 4px 15px rgba(0,0,0,0.2);        }        .file-icon {            font-size40px;


    
            text-align: center;            margin-bottom10px;        }        .file-name {            text-align: center;            font-weight: bold;            word-break: break-all;        }    style>head><body>    <div class="header">        <h1>📁 文件管理器h1>        <div class="current-path" id="currentPath">加载中...div>    div>
    <div class="toolbar">        <button onclick="goBack()">⬅️ 返回上级button>        <button onclick="refreshFiles()">🔄 刷新button>        <button onclick="openFileDialog()">📂 选择文件夹button>    div>
    <div class="file-list" id="fileList">        正在加载文件列表...    div>
    <script>        let currentPath = '';
        // 页面加载时获取当前目录        window.addEventListener('DOMContentLoaded'function() {            refreshFiles();        });
        function refreshFiles() {            pywebview.api.get_current_directory().then(path => {                currentPath = path;                document.getElementById('currentPath').textContent = path;                loadFiles(path);            });        }
        function loadFiles(path) {            pywebview.api.list_files(path).then(files => {                displayFiles(files);            });        }
        function displayFiles( files) {            const fileList = document.getElementById('fileList');            fileList.innerHTML = '';
            files.forEach(file => {                const fileItem = document.createElement('div');                fileItem.className = 'file-item';                fileItem.onclick = () => openItem(file);
                const icon = file.is_directory ? '📁' : getFileIcon(file.name);
                fileItem.innerHTML = `                   
${icon}
                   
${file.name}
                `;
                fileList.appendChild(fileItem);            });        }
        function getFileIcon(filename) {            const ext = filename.split('.').pop().toLowerCase();            const iconMap = {                'txt''📄''doc''📄''docx''📄',                'pdf''📕''jpg''🖼️''jpeg''🖼️'                'png''🖼️''gif''🖼️''mp3''🎵',                'mp4''🎬''zip''📦''rar''📦',                'py''🐍''js''💛''html''🌐'            };            return iconMap[ext] || '📄';        }
        function openItem(file) {            if (file.is_directory) {                loadFiles(file.path);                currentPath = file.path;                document.getElementById('currentPath').textContent = file.path;            } else {                pywebview.api.open_file(file.path);            }        }
        function goBack() {            pywebview.api.get_parent_directory(currentPath).then(parentPath => {                if (parentPath) {                    loadFiles(parentPath);                    currentPath = parentPath;                    document.getElementById('currentPath').textContent = parentPath;                }            });        }
        function openFileDialog() {            pywebview.api.choose_directory().then(path => {                if (path) {                    loadFiles(path);                    currentPath = path;                    document.getElementById('currentPath').textContent = path;                }            });        }    script>body>html>

Python后端(file_manager.py)

import webviewimport osimport subprocessimport platformfrom pathlib import Path
class FileManagerApi:    def get_current_directory(self):        """获取当前工作目录"""        return str(Path.cwd())
    def list_files(self, path):        """列出指定目录下的文件和文件夹"""        try:            items = []            path_obj = Path(path)
            for item in path_obj.iterdir():                items.append({                    'name': item.name,                    'path'str(item.absolute()),                    'is_directory': item.is_dir()                })
            # 按类型排序(文件夹在前,然后按名称排序)            items.sort(key=lambda x: (not x['is_directory'], x['name'].lower()))            return items
        except Exception as e:            return [{'name'f'错误:{str(e)}''path''''is_directory'False}]
    def get_parent_directory(self, path):        """获取父目录"""        try:            parent = Path(path).parent            if parent != Path(path):  # 不是根目录                return str(parent)            return None        except:            return None
    def open_file(self, file_path):        """用系统默认程序打开文件"""        try:            system = platform.system()            if system == 'Windows':                os.startfile(file_path)            elif system == 'Darwin':  # macOS                subprocess.run(['open', file_path])            else:  # Linux                subprocess.run(['xdg-open', file_path])            return True        except Exception as e:            print(f"打开文件失败:{str(e)}")            return False
    def choose_directory(self):        """打开文件夹选择对话框"""        try:            result = webview.windows[0].create_file_dialog(                webview.FOLDER_DIALOG            )            if result:                return result[0]            return None        except:            return None
def main():    api = FileManagerApi()
    # 创建窗口    webview.create_window(        title='文件管理器 v1.0',        url='file_manager.html',        js_api=api,        width=900,        height=700,        min_size=(600400)    )
    webview.start(debug=True)
if __name__ == '__main__':    main()

🔥 高级技巧:与前端框架结合

如果你熟悉Vue.js或React,你完全可以用它们来构建更复杂的界面!

简单的Vue.js集成示例

html><html><head>    <meta charset="UTF-8">    <title>Vue + Pywebviewtitle>    <script src="https://unpkg.com/vue@3/dist/vue.global.js">script>    <style>        body { font-family: Arial, sans-serif; padding20px; }        .todo-item { padding10pxmargin5px 0background#f0f0f0border-radius5px; }        .completed { text-decoration: line-through; opacity0.6; }        inputbutton { padding10pxmargin5pxborder1px solid #dddborder-radius4px; }        button { background#007bffcolor: white; cursor: pointer; }    style>head><body>    <div id="app">        <h1>📝 我的待办清单h1>
        <div>            <input v-model="newTodo" @keyup.enter="addTodo" placeholder="输入新的待办事项">            <button @click="addTodo">添加button>        div>
         <div v-for="(todo, index) in todos" :key="index" class="todo-item" :class="{completed: todo.completed}">            <input type="checkbox" v-model="todo.completed" @change="updateTodo(index)">            {{ todo.text }}            <button @click="deleteTodo(index)" style="float: right; background: #dc3545;">删除button>        div>
        <p>总计:{{ todos.length }} 项,已完成:{{ completedCount }} 项p>    div>
    <script>        const { createApp } = Vue;
        createApp({            data() {                return {                    newTodo'',                    todos: []                }            },            computed: {                completedCount() {                    return this.todos.filter(todo => todo.completed).length;                }            },            methods: {                async addTodo() {                    if (this.newTodo.trim()) {                        const todo = { textthis.newTodocompletedfalse };                        this.todos.push(todo);                        await pywebview.api.save_todos(this.todos);                        this.newTodo = '';                    }                },                async deleteTodo(index) {                    this.todos.splice(index, 1);                    await pywebview.api.save_todos(this.todos);                },                async updateTodo(index) {                    await pywebview.api.save_todos(this.todos);                },                async loadTodos() {                    this.todos = await pywebview.api.load_todos();                }            },            async mounted() {                await this.loadTodos();            }         }).mount('#app');    script>body>html>

对应的Python代码

# vue_todo_app.pyimport webviewimport jsonimport os
class TodoApi:    def __init__(self):        self.data_file = 'todos.json'
    def load_todos(self):        """从文件加载待办事项"""        if os.path.exists(self.data_file):            try:                with open(self.data_file, 'r', encoding='utf-8'as f:                    return json.load(f)            except:                return []        return []
    def save_todos(self, todos):        """保存待办事项到文件"""        try:            with open(self.data_file, 'w', encoding='utf-8'as f:                json.dump(todos, f, ensure_ascii=False, indent=2)            return True        except Exception as e:            print(f"保存失败:{str(e)}")            return False
def main():    api = TodoApi()
    webview.create_window(        title='Vue + Python 待办清单',        url='vue_app.html',        js_api=api,        width=600,        height=500    )
    webview.start(debug=True)
if __name__ == '__main__':    main()

📱 打包发布:让你的应用变成真正的软件

开发完成后,你肯定想把应用打包成exe文件分享给别人。推荐使用PyInstaller:

# 安装PyInstaller
pip install pyinstaller

# 打包成单个exe文件
pyinstaller --onefile--windowed calculator_app.py

# 如果有额外的HTML文件,需要添加到打包中
pyinstaller --onefile --windowed--add-data"index.html;." calculator_app.py

🛠️ 常见问题与解决方案

1. 中文显示乱码

# 在HTML的head中添加
<metacharset="UTF-8">

# Python文件保存为UTF-8编码

2. JavaScript调用Python函数失败

# 确保在create_window时传入了js_api参数
webview.create_window(
    title='我的应用',
    url='index.html',
    js_api=api_instance  # 这个很重要!
)

3. 文件路径问题

import os
# 获取当前脚本所在目录current_dir = os.path.dirname(os.path.abspath(__file__))html_path = os.path.join(current_dir, 'index.html')
webview.create_window('应用', html_path)

🎯 为什么选择Pywebview?

经过这么多实例,相信你已经感受到了Pywebview的魅力:

  1. 学习成本低:会写网页就能做桌面应用

  2. 开发效率高 :不用学复杂的GUI框架

  3. 界面现代化:告别Tkinter的上世纪风格

  4. 跨平台无压力:一套代码到处跑

  5. 生态丰富:可以用任何前端框架和库

适合哪些场景?

  • 数据可视化工具

  • 个人效率工具

  • 企业内部管理系统

  • 简单的桌面小应用

  • 原型快速开发

转发、收藏、在看,是对作者最大的鼓励!👏
关注逍遥不迷路,Python知识日日补!






           对Python,AI,自动化办公提效,副业发展等感兴趣的伙伴们,扫码添加逍遥,限免交流群

备注【成长交流】

图片

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/184143
 
20 次点击