前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你使用python实现ui框架

手把手教你使用python实现ui框架

原创
作者头像
brzhang
发布2023-12-30 23:01:30
4100
发布2023-12-30 23:01:30
举报
文章被收录于专栏:玩转全栈玩转全栈

其实,我本人是抗拒使用 Python 去实现一个 UI 框架的,因为做 App 应用,React Native,Flutter 基本上在江湖上已经是公认的比较合适的选择,而且对于技术栈是 Python 的朋友,有一些流行的UI框架,可以用于构建跨平台的桌面应用程序。其中一些框架包括Tkinter、PyQt、wxPython和Kivy等。这些框架提供了创建窗口、按钮、文本框等UI元素的功能,并且可以在不同的操作系统上运行。但是,我想要说的,别人有是有,自己动手整一个,是可以加深对这方面原理的了解的,这很重要,你会用是一回事,能不能用的好那就是另外一回事了,想必作为 Pythoner,你是希望作为后者的,那我建议你还是来看看。因此,学习本文,你可以了解如何自己动手实现一个 Python 上的 UI 框架。

我思来想去,打算使用 SwiftUI的方式来实现一个最最基本的 Python 上的响应式 UI 吧,ok,我们的目标大概是会写起来想这样。

SwiftUI是一个声明式的UI框架,它允许开发者以一种非常简洁和直观的方式来描述用户界面,而不是通过命令式的代码来操作UI元素。

在Python中,要创建一个类似的框架,咱们需要考虑以下几个关键点:

  1. 渲染引擎:一个能够绘制基本图形元素的渲染引擎。这可以是基于OpenGL的,或者使用现有的库如Pygame、Pyglet等。
  2. 事件处理:一个事件循环来处理用户输入,如鼠标点击、键盘输入等。
  3. 组件系统:咱们要定义一套组件系统,允许用户创建按钮、文本框、列表等UI元素。
  4. 数据绑定:咱们需要实现一种机制来绑定UI元素到数据源,以便当数据变化时,UI可以自动更新。
  5. 声明式语法:咱们需要定义一种简洁的语法,让用户能够以声明式的方式来描述UI。
  6. 布局系统:咱们需要一个布局系统来自动处理组件的位置和大小。

怎么说呢,实现这些东西,的确是比较难的,尤其是让我们实现渲染引擎,这么一篇短短的文章,不是太现实,但是,我们总算是有点思路的,千里之行,始于足下,我们先往前走两步。站在巨人的肩膀上,如,站在Tkinter的肩膀上,来实现一个极其简单的,就登录页面,来验证下吧。为什么选择Tkinter呢,问这这个正好操作系统上自带就有,免得去安装,占用磁盘空间,另外一个,这个确实也比较熟悉了。那么我们将Tkinter 略微封装封装,整成一个声明式的 UI的化,也不准备给他实现的太全了,就封装一下 Button,Label,Input 吧,因为基于这个我们就可以实现一个简单的登录页了。ok,代码如下:

代码语言:javascript
复制
import tkinter as tk

# 声明式组件类

class Label:
    def __init__(self, text):
        self.text = text
        self.widget = None

    def render(self, parent):
        if self.widget is None:
            self.widget = tk.Label(
                parent, justify="left", text=self.text, font=('Arial', 12))
        return self.widget

class Input:
    def __init__(self, placeholder, isPassWord=False):
        self.placeholder = placeholder
        self.widget = None
        self.isPassWord = isPassWord

    def render(self, parent):
        if self.widget is None:
            self.widget = tk.Entry(parent, font=('Arial', 12))
            self.widget.bind("<FocusIn>", self.on_focus_in)
            self.widget.bind("<FocusOut>", self.on_focus_out)
            if self.isPassWord:
                self.widget["show"] = "*"  # 隐藏密码

        return self.widget

    def on_focus_in(self, event):
        if self.widget.get() == self.placeholder:
            self.widget.delete(0, tk.END)

    def on_focus_out(self, event):
        if not self.widget.get():
            self.widget.insert(0, self.placeholder)

class Button:
    def __init__(self, text, on_click):
        self.text = text
        self.on_click = on_click
        self.widget = None

    def render(self, parent):
        if self.widget is None:
            self.widget = tk.Button(parent, text=self.text, command=self.on_click, font=(
                'Arial', 12))
        return self.widget

# 应用类

class App:
    def __init__(self, title, components):
        self.title = title
        self.components = components
        self.root = tk.Tk()
        self.root.title(title)

    def run(self):
        for component in self.components:
            widget = component.render(self.root)
            widget.pack(pady=5)

        self.root.mainloop()

# 创建登录页面的组件
username_label = Label(text="Username:")
username_entry = Input(placeholder="Enter your username")
password_label = Label(text="Password:")
password_entry = Input(placeholder="", isPassWord=True)
login_button = Button("Login", lambda: print(
    f"Logging in with {username_entry.widget.get()}"))

# 创建并运行应用
app = App("Login Page", [username_label, username_entry,
          password_label, password_entry, login_button])
app.run()

运行一下:

不出意外的画,结果肯定是测试成功了。ok,起码一个声明式的架子是看到了,但是似乎有点寒碜啊,我们家一个布局组件进来来管理一下组件的摆放,原汁原味的组件虽然又不是不能用,但是没布局还是不可以的。我们增加一个布局组件,就比如 FlexLayout,毕竟我们熟悉,ok ,我们增加一个布局类,FlexLayout,如下,当然比较简单,如果是下全部的 FlexLayout,我可能会疯,毕竟虽然 FlexLayout 好用,但是并不那么好写一个完整的,因此只写一个极其简单的。

代码语言:javascript
复制
class FlexLayout:
    def __init__(self, direction='row', justify='start', align='start'):
        self.direction = direction
        self.justify = justify
        self.align = align
        self.children = []
        self.widget = None

    def add(self, component):
        self.children.append(component)

    def render(self, parent):
        if self.widget is None:
            self.widget = tk.Frame(parent)
            self.widget.pack(fill='both', expand=True)

        for child in self.children:
            widget = child.render(self.widget)
            if self.direction == 'row':
                widget.pack(side='left', fill='both', expand=True)
            else:
                widget.pack(side='top', fill='both', expand=True)

        return self.widget

随后,我们将之前的代码改造一下,主要是把 Input,Label,Button 放入到布局中:

代码语言:javascript
复制
# 创建布局
username_frame = FlexLayout(direction='row', justify='center', align='center')
username_frame.add(username_label)
username_frame.add(username_entry)

password_frame = FlexLayout(direction='row', justify='center', align='center')
password_frame.add(password_label)
password_frame.add(password_entry)

button_frame = FlexLayout(direction='row', justify='center', align='center')
button_frame.add(login_button)

# 创建并运行应用
app = App("Login Page", [username_frame, password_frame, button_frame])
app.run()

走你,看看效果:

顺利将 姓名的 Label 和 Input 摆放到了一行啦。

总结

我们这个声明式的 Python UI 框架最终实现的效果基本上算是有了一点点改进,但是恐怕离好用还有着巨大的差距,个人认为,写 UI 的最佳方式应该是类 HTML 那种方式,无论是 React 也好,还是 Vue 也好,更不要说html 原生方式,UI 的部分基本上都是这种方式在做,anyway,有那么一点点改进也是推进了一点。

我更加推荐你去看一看其他的一些Python 的 UI 框架,如:kivy ,它的这种写法已经基本趋向于 web 的方式了,但是还有极大的差距,没办法,这就是语言之间的差距,嗯,这是一道难以逾越的鸿沟,但也不是说不可能,肯定是有大神会想办法变为可能,只不过,这个代驾是否值得而已。

我正在参与2023腾讯技术创作特训营第四期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档