WxPython跨平台开发框架之用户选择和标签组件的设计
在系统的权限管理中,往往都会涉及到用户的选择处理,特别是基于角色的访问控制中,很多情况下需要用到选择用户的处理。本篇随笔,基于WxPython跨平台开发框架,采用原有开发框架成熟的一套权限系统理念,对机构、用户、角色、权限、菜单、日志、字典等内容进行管理的,因此也涉及到了用户选择的处理,在WxPython开发中,为了方便,我们往往会构建一些自定义控件,以便重用处理,本篇设计了标签组件来简化一些处理操作,同时可以在很多地方进行重用。
1、权限内容的相关介绍
RBAC(Role-Based Access Control,基于角色的访问控制)是一种常用的访问控制模型,在此模型中,系统中的每个用户被赋予一个或多个角色,权限则与角色相关联。角色是一组权限的集合,用户通过其所扮演的角色来获得相应的权限,从而限制用户对系统资源的访问。
基本概念
角色(Role): 角色是一个标识符,表示一组权限的集合。每个角色可以有不同的权限,例如 "管理员"、"普通用户"、"审计员" 等。
用户(User): 用户是系统中的实际操作主体。每个用户可以分配一个或多个角色,根据角色来获得相应的权限。
权限(Permission): 权限是指用户可以执行的操作或可以访问的资源。例如,"读取文件"、"修改文件"、"删除文件" 等。
会话(Session): 会话是用户和系统之间交互的时间段,系统通过会话信息管理用户的角色和权限。
RBAC的工作原理
RBAC模型的基本思想是:
- 用户通过其角色获得访问权限。
- 系统将权限与角色进行绑定,角色分配给用户。
- 用户通过角色来执行操作,而不是直接赋予权限。
RBAC的主要组成部分
角色权限映射: 角色和权限之间的映射关系是RBAC的核心。例如,"管理员"角色拥有创建、删除、编辑用户的权限,而"普通用户"角色只拥有读取数据的权限。
用户角色映射: 用户通过角色来获得权限。例如,某个用户被赋予"经理"角色,那么该用户便获得了与"经理"角色相关的所有权限。
访问控制: 系统根据用户当前角色的权限来判断是否允许访问某个资源或执行某个操作。
RBAC的实现
定义角色和权限: 在系统中定义不同的角色,例如管理员、用户、访客等,并为每个角色分配不同的权限。
用户分配角色: 每个用户通过管理员或系统自动分配角色,获得该角色所拥有的权限。
控制访问: 系统根据用户的角色判断是否允许其访问某些资源或执行某些操作。
我曾经在《Winform开发框架之权限管理系统改进的经验总结(2)-用户选择界面的设计》中介绍了选择用户的界面,

可以在组织机构或者用户角色管理中选择相关用户,我们在WxPython跨平台开发框架同样也是采用这种管理概念。界面设计效果如下所示。

弹出的界面中,模仿了之前的用户界面,由于之前在添加每个用户的时候,采用的是添加自定义控件的方式呈现,在wxpython中做一些自定义控件的处理也是非常方便的,我们先来看最终的界面效果,后面在分析如何实现用户标签的添加和移除等操作管理。

2、标签信息的自定义控件的处理
为了实现标签信息的处理,我们先来做一个简单的标签组件的测试,在整合在界面中进行完善的处理。
简单的例子代码如下所示。
import wx
import random class Tag(wx.Panel):
def __init__(self, parent, label, on_close):
"""标签控件初始化
:param parent: 父控件
:param label: 标签文本
:param on_close: 关闭事件回调函数
"""
super().__init__(parent, style=wx.BORDER_SIMPLE) self.on_close = on_close # 回调函数,用于处理关闭事件
self.label = label # 设置背景颜色,方便区分标签
# self.SetBackgroundColour(wx.Colour(240, 240, 240))
r = random.randint(128, 255)
g = random.randint(128, 255)
b = random.randint(128, 255)
self.SetBackgroundColour(wx.Colour(r,g,b)) # 创建控件
self.text = wx.StaticText(self, label=label)
self.close_btn = wx.Button(self, label="x", size=(20, 20)) # 布局
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.text, 0, wx.ALIGN_CENTER | wx.RIGHT, 5)
sizer.AddSpacer(2)
sizer.Add(self.close_btn, 0, wx.ALIGN_CENTER) self.SetSizer(sizer)
sizer.Fit(self) # 绑定事件
self.close_btn.Bind(wx.EVT_BUTTON, self._on_close) def _on_close(self, event):
"""处理关闭事件"""
if self.on_close:
self.on_close(self) class TagPanel(wx.Panel):
def __init__(self, parent):
"""标签面板控件初始化
:param parent: 父控件
"""
super().__init__(parent) self.sizer = wx.WrapSizer(wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS)
self.SetSizer(self.sizer) # 测试用标签
for i in range(5):
self.add_tag(f"张三发 {i+1}") def add_tag(self, label):
"""添加一个新标签"""
tag = Tag(self, label, self.remove_tag)
self.sizer.Add(tag, 0, wx.ALL, 5)
self.Layout() def remove_tag(self, tag):
"""移除一个标签"""
self.sizer.Detach(tag)
tag.Destroy()
self.Layout() class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="标签测试案例", size=(400, 300)) panel = wx.Panel(self)
main_sizer = wx.BoxSizer(wx.VERTICAL) self.tag_panel = TagPanel(panel)
main_sizer.Add(self.tag_panel, 1, wx.EXPAND | wx.ALL, 10) # 添加标签的按钮
add_button = wx.Button(panel, label="添加新标签")
add_button.Bind(wx.EVT_BUTTON, self.on_add_tag)
main_sizer.Add(add_button, 0, wx.ALIGN_CENTER | wx.ALL, 10) panel.SetSizer(main_sizer) def on_add_tag(self, event):
"""添加新标签"""
self.tag_panel.add_tag(f"标签 {len(self.tag_panel.sizer.GetChildren()) + 1}") if __name__ == "__main__":
app = wx.App()
frame = MyFrame()
frame.Show()
app.MainLoop()
实现的效果如下所示。

大概就是这样的效果,我们在添加用户的界面中整合它,并完善一些操作,如可以记录选中的相关信息,清空记录,可以更新选中的记录数等信息。
首先,我们增加一个通用的对象类,来承载显示内容和值信息的(text,value键值对),如下定义所示。
class CListItem(BaseModel):
"""框架用来记录字典键值的类,用于Comobox等控件对象的值传递""" def __init__(self, text: str, value: Any, **data: Any):
super().__init__(**data)
self.text = text
self.value = value text: str = None
value: Any = None def __repr__(self):
return f"CListItem(text={self.text}, value={self.value})"
因此Tag的类初始化,改用该对象来来处理,如下代码所示
class MyTag(wx.Panel):
"""自定义标签, 带有关闭按钮""" item: CListItem = None # 标签数据 def __init__(self, parent, item: CListItem, on_close):
"""初始化 :param parent: 父容器
:param label: 标签文本
:param on_close: 关闭事件回调函数
"""
super().__init__(parent, style=wx.BORDER_SIMPLE) self.on_close = on_close # 回调函数,用于处理关闭事件
self.item = item # 设置背景颜色,方便区分标签
# self.SetBackgroundColour(wx.Colour(240, 240, 240))
r = random.randint(128, 255)
g = random.randint(128, 255)
b = random.randint(128, 255)
self.SetBackgroundColour(wx.Colour(r, g, b)) # 创建控件
self.text = wx.StaticText(self, label=item.text)
self.close_btn = wx.Button(self, label="x", size=(20, 20)) # 布局
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.text, 0, wx.ALIGN_CENTER | wx.RIGHT, 5)
sizer.AddSpacer(2)
sizer.Add(self.close_btn, 0, wx.ALIGN_CENTER) self.SetSizer(sizer)
sizer.Fit(self) # 绑定事件
self.close_btn.Bind(wx.EVT_BUTTON, self._on_close) def _on_close(self, event):
"""处理关闭事件"""
if self.on_close:
self.on_close(self)
标签面板的内容中,我们也改用 wx.WrapSizer 这个可折叠的面板Sizer来做标签控件的展示,这样可以在一个位置上堆叠,并且可以换行处理等,比较常规的Panel会好一些。
同时,标签面板增加一个更新的处理函数给外部定义,并且在添加的时候,对重复添加的进行判断,这样我们在标签的添加、删除等操作都进行外部事件的调用就完美了。完整的代码如下所示。
class MyTagPanel(wx.Panel):
def __init__(
self,
parent,
id=wx.ID_ANY,
pos=wx.DefaultPosition,
size=wx.DefaultSize,
on_tag_updated=None,
*args,
**kwargs,
):
super().__init__(parent, id, pos, size, *args, **kwargs) self.sizer = wx.WrapSizer(wx.HORIZONTAL, wx.WRAPSIZER_DEFAULT_FLAGS)
self.SetSizer(self.sizer) self.list = [] # 标签数据列表
self.on_tag_updated = on_tag_updated # 回调函数,用于处理标签变化事件 def _find_tag(self, item: CListItem):
"""查找标签"""
# for child in self.list:
# if item.value == child.value:
# return item
# return None # next 函数:用于从迭代器中获取第一个匹配的元素。如果找不到匹配项,可以指定默认值(这里为 None)。
return next((child for child in self.list if item.value == child.value), None) def add_tag(self, item: CListItem):
"""添加一个新标签"""
if self._find_tag(item):
return # 标签已存在,不再添加
self.list.append(item) tag = MyTag(self, item, self.remove_tag)
self.sizer.Add(tag, 0, wx.ALL, 5)
self.Layout() self.update_tag() def update_tag(self):
# 触发回调函数
if self.on_tag_updated:
self.on_tag_updated() def remove_tag(self, tag: MyTag):
"""移除一个标签""" self.list.remove(tag.item) # 从列表中移除标签数据 self.sizer.Detach(tag)
tag.Destroy()
self.Layout() self.update_tag() def get_items(self):
"""获取标签列表"""
items = []
for child in self.sizer.GetChildren():
tag = child.GetWindow()
tag: MyTag # 限定类型
items.append(tag.item)
return items def clear_items(self):
"""清空标签列表"""
for child in self.sizer.GetChildren():
tag = child.GetWindow()
tag: MyTag # 限定类型
self.remove_tag(tag) self.update_tag()
3、在用户选择界面中使用标签控件和标签面板
我们来看看在选择用户的界面中添加标签面板和相关处理的按钮,如下界面效果所示。

添加标签面板我们用一个函数来处理,如下所示。
def create_tag_panel_buttons(self, parent, style=wx.BORDER_SIMPLE):
"""创建标签面板和操作按钮"""
dlg_sizer: wx.BoxSizer = self.GetSizer() # 添加选择区标签面板
self.tag_panel = ctrl.MyTagPanel(
parent, size=(-1, 100), style=style, on_tag_updated=self.OnTagsUpdated
)
dlg_sizer.Add(self.tag_panel, 0, wx.EXPAND | wx.ALL, 10) # 添加按钮区
btn_sizer = self.CreateCustomButtons(self)
dlg_sizer.Add(
btn_sizer, 0, wx.EXPAND | wx.BOTTOM | wx.RIGHT, self.GetDialogBorder()
)
我们在标签的变化处理事件OnTagsUpdated实现通知外部标签显示的处理,如下代码所示。
def OnTagsUpdated(self):
"""更新选择的用户信息"""
items = self.tag_panel.get_items()
self.lbl_selected_text.SetLabel(f"已选择【{len(items)}】个项目")
我们在表格的双击事件或者按钮事件中添加对应的用户到面板中,我们获得记录的id和用户姓名添加到标签面板即可。
# 获取该行的主键值和值
entity_id = self.table.GetPrimaryKeyValue(row)
fullname = self.table.GetValueByKey(row, "fullname")
if entity_id and fullname:
self.tag_panel.add_tag(CListItem(fullname, entity_id))
else:
MessageUtil.show_info(self, "该行无主键值 或 真实姓名")
如果我们需要再外部清空,也是调用标签面板的清空函数,如果需要获得选中的记录,也还是调用标签面板的选择记录集合即可。
如确认选择的处理事件中,代码如下所示。
async def OnConfirmSelect(self, event):
"""确认选择"""
items = self.tag_panel.get_items()
if len(items) == 0:
MessageUtil.show_warning(self, "请先选择项目")
return self.selected_items = items # 保存选择的记录
self.CloseModel(wx.ID_OK)
因此简单的封装一下,就可以实现了标签记录的相关处理,非常方便,当前其他日常的功能实现,也是如此简化即可。
当然,对于选择用户的整个界面窗体,我们也是可以把它当做一个公用组件来实现的,因此在机构和角色中都需要对用户进行关联,因此它们公用一个界面处理。
以上就是对于常规界面中常用到的一些功能,进行了相关的封装,然后在系统中整合使用的整个过程。
WxPython跨平台开发框架之用户选择和标签组件的设计的更多相关文章
- Winform开发框架之权限管理系统改进的经验总结(2)-用户选择界面的设计
在上篇总结随笔<Winform开发框架之权限管理系统改进的经验总结(1)-TreeListLookupEdit控件的使用>介绍了权限管理模块的用户管理部分,其中主要介绍了其中的用户所属公司 ...
- [ionic开源项目教程] - 手把手教你使用移动跨平台开发框架Ionic开发一个新闻阅读APP
前言 这是一个系列文章,从环境搭建开始讲解,包括网络数据请求,将持续更新到项目完结.实战开发中遇到的各种问题的解决方案,也都将毫无保留的分享给大家. 关注订阅号:TongeBlog ,查看移动端跨平台 ...
- 移动跨平台开发框架Ionic开发一个新闻阅读APP
移动跨平台开发框架Ionic开发一个新闻阅读APP 前言 这是一个系列文章,从环境搭建开始讲解,包括网络数据请求,将持续更新到项目完结.实战开发中遇到的各种问题的解决方案,也都将毫无保留的分享给大家. ...
- 使用Ajax获取多选框用户选择的值问题
目录 说明 正文 说明 在web开发过程中,将多选框的值提交到django后台,有两种提交方式: form表单提交 ajax异步提交 我需要使用ajax将复选框的值提交到后台,记录一下当时碰到的问题 ...
- 自定义Vue&Element组件,实现用户选择和显示
在我们很多前端业务开发中,往往为了方便,都需要自定义一些用户组件,一个是减少单一页面的代码,提高维护效率:二个也是方便重用.本篇随笔介绍在任务管理操作中,使用自定义Vue&Element组件, ...
- WPF整理-使用用户选择主题的颜色和字体
“Sometimes it's useful to use one of the selected colors or fonts the user has chosen in theWindows ...
- 从a站点跳转到b站点,通过url的参数判断是否让该用户选择身份
一.问题的由来 问题是这样子给出来,今天产品那边跟我说,在a网站跳转到b网站时,让用户有一个选择身份的弹窗.因为公司有两个不同站点,你无论在a或者b网站注册后,都可以随便登录这两个站点,进入之后都会有 ...
- asp.net在后台弹出confirm确认对话框并获取用户选择的值做出相应的操作
在asp项目中,这种情况是经常出现的,前段时间通过查找资料以及自己尝试,找到一种解决方案,但是不知是否有更好的方案,以后发现再进行记录. 一.思路 在本次项目中,在一个函数中需要让用户判断,并根据用户 ...
- 【百度地图API】让用户选择起点和终点的驾车导航
原文:[百度地图API]让用户选择起点和终点的驾车导航 摘要: 如果用户搜索“从机场到火车站”,使用驾车导航DrivingRoute会默认显示一条结果.但同一个城市可能有多个机场和火车站,那么,如何用 ...
- windows API实现用户选择文件路径的对话框
在编写应用程序时,有时需要用户选择某个文件,以供应用程序使用,比如在某些管理程序中需要打开某一个进程,这个时候需要弹出一个对话框来将文件路径以树形图的形式表示出来,以图形化的方式供用户选择文件路径,而 ...
随机推荐
- 手搓大模型Task03:手搓一个最小的 Agent 系统
前言 训练一个大模型是一件高投入低回报的事情,况且训练的事情是由大的巨头公司来做的事情:通常我们是在已有的大模型基础之上做微调或Agent等:大模型的能力是毋庸置疑的,但大模型在一些实时的问题上, ...
- 智慧高校IT智能运维方案
当前高校网络已成为每个学校必备的信息基础设施,也成了学校提高教学.科研及管理水平的重要途径和手段.随着信息化发展,高校网络建设逐步走向数字化.智慧化,传统的人力巡检.运维逐渐难以支撑高校校园稳定运行. ...
- PasteForm最佳CRUD实践,实际案例PasteTemplate详解之3000问(二)
作为"贴代码"力推的一个CRUD实践项目PasteTemplate,在对现有的3个项目进行实战后效果非常舒服!下面就针对PasteForm为啥我愿称为最佳CRUD做一些回答: 哪里 ...
- std::vector 和 std::map 都支持以下比较运算符
在 C++ 标准库中,std::vector 和 std::map 都支持以下比较运算符: ==(相等运算符) !=(不等运算符) <(小于运算符) <=(小于等于运算符) >(大于 ...
- Pytorch 基于加权平滑过渡的无缝拼接
基于加权平滑过渡的无缝拼接 背景 在做照片数字人视频生成的时候,为了达到快速响应实时播放的需求,即视频的生成速度 必须小于 音频的播放速度. 因此,我们截取了一部分较小的可动区域进行推理生成,然后把生 ...
- 泛型dotnet
// 什么是泛型List<T> T:表示类型参数,指代任意类型 T可以是任意标识 // 编写代码时使用特殊符号替代位置类型,在实例化或使用/调用时才会进行具体类型的定义 // 特点:重用代 ...
- 我们如何在 vue 应用我们的权限
权限可以分为用户权限和按钮权限: 用户权限,让不同的用户拥有不同的路由映射 ,具体实现方法: 1. 初始化路由实例的时候,只把静态路由规则注入 ,不要注入动态路由规则 : 2. 用户登录的时候,根据返 ...
- 自定义log4j2的PatternLayout参数
1.添加类 package com.yuanian.micro.config; import org.apache.logging.log4j.core.LogEvent; import org.ap ...
- 容器部署DNS你会吗?
docker快速部署DNS,实现快速上线 概念 环境介绍 部署DNS 下载相关镜像 创建并启动DNS容器 简单介绍三种创建方式 容器启动停止 创建dns交互式容器 配置DNS容器相关配置 测试 修改客 ...
- C++之OpenCV入门到提高002:加载、修改、保存图像
一.介绍 今天是这个系列<C++之 Opencv 入门到提高>得第二篇文章.今天这个篇文章很简单,只是简单介绍如何使用 Opencv 加载图像.显示图像.修改图像和保存图像,先给大家一个最 ...