利用wxpython开发跨平台桌面应用,基类列表窗体的抽象封装处置惩罚 ...

宁睿  金牌会员 | 2024-11-11 16:38:05 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 944|帖子 944|积分 2842

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

例如对于体系的窗体来说,一般我们可以按主要的功能视图来区分,一个是列表展示界面,一个是编辑/查看具体内容界面,前面有一篇文章《利用wxpython开发跨平台桌面应用,基类对话框窗体的封装处置惩罚》我专门先容了编辑对话框的抽象设计,所以本篇随笔主要针对列表界面进行先容。
如我们大致需要一个展示列表的界面,列表界面一般分为查询区、列表界面展示区和分页信息区,我们把它分为两个主要的部门,如下界面所示。

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

不同的业务模块,查询的条件肯定是不同的,这部门为内容变化区。
而对于常见的功能按钮,基本上是固定的,我们后期可以根据一些条件进办法态的按钮显示/潜伏,但是这里业务按钮就这些,虽然触发的界面肯定有所不同,但是这些按钮的处置惩罚逻辑是不会变化的,所以称为固定逻辑区。
对于内容变化的,我们可以把它们下发到子类内里,每一个业务模块的列表界面为一个子类,继承基类即可。

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

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

也就是说,这些控件的相干排版信息,我们可以抽象到父类中进行创建,而数据则由子类进行更新变化即可。
此中包罗表格的列名、中文名称对应、列的宽度,数据聚集等信息,而分页栏这是根据每页的大小、当前页码、总数等信息进行按钮的状态控制即可。
 
2、引入泛型定义,实现更丰富的界面控制

我在文章《基于SqlAlchemy+Pydantic+FastApi的Python开发框架》 中先容过利用泛型来构建更加弹性化的基类处置惩罚。
如对于路由器,我们通过泛型参数的处置惩罚,让基类的接口更加个性化一些,如下代码所示。

在 Python 中,泛型(Generic) 是一种答应类型参数化的特性,通常用于类型注解和类型检查。Python 的泛型支持主要通过 typing 模块中的类型提示来实现,例如 Generic、TypeVar、List[T] 等。利用泛型定义基类有以下几个利益:
1. 提高类型安全性

泛型答应在编写类和函数时,明白指定类型参数,如许在利用时可以进行更严酷的类型检查,减少类型错误。
如果基类利用了泛型 T,如许在子类或实例化时可以指定具体的类型(如 User),从而在编译期(利用 IDE 或类型检查工具时)得到类型安全性,克制类型错误。
2. 加强代码重用性

泛型答应编写更具通用性的基类,不必针对每种数据类型重复实现雷同功能,增长了代码的重用性。
3. 提升代码的可读性和维护性

泛型使得类型参数在类定义中显式化,如许可以资助开发者更清晰地了解类或函数的预期类型,减少类型推断的复杂度,提升可读性。
4. 与类型提示和类型检查工具集成

现代 Python 开发中,利用类型提示(type hinting)已经成为一种最佳实践。泛型定义能够更好地与类型检查工具(如 mypy、pyright 等)集成,资助在编写代码时发现潜在的类型错误。
5. 加强代码的灵活性

利用泛型定义基类可以让类的功能更通用,从而在不修改类代码的情况下,通过类型参数化来实现不同的数据处置惩罚逻辑。
 
以上这些优势使得在大型项目或库开发中利用泛型变得尤为重要,尤其是在设计通用数据布局、工具类或框架时。
泛型定义,我们声明一个类型,如下所示。
  1. ModelType = TypeVar("ModelType")  # 定义泛型基类
复制代码
然后就可以根据需要采取泛型类型了,如下是窗体基类的定义,采取一个泛型的类型来定义业务模块的子类DTO对象,从而使得该父类很多接口都具有很好的类型化定义。
  1. # 创建泛型基类 BaseFrameList,并继承 wx.Panel
  2. class BaseFrameList(wx.Panel, Generic[ModelType]):
复制代码
而对于业务模块的子类,如此中业务列表界面的子类定义如下所示。
  1. # 继承BaseFrameList类,并传入实体类SystemTypeInfo,作为泛型类型
  2. class FrmSystemType(BaseFrameList[SystemTypeDto]):
复制代码
如许我们构建的列表界面父子类的关系如下 所示,此中包罗两个业务模块的列表界面:体系类型定义,客户信息。

例如我们以客户列表界面的子类代码进行分析,如下所示。我们只需要传入所需的一些字段显示及中文解析,并传入相干的DTO对象,如下代码所示。
  1. # 继承BaseFrameList类,并传入实体类CustomerInfo,作为泛型类型
  2. class FrmCustomer(BaseFrameList[CustomerDto]):
  3.     """客户信息"""
  4.     # 显示的字段名称,逗号分隔
  5.     display_columns = "id,name,age,creator,createtime"
  6.     # 列名映射(字段名到显示名的映射)
  7.     column_mapping = {
  8.         "id": "编号", "name": "姓名", "age": "年龄", "creator": "创建人", "createtime": "创建时间",
  9.     }
  10.     def __init__(self, parent):
  11.         # 初始化基类信息
  12.         super().__init__(
  13.             parent,
  14.             model=CustomerDto,
  15.             display_columns=self.display_columns,
  16.             column_mapping=self.column_mapping
  17.         )
复制代码
这些内容肯定是必须的,另外,如果我们需要自定义列表界面中单元格列的宽度,也可以指定宽度的字典参照,不指定则利用默认宽度即可(在基类定义默认宽度,如为150像素)。
例如,我在体系类型定义中就包罗了列表宽度的字典参考,代码如下所示。
  1. # 继承BaseFrameList类,并传入实体类SystemTypeInfo,作为泛型类型
  2. 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。

下面是客户信息的列表界面,重写父类函数的实现代码 
  1.     def CreateConditions(self, pane: wx.Window) -> List[wx.Window]:
  2.         """创建折叠面板中的查询条件输入框控件"""
  3.         # 创建控件,不用管布局,交给CreateConditionsWithSizer控制逻辑
  4.         # 默认的FlexGridSizer为4*2=8列,每列间隔5px
  5.         lblName = wx.StaticText(pane, -1, "姓名:")
  6.         self.txtName = wx.TextCtrl(pane, -1, size=(150, -1))
  7.         lblAge = wx.StaticText(pane, -1, "年龄:")
  8.         self.txtAge = ctrl.MyNumericRange(pane, -1, size=(150, -1))
  9.         return [lblName, self.txtName, lblAge, self.txtAge]
复制代码
如果偶然候需要重新定义布局,那么可以重写它上一级包罗布局的函数即可实现更高维度的定制处置惩罚。
如对于体系类型的列表界面,如下所示

 为了更好先容对于面板布局的控制,我重写其上一级的函数,包罗FlexGridSizer的定义信息,这里我们的代码如下所示。
  1.     def CreateConditionsWithSizer(self, pane: wx.Window):
  2.         """子类可重写该方法,创建折叠面板中的查询条件,包括布局FlexGridSizer"""
  3.         # 先创建控件
  4.         lblSystemType = wx.StaticText(pane, -1, "系统标识:")
  5.         self.txtSystemType = wx.TextCtrl(pane, -1, "", size=(150, -1))
  6.         lblName = wx.StaticText(pane, -1, "系统名称:")
  7.         self.txtName = wx.TextCtrl(pane, -1, "", size=(150, -1))
  8.         lblCustomCode = wx.StaticText(pane, -1, "客户编码:")
  9.         self.txtCustomCode = wx.TextCtrl(pane, -1, "", size=(150, -1))
  10.         lblAuthCode = wx.StaticText(pane, -1, "授权编码:")
  11.         self.txtAuthCode = wx.TextCtrl(pane, -1, "", size=(150, -1))
  12.         # 增加条件面板
  13.         input_sizer = wx.FlexGridSizer(cols=4 * 2, hgap=5, vgap=5)
  14.         # 统一处理查询条件控件的添加,使用默认的布局方式
  15.         list = [lblSystemType, self.txtSystemType, lblName, self.txtName, lblCustomCode, self.txtCustomCode, lblAuthCode, self.txtAuthCode]
  16.         for i in range(len(list)):
  17.             input_sizer.Add(list[i], 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
  18.         # input_sizer.AddGrowableCol(1)
  19.         # input_sizer.Add((5, 5))
  20.         return input_sizer
复制代码
 
而对于子类的编辑查看对话框,我们通过触发按钮进行弹出,或者右键的菜单中弹出,都是同样的逻辑,不外就是界面内容不同,我们可以让子类进行实现即可。

子类对于新增编辑的界面实现代码如下所示,此中Add和OnEditById都是父类的空函数,由子类来具体实现即可(覆盖重写)。
  1.     def OnAdd(self, event: wx.Event) -> None:
  2.         """子类重写-打开新增对话框"""
  3.         dlg = FrmSystemTypeEdit(self)
  4.         if dlg.ShowModal() == wx.ID_OK:
  5.             # 新增成功,刷新表格
  6.             self.update_grid()
  7.         dlg.Destroy()
  8.     def OnEditById(self, entity_id: Any | str):
  9.         """子类重写-根据主键值打开编辑对话框"""
  10.         dlg = FrmSystemTypeEdit(self, entity_id)
  11.         if dlg.ShowModal() == wx.ID_OK:
  12.             # 更新grid列表数据
  13.             self.update_grid()
  14.         dlg.Destroy()
复制代码
其他处置惩罚,如删除记录、导入、导出的处置惩罚,大同小异,从变化中找到不变的逻辑交给父类处置惩罚,子类负责最原始变化的内容即可。
当然对于一些复杂的列表界面,可能还需要考虑左侧放置一些树列表以便快速的选择不同分类的数据,如下Winform上的界面。

这个界面的内容,左侧就是折叠的两个树列表:机构列表、角色列表,以便方便选择用户信息。
那么对于如许的效果,我们在基类窗体中是否可以抽象出来,答案当然是可以的,还记得我们前面《利用wxpython开发跨平台桌面应用,动态工具的创建处置惩罚 》先容过的工具栏的时候,利用了Manager类的实现效果。

不外这个是主窗体级别的,我们需要为具体的业务列表界面定义一个雷同的效果。
我们在父类窗体中定义一个开关变量,用来开启或者关闭左侧树列表面板的,如下代码所示。

如许构建立列表就交给函数 create_tree_panels 实现即可,它会构建一到多个的树列表,父界面窗体负责整合它们显示即可。
如子类定义重写创建立列表的函数,如下代码所示。
  1.     def create_tree_panels(self) -> dict[str, wx.Panel]:
  2.         """子类重写该方法,创建左侧树列表面板-可以多个树列表"""
  3.         dict = {}
  4.         # 示例代码
  5.         for key in ["机构列表", "角色列表"]:
  6.             tree_panel = wx.Panel(self)
  7.             tree_sizer = wx.BoxSizer(wx.VERTICAL)
  8.             tree = CT.CustomTreeCtrl(tree_panel, style=wx.TR_DEFAULT_STYLE)
  9.             self._populate_tree(tree)
  10.             tree_sizer.Add(tree, 1, wx.EXPAND)
  11.             tree_panel.SetSizer(tree_sizer)
  12.             dict[key] = tree_panel
  13.         return dict
复制代码
那么界面效果会得到如下所示。

我们根据需要实现具体的树数据显示即可。 
 以上就是我对于界面的分析和逐步的抽象处置惩罚,把主要的逻辑提取到父类中去,变化的小部门内容,交给子类差别实现即可,减少代码,提高服从。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表