在开发一套系统框架的时候,除了关注实现系统的功能实现外,我们对于系统的各个方面都是应该精益求精,以最少的编码做最好的事情,在开发的各个层次上,包括前端后端,界面处理、后端处理、常用辅助类、控件封装等等方面,我们都可以通过抽象、重用等方式,实现代码的优化、简化,以期达到快速开发的目的。本篇随笔我们就来聊聊界面的抽象迭代处理,以及最终的实现过程。

1、列表窗体界面的抽丝剥茧式的迭代抽象

例如对于系统的窗体来说,一般我们可以按主要的功能视图来区分,一个是列表展示界面,一个是编辑/查看详细内容界面,前面有一篇文章《使用wxpython开发跨平台桌面应用,基类对话框窗体的封装处理》我专门介绍了编辑对话框的抽象设计,所以本篇随笔主要针对列表界面进行介绍。

如我们大致需要一个展示列表的界面,列表界面一般分为查询区、列表界面展示区和分页信息区,我们把它分为两个主要的部分,如下界面所示。

对于查询条件标号为1区的内容,又可以继续细分,根据不同的业务模块,内容有变化区和固定区,如下标识。

不同的业务模块,查询的条件肯定是不同的,这部分为内容变化区。

而对于常见的功能按钮,基本上是固定的,我们后期可以根据一些条件进行动态的按钮显示/隐藏,但是这里业务按钮就这些,虽然触发的界面肯定有所不同,但是这些按钮的处理逻辑是不会变化的,所以称为固定逻辑区。

对于内容变化的,我们可以把它们下发到子类里面,每一个业务模块的列表界面为一个子类,继承基类即可。

其中业务列表窗体界面分为两个部分

而对于通过wx.Grid展示的列表界面部分,虽然分为列表内容和分页栏内容,但是它们的数据变化,控件却是不会变化的,如下界面截图所示。

也就是说,这些控件的相关排版信息,我们可以抽象到父类中进行创建,而数据则由子类进行更新变化即可。

其中包括表格的列名、中文名称对应、列的宽度,数据集合等信息,而分页栏这是根据每页的大小、当前页码、总数等信息进行按钮的状态控制即可。

2、引入泛型定义,实现更丰富的界面控制

我在文章《基于SqlAlchemy+Pydantic+FastApi的Python开发框架》 中介绍过使用泛型来构建更加弹性化的基类处理。

如对于路由器,我们通过泛型参数的处理,让基类的接口更加个性化一些,如下代码所示。

在 Python 中,泛型(Generic) 是一种允许类型参数化的特性,通常用于类型注解和类型检查。Python 的泛型支持主要通过 typing 模块中的类型提示来实现,例如 GenericTypeVarList[T] 等。使用泛型定义基类有以下几个好处:

1. 提高类型安全性

泛型允许在编写类和函数时,明确指定类型参数,这样在使用时可以进行更严格的类型检查,减少类型错误。

如果基类使用了泛型 T,这样在子类或实例化时可以指定具体的类型(如 User),从而在编译期(使用 IDE 或类型检查工具时)获得类型安全性,避免类型错误。

2. 增强代码重用性

泛型允许编写更具通用性的基类,不必针对每种数据类型重复实现类似功能,增加了代码的重用性。

3. 提升代码的可读性和维护性

泛型使得类型参数在类定义中显式化,这样可以帮助开发者更清楚地了解类或函数的预期类型,减少类型推断的复杂度,提升可读性。

4. 与类型提示和类型检查工具集成

现代 Python 开发中,使用类型提示(type hinting)已经成为一种最佳实践。泛型定义能够更好地与类型检查工具(如 mypypyright 等)集成,帮助在编写代码时发现潜在的类型错误。

5. 增强代码的灵活性

使用泛型定义基类可以让类的功能更通用,从而在不修改类代码的情况下,通过类型参数化来实现不同的数据处理逻辑。

以上这些优势使得在大型项目或库开发中使用泛型变得尤为重要,尤其是在设计通用数据结构、工具类或框架时。

泛型定义,我们声明一个类型,如下所示。

ModelType = TypeVar("ModelType")  # 定义泛型基类

然后就可以根据需要采用泛型类型了,如下是窗体基类的定义,采用一个泛型的类型来定义业务模块的子类DTO对象,从而使得该父类很多接口都具有很好的类型化定义。

# 创建泛型基类 BaseFrameList,并继承 wx.Panel
class BaseFrameList(wx.Panel, Generic[ModelType]):

而对于业务模块的子类,如其中业务列表界面的子类定义如下所示。

# 继承BaseFrameList类,并传入实体类SystemTypeInfo,作为泛型类型
class FrmSystemType(BaseFrameList[SystemTypeDto]):

这样我们构建的列表界面父子类的关系如下 所示,其中包括两个业务模块的列表界面:系统类型定义,客户信息。

例如我们以客户列表界面的子类代码进行分析,如下所示。我们只需要传入所需的一些字段显示及中文解析,并传入相关的DTO对象,如下代码所示。

# 继承BaseFrameList类,并传入实体类CustomerInfo,作为泛型类型
class FrmCustomer(BaseFrameList[CustomerDto]):
"""客户信息""" # 显示的字段名称,逗号分隔
display_columns = "id,name,age,creator,createtime"
# 列名映射(字段名到显示名的映射)
column_mapping = {
"id": "编号", "name": "姓名", "age": "年龄", "creator": "创建人", "createtime": "创建时间",
} def __init__(self, parent):
# 初始化基类信息
super().__init__(
parent,
model=CustomerDto,
display_columns=self.display_columns,
column_mapping=self.column_mapping
)

这些内容肯定是必须的,另外,如果我们需要自定义列表界面中单元格列的宽度,也可以指定宽度的字典参照,不指定则使用默认宽度即可(在基类定义默认宽度,如为150像素)。

例如,我在系统类型定义中就包含了列表宽度的字典参考,代码如下所示。

# 继承BaseFrameList类,并传入实体类SystemTypeInfo,作为泛型类型
class FrmSystemType(BaseFrameList[SystemTypeDto]):
"""系统类型定义""" # 显示的字段名称,逗号分隔
display_columns = "id,name,customid,authorize,note"
# 列名映射(字段名到显示名的映射)
column_mapping = {
"id": "系统标识",
"name": "系统名称",
"customid": "客户编码",
"authorize": "授权编码",
"note": "备注",
}
# 表格显示的列宽
column_widths = {"id": 150, "name": 250, "customid": 100, "authorize": 100} def __init__(self, parent):
# 初始化基类信息
super().__init__(
parent,
model=SystemTypeDto,
display_columns=self.display_columns,
column_mapping=self.column_mapping,
column_widths=self.column_widths,
use_left_panel=False,
)

3、变化中提取不变的逻辑,界面代码的简化

而对于子类查询条件的内容,我们前面说它是动态不同的,因此需要子类来具体实现。

下面这个是客户信息的查询内容,我们只需要添加对应的标签和输入控件即可,不需要理会布局的处理,默认的FlexGridSizer为4*2=8列,每列间隔5px。

下面是客户信息的列表界面,重写父类函数的实现代码

    def CreateConditions(self, pane: wx.Window) -> List[wx.Window]:
"""创建折叠面板中的查询条件输入框控件"""
# 创建控件,不用管布局,交给CreateConditionsWithSizer控制逻辑
# 默认的FlexGridSizer为4*2=8列,每列间隔5px lblName = wx.StaticText(pane, -1, "姓名:")
self.txtName = wx.TextCtrl(pane, -1, size=(150, -1))
lblAge = wx.StaticText(pane, -1, "年龄:")
self.txtAge = ctrl.MyNumericRange(pane, -1, size=(150, -1)) return [lblName, self.txtName, lblAge, self.txtAge]

如果有时候需要重新定义布局,那么可以重写它上一级包含布局的函数即可实现更高维度的定制处理。

如对于系统类型的列表界面,如下所示

为了更好介绍对于面板布局的控制,我重写其上一级的函数,包含FlexGridSizer的定义信息,这里我们的代码如下所示。

    def CreateConditionsWithSizer(self, pane: wx.Window):
"""子类可重写该方法,创建折叠面板中的查询条件,包括布局FlexGridSizer""" # 先创建控件
lblSystemType = wx.StaticText(pane, -1, "系统标识:")
self.txtSystemType = wx.TextCtrl(pane, -1, "", size=(150, -1)) lblName = wx.StaticText(pane, -1, "系统名称:")
self.txtName = wx.TextCtrl(pane, -1, "", size=(150, -1)) lblCustomCode = wx.StaticText(pane, -1, "客户编码:")
self.txtCustomCode = wx.TextCtrl(pane, -1, "", size=(150, -1)) lblAuthCode = wx.StaticText(pane, -1, "授权编码:")
self.txtAuthCode = wx.TextCtrl(pane, -1, "", size=(150, -1)) # 增加条件面板
input_sizer = wx.FlexGridSizer(cols=4 * 2, hgap=5, vgap=5) # 统一处理查询条件控件的添加,使用默认的布局方式
list = [lblSystemType, self.txtSystemType, lblName, self.txtName, lblCustomCode, self.txtCustomCode, lblAuthCode, self.txtAuthCode]
for i in range(len(list)):
input_sizer.Add(list[i], 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) # input_sizer.AddGrowableCol(1)
# input_sizer.Add((5, 5)) return input_sizer

而对于子类的编辑查看对话框,我们通过触发按钮进行弹出,或者右键的菜单中弹出,都是同样的逻辑,不过就是界面内容不同,我们可以让子类进行实现即可。

子类对于新增编辑的界面实现代码如下所示,其中Add和OnEditById都是父类的空函数,由子类来具体实现即可(覆盖重写)。

    def OnAdd(self, event: wx.Event) -> None:
"""子类重写-打开新增对话框"""
dlg = FrmSystemTypeEdit(self)
if dlg.ShowModal() == wx.ID_OK:
# 新增成功,刷新表格
self.update_grid() dlg.Destroy() def OnEditById(self, entity_id: Any | str):
"""子类重写-根据主键值打开编辑对话框"""
dlg = FrmSystemTypeEdit(self, entity_id)
if dlg.ShowModal() == wx.ID_OK:
# 更新grid列表数据
self.update_grid()
dlg.Destroy()

其他处理,如删除记录、导入、导出的处理,大同小异,从变化中找到不变的逻辑交给父类处理,子类负责最原始变化的内容即可。

当然对于一些复杂的列表界面,可能还需要考虑左侧放置一些树列表以便快速的选择不同分类的数据,如下Winform上的界面。

这个界面的内容,左侧就是折叠的两个树列表:机构列表、角色列表,以便方便选择用户信息。

那么对于这样的效果,我们在基类窗体中是否可以抽象出来,答案当然是可以的,还记得我们前面《使用wxpython开发跨平台桌面应用,动态工具的创建处理》介绍过的工具栏的时候,使用了Manager类的实现效果。

不过这个是主窗体级别的,我们需要为具体的业务列表界面定义一个类似的效果。

我们在父类窗体中定义一个开关变量,用来开启或者关闭左侧树列表面板的,如下代码所示。

这样构建树列表就交给函数 create_tree_panels 实现即可,它会构建一到多个的树列表,父界面窗体负责整合它们显示即可。

如子类定义重写创建树列表的函数,如下代码所示。

    def create_tree_panels(self) -> dict[str, wx.Panel]:
"""子类重写该方法,创建左侧树列表面板-可以多个树列表"""
dict = {} # 示例代码
for key in ["机构列表", "角色列表"]:
tree_panel = wx.Panel(self)
tree_sizer = wx.BoxSizer(wx.VERTICAL)
tree = CT.CustomTreeCtrl(tree_panel, style=wx.TR_DEFAULT_STYLE)
self._populate_tree(tree)
tree_sizer.Add(tree, 1, wx.EXPAND)
tree_panel.SetSizer(tree_sizer) dict[key] = tree_panel return dict

那么界面效果会获得如下所示。

我们根据需要实现具体的树数据显示即可。

以上就是我对于界面的剖析和逐步的抽象处理,把主要的逻辑提取到父类中去,变化的小部分内容,交给子类差别实现即可,减少代码,提高效率。

使用wxpython开发跨平台桌面应用,基类列表窗体的抽象封装处理的更多相关文章

  1. moviepy音视频开发:音频剪辑基类AudioClip

    ☞ ░ 前往老猿Python博文目录 ░ 一.背景知识介绍 1.1.声音三要素: 音调:人耳对声音高低的感觉称为音调(也叫音频).音调主要与声波的频率有关.声波的频率高,则音调也高. 音量:也就是响度 ...

  2. Electron+Vue开发跨平台桌面应用

    Electron+Vue开发跨平台桌面应用 xiangzhihong发布于 2019-12-23 虽然B/S是目前开发的主流,但是C/S仍然有很大的市场需求.受限于浏览器的沙盒限制,网页应用无法满足某 ...

  3. Delphi 继承基类的窗体,并显示基类的控件操作。

    1.  先建一个普通的窗体,until1 2.  先把类实现基类, 并需要实现基类需要继承的方法, 可以先不用再方法中写实现代码. TForm4 = class(TfrmmtAReportPeriod ...

  4. WPF组件开发之组件的基类

    之前在网上看到很多关于组件开发的资料,但真正可以用到框架内的却很少.今天贴出自己做的组件,并适合大部分框架的代码. 组件开发需要先做出组件的基类,然后由其他的各类组件去继承这个基类,下面是组件基类的代 ...

  5. Filter组件开发中的SDK基类分析

    DirectShow SDK提供了一套开发Filter的基类源代码.基于这些基类开发Filter将大大简化开发过程. 1.CBaseObject 大部分SDK类都从CBaseObject类(参见com ...

  6. iOS开发——项目实战OC篇&类QQ黏性按钮(封装)

    类QQ粘性按钮(封装) 那个,先来说说原理吧: 这里原理就是,在界面设置两个控件一个按钮在上面,一个View在下面(同样大小),当我们拖动按钮的时候显示下面的View,view不移动,但是会根据按钮中 ...

  7. 使用XUL开发跨平台桌面应用

    先上图: 现在使用html,css,js开发桌面的优势越来越明显了,硬件性能的不断提升,人力成本越发昂贵,用户对界面要求越来越高,全球化下企业间的竞争越发激烈. 桌面软件50%+的工作量都在界面开发这 ...

  8. Electron开发跨平台桌面程序入门教程

    最近一直在学习 Electron 开发桌面应用程序,在尝试了 java swing 和 FXjava 后,感叹还是 Electron 开发桌面应用上手最快.我会在这一篇文章中实现一个HelloWord ...

  9. moviepy音视频开发:音频剪辑基类AudioClip详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.背景知识介绍 1.1.声音三要素: 音调:人耳对声音高低的感觉称为音调(也叫音频).音调主要与声波的频率有关.声波的频率高,则音调也高. 音量:也就是响度 ...

  10. c#基类 常用数据验证的封装,数字,字符,邮箱的验证

    #region 常用数据验证的封装,数字字符的验证       /// <summary>       /// 常用数据验证的封装,数字字符的验证       /// </summa ...

随机推荐

  1. zabbix snmp OID 列表

    系统参数(1.3.6.1.2.1.1) OID 描述 备注 请求方式 .1.3.6.1.2.1.1.1.0 获取系统基本信息 SysDesc GET .1.3.6.1.2.1.1.3.0 监控时间 s ...

  2. React的prop-types下载安装教程

    最近刚入门react,所有react的资源都是从本地导入的,这就难免要去网上找要用的包,react包和reactdom,还有babel的包都挺好找的,官网就有现成的可以用,但是prop-types包貌 ...

  3. LaTeX 常用引用标签前缀

    引用对象 标签前缀 Chapter ch Section sec Subsection sec Appendix app Figure fig Table tab List item itm Equa ...

  4. Python新手爬虫四:爬取视频

    老样子,先上最后成功源码(在D盘下创建'好看视频'文件夹,直接运行即可获取视频): import sys import re,os import requests from you_get impor ...

  5. 如何将图片转换为向量?(通过DashScope API调用)

    本文介绍如何通过模型服务灵积DashScope将 图片转换为向量 ,并入库至向量检索服务DashVector中进行向量检索. 模型服务灵积DashScope,通过灵活.易用的模型API服务,让各种模态 ...

  6. 十,Spring Boot 的内容协商的详细剖析(附+Debug调试说明)

    十,Spring Boot 的内容协商的详细剖析(附+Debug调试说明) @ 目录 十,Spring Boot 的内容协商的详细剖析(附+Debug调试说明) 1. 基本介绍 2. 准备工作 3. ...

  7. Element——前端样式美化

    Element 简介    Element 快速入门      https://element.eleme.cn/#/zh-CN/component/button Element 布局      ht ...

  8. 系统编程-进程-vfork使用、浅析

    1. 先贴代码 #include <stdio.h> #include <stdlib.h> #include <unistd.h> int globvar = 6 ...

  9. Android复习(三)清单文件中的元素——>uses-configuration、uses-library、uses-permission、uses-permission-sdk-23

    <uses-configuration> 语法: <uses-configuration android:reqFiveWayNav=["true" | &quo ...

  10. 云原生周刊:一文读懂 Pod 网络 | 2023.4.10

    文章推荐 一文读懂 Pod 网络 这篇文章旨在帮助读者理解 Pod 网络的概念和原理.Pod 网络是 Kubernetes 中的一个重要概念,它描述了如何在一个集群中部署和运行应用程序. Pod 网络 ...