为了账号安全,请及时绑定邮箱和手机立即绑定

我为Python UI制作了一个拖放构建器 🤯。下面是如何构建它的过程。

标签:
Python 开源

要是你记得,大概两周前,我说过我正在为Python开发一个拖拽界面构建工具。

嗯……终于来了……

你可以在这里看看 PyUIBuilder(一个用于构建PyUI的工具)。

代码库: https://github.com/PaulleDemon/PyUIBuilder

这个构建工具能做什么?
简而言之,它能帮助你快速搭建Python的用户界面,并生成多种库和框架的UI代码,包括Tkinter和customtkinter等。想了解更多功能,请参阅功能部分

但我不仅想发起一个项目,我还希望与你们分享我的经验。这次博客里,我将分享我的思考过程以及构建这个应用的高层次的概述。

想到这个点子

跟大家通常想的不一样,Python 经常被用来快速搭建应用,特别是在做数据科学、自动化、脚本等工作的人里很受欢迎。很多内部工具和图形界面,尤其是在科研领域中,因为 Python 简单,还有一堆框架可以选,比如 Tkinter、PyQt 等,都是用 Python 构建的。

idea

现在,有很多用于网页的拖拽构建器,但在Python GUI领域却很少,尤其是对于tkinter。我见过几个,但问题是这些构建器可使用的部件种类很少,或者生成的是XML格式的代码,这在用Python开发UI时并不是最理想的选择。

所以,最开始我只是想为Tkinter做一个好用的拖放式界面搭建工具。

我一直在琢磨一个理想的GUI构建器(无意玩文字游戏)。受到Canva界面的启发,想出了一些特性,让我的GUI更加理想。

  1. 所有小部件应像插件一样制作。
  2. 应支持以插件形式存在的第三方UI小部件,以便增强灵活性。
  3. 应能够上传诸如图片、视频等素材。
  4. 应生成Python代码。

所以,在七月底左右,我决定开始这个项目

把这个想法再延伸一下

最初它被称为tkbuilder,意味着它是一个用于Tkinter用户界面库的GUI构建器。

但是,你可能注意到了,我也可以进一步发展这个想法来支持多个Python GUI框架和库,因为这些都是插件形式的,这正是我的计划。

我们要制定初始版本的计划。

对于最初版本,我不想添加太多可能会让用户感到不知所措的功能。我想根据实际使用中人们的反馈来构建它。这样我就不会浪费时间在构建那些人们不需要的功能上。

一开始我就决定不使用后端或任何注册表。这样对我来说开发更简单,对用户使用起来也更简单。我只是想要一个简单的前端界面,让人们可以轻松上手。

选择你想要的编程语言:JS、TS、或 Python

choosing a language

是的,我考虑了很久,大多数Python的图形界面构建工具都是用Python实现的。我的第一个选择就是PySide。

几年前我用PyQt/Pyside构建的最复杂的一个GUI应用程序是一个节点编辑器(请参见https://github.com/PaulleDemon/PythonDesigner)。

但很快我意识到,用Python构建初始版本的局限性。

  • Python的UI库没有很多第三方控件来帮助我快速搭建初始版本。
  • 将Python应用作为exe文件分发并不容易,而使用JS可以将其分发为一个electron应用。
  • 大多数人更喜欢使用网页,而不是从一个不熟悉的网站下载可执行文件。

TypeScript 也是一种选择,但用 TypeScript 的时候,我总觉得它有点啰嗦

这些是我第一时间注意到的唯一几件事,所以我选择了使用JS。

PS:后来我想,要是当初从TS开始就好了,但这又是另一个故事了,留待以后再说。

有框架还是没框架。

我最熟悉的框架库是 React.js,但是创建抽象通常需要用到类,自从有了钩子(hooks)以来,这不再被推荐。

不用框架的问题是我得亲自动手构建所有东西,而且不能用上React那些丰富的组件库。

两者都有各自的取舍,但 React 类组件仍然可以继续使用,所以我自然而然地选择了它。

沮丧起步

我在八月初从最基础的部分和侧边栏开始构建,但由于资金不足不得不停止,所以我接了一个客户的项目,但不幸的是,他没有付清尾款。我也尝试了众筹,但也没有凑到足够的资金。

所以,在九月,我用手里剩余不多的钱决定把所有精力都投入到这个项目。到了9月9号,我就从那天开始继续干活。

提前规划 ...

提前做好规划

花了大量的时间思考基本抽象概念,这个概念可以扩展以适应规模,以适应需求。

  1. 想要一个可以像 Figma 一样缩放和平移的画布。
  2. 一个所有其他组件都可以继承的基础组件。
  3. 一个可以将UI元素拖放到画布上的拖放功能。

要使用 React 构建,你需要按照特定的方式去思考和构建。尽管关于它究竟是库还是框架一直存在争议,它更像是一种框架,而不是库。

UI设计

我一直觉得Canva的侧边栏设计很赞,我也想在我的拖放构建器里加入一个类似的侧边栏。

我随手画了心里想的东西,虽然画工不算太好 🙄

界面设计

我对画布和控件交互的想法。

planning ahead...

所以,谁应该负责拖拽、调整大小、选择等操作?是画布还是基本部件?那么画布内的控件又该如何处理?

基础部件会知道它的子元素吗,还是由画布本身通过单一数据结构管理?我在子元素内部如何渲染子元素呢?

拖放操作在画布和其他部件内是如何实现的?

我们打算怎么管理布局呢?

这些是我开始构建整个东西之前问的一些问题。

虽然现在的界面看起来更简单了,但其实花了很多心思在构建底层上,所以用户看起来简单多了。

基于 HTML Canvas 的方式或不使用 HTML Canvas 的方式。

基于数字画布的方案

现在 HTML 有一个默认的 Canvas 元素,可以让你做很多事情,比如绘制、添加图像和类似的事情,现在它看起来非常适合用于我的程序。

所以,我开始查看是否存在拖拽、缩放和平移的现有实现。我发现FabricJs,这非常适合我的需求。

我尝试用Fabric.Js做了一些实验,如下所示,并尝试将整个项目用fabric.js实现,如你在这个实现中可以看到,但是关于canvas有一件事是出乎我意料的。

  1. 我开始使用基于钩子(hooks)的方法来构建画布,但是fabric.js的dispose函数是异步的,所以它与Hooks不兼容,因此不能很好地协同工作。
  2. 画布不能包含如div或其他元素的子元素,这使布局管理器的构建稍微复杂一些。
  3. 在画布上进行任何调试都非常困难,因为画布内部的元素不会在开发者工具的元素检查中显示。

非基于Canvas的方法
经过一番尝试后,非Canvas的方法似乎更好,因为我可以利用内置的布局管理器,还有许多预构建的UI组件可供选择,这在扩展时是理想的选择。

我打算用两个不同的div来模拟canvas,一个内部div和一个外部容器。

现在实现缩放和平移非常容易,因为CSS已经有了transform(变换)、scale(缩放)和translate(平移)。

要实现这一点,我需要一个容器来容纳这个画布。这个画布是不可见的元素(没有设置溢出隐藏),所有元素都放在这里,并在这里还会进行缩放和平移。

这个箱子

为了放大,我需要增加比例;为了缩小,我则需要减少比例。

试试这个简单的例子。按+键来放大视图,按-键来缩小视图。

平移操作类似

拖拽与放置

drag and drop

刚开始的时候,我研究了几款库,比如React-beautiful-DndReact Dnd-kitReact Swappy等。

经过研究,我看到 react-beautiful-dnd 不再被维护,于是开始尝试 React dnd-kit。在开始构建过程中,我发现 dnd-kit 的文档对于我正在做的项目来说有点不足。此外,即将发布的新版本可能会导致库发生重大变化,因此我决定暂时搁置 react-dnd-kit,等到重大版本发布后再继续。

我用HTML的拖放API重写了之前使用DND-kit的部分。唯一的限制是,原生的拖放API在一些触屏设备上仍然不受支持,但这对我来说并不重要,因为我是在为非触屏设备开发。

单一事实标准

勇敢地面对现实吧!

在构建这样的应用程序时,很容易迷失在众多变量和变化中。因此,我不能让多个变量跟踪同一信息。

每个小部件的信息或状态应当由画布或者小部件自身来保存,并在收到请求时传递信息。

或者使用像redux之类的状态管理库。

我尝试了不同的方法之后,选择了让Canvas组件管理所有关于小工具的信息。

数据结构大概长这样

    [
      {
        id: "", // 小部件ID
        widgetType: WidgetClass,  // 基础小部件
        children: [], // 子小部件也将具有相同的结构
        parent: "", // 当前小部件的父ID
        initialData: {} // 即将渲染的小部件的数据信息,例如:背景颜色,前景颜色等
      }
    ]

点击全屏按钮 点击退出全屏按钮

React上下文管理器

现在我想让侧边栏上传的资产可以通过工具栏访问。但是每次我切换侧边标签,重新加载内容会导致上传的资产消失不见。

Redux的一个主要限制是只能存储序列化数据。非序列化数据,例如图片、视频和其他资产,不能存储在Redux中。这使得在不同组件间传递常用数据变得比较麻烦。

简而言之,React Context 提供了一种方法,可以在组件树中传递数据而无需手动逐层传递 prop。一种克服这个问题的方法是使用 React Context。

我所要做的就是用 React 上下文提供者把数据包装起来,这样就可以在不同的组件中使用这些数据了。

我为自己做了两个这样的东西:

  1. 拖拽 - 启用从侧边栏拖拽以及在子组件内的拖拽功能。
  2. 文件上传 - 让上传的文件在每个组件的工具栏上可见。

这里是一个我使用了 React 的上下文进行拖放的简单例子。


    import React, { createContext, useContext, useState } from 'react'

    const DragWidgetContext = createContext()

    export const useDragWidgetContext = () => useContext(DragWidgetContext)

    // 包裹需要拖放功能的组件,提供拖放操作的开始和结束处理
    export const DragWidgetProvider = ({ children }) => {
        const [draggedElement, setDraggedElement] = useState(null)

        const onDragStart = (element) => {
            setDraggedElement(element)
        }

        const onDragEnd = () => {
            setDraggedElement(null)
        }

        return (
            <DragWidgetContext.Provider value={{ draggedElement, onDragStart, onDragEnd }}>
                {children}
            </DragWidgetContext.Provider>
        )
    }

进入全屏 关闭全屏

好的!就是这个。现在我需要做的就是将它包裹在我需要上下文的组件上,我的情况是在Canvas和侧边栏上。

写代码

责任
责任是每个人都要承担的重要事情。

因为每个部件的行为不同,并且有自己的属性,我决定让部件负责生成自己的代码,而代码引擎仅处理变量名冲突和代码拼接。

这样一来,我很容易就能扩展支持许多预构建的小工具以及一些用户界面插件。

开始直播

我没有后端服务也没有设置注册表单功能,而且有很多公司提供静态页面的免费托管服务。我最开始打算用Vercel,但发现当请求量太大时,Vercel的免费套餐可能会宕机。

那时候我知道了Cloudflare Pages的服务。他们的免费套餐几乎包含了所有功能,而且大部分是不限量的。从那时起,我就主要使用Cloudflare了。

唯一的缺点就是构建过程比较慢,而且文档不够充分。

最烦人的部分是构建失败,在 Vercel 上可以正常工作,但在 cloudflare pages 上却不行?日志也不是很清楚,这让我更难找到问题所在。在免费套餐里,我们每个月只能有 500 次构建机会,所以我尽量避免在失败的构建上浪费这些有限的机会。

我尝试了好几个小时,干脆将其设为空字符串。

设置 CI 环境变量为空,然后安装 npm 包

    CI='' npm install

全屏显示,切出全屏

总算上线了。

Live

想看看它最近几个月的进展吗?

我一直都在公开构建这个项目。如果你对它从一个简单的侧边栏一步步成长为一个完整的拖放构建器感兴趣的话,你可以在这里查看整个过程。

#公开构建

哦!别忘了关注我们获取最新消息

星项目 ⭐️

zh: *

如果你喜欢这种类型的内容,我将会撰写更多的博客,深入介绍我是如何计划和构建项目的,想跟上的话,可以关注我的 Substack 订阅号 :)

(Substack简报](https://substack.com/@paulfreeman) (点击此处阅读))

(Note: The parentheses around the link are kept as in the source text, but the expert suggestion to add spaces around the parentheses wasn't directly applicable due to the nature of Markdown formatting which doesn't require spaces for proper functioning. However, the overall readability and typical Chinese web content style are maintained.)

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消