我在随笔《使用PySide6/PyQt6实现Python跨平台GUI框架的开发》中介绍过PySide6/PyQt6 框架架构的整体设计,本篇随笔继续深入探讨框架的设计开发工作,主要针对通用列表页面的基类设计进行介绍,分析基类的各个模块的功能,以及介绍如何抽象一些公用的逻辑,实现对子类页面的简化处理。

1、通用列表界面的设计

大多数情况下,界面的表现逻辑可以使用不同的规则进行抽象,如自定义控件、列表界面、弹出对话框界面等,我们把它抽象出来进行不同的处理。子类界面进行一定程度的扩张即可获得更好的使用、更简化的代码。

对于列表和对话框界面的封装,能够简化对泛型模型数据的统一处理,因此可以简化继承子类的代码,提供代码维护开发和维护的效率。

其中用户管理界面的列表界面如下所示。

树列表或者表格控件,右键可以弹出相关的右键菜单

列表包含有有树形列表、条件查询框、通用条件(查询、新增、编辑、删除、导出)等、列表展示、分页导航、右键菜单等内容。这些都是在基类中进行了统一的抽象处理,子类根据需要调整属性或重写相关函数即可实现个性化的界面定义。

2、通用列表界面的分析处理

如果我们需要设计通用列表界面窗体的基类,那么我们需要尽可能的减少子类的代码,把常用的功能封装在基类里面,以及特殊的内容,可以通过封装逻辑,下发具体实现给子类进行重写实现即可。

前面我们介绍过,常用列表包含有有树形列表、条件查询框、通用条件(查询、新增、编辑、删除、导出)等、列表展示、分页导航、右键菜单等内容,另外还有详细的需要接受一些子类的列表字段显示和中文参考,以及表格处理的功能按钮的权限控制等方面。

由于我们需要子类传入的相关DTO类型,因此我们定义泛型类型来传入处理。

基类定义如下所示。

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

# 创建泛型基类 BaseListFrame ,并继承 QMainWindow
class BaseListFrame(QMainWindow, Generic[ModelType]):

另外我们初始化函数,需要接受子类的一些信息,用于对显示内容进行精准的控制处理,因此构造函数__init__里面定义好相关的参数,如下所示。

# 创建泛型基类 BaseListFrame ,并继承 QMainWindow
class BaseListFrame(QMainWindow, Generic[ModelType]):
def __init__(
self,
parent,
model: Optional[ModelType] = None,
display_columns: str = display_columns,
column_mapping: dict = column_mapping,
items_per_page: int = items_per_page,
EVT_FLAGS: EventFlags = EVT_FLAGS,
show_menu_tips: bool = show_menu_tips,
menu_tips: str = DEFAULT_MENU_TIPS,
use_left_panel: bool = False,
column_widths={"id": 50},
plugins=None,
):
"""初始化窗体 :param parent: 父窗口
:param model: 实体类
:param display_columns: 显示的字段名称,逗号分隔,如:id,name,customid,authorize,note
:param column_mapping: 列名映射(字段名到显示名的映射)dict格式:{"name": "显示名称"}
:param items_per_page: 每页显示的行数
:param EVT_FLAGS: 设置可以显示的操作按钮
:param show_menu_tips: 是否显示提示信息
:param menu_tips: 设置菜单提示信息
:param use_left_panel: 是否使用树控件
:param column_widths: Grid列的宽度设置
"""

1)树列表的控制和实现

我们在init函数里面,主要通过_create_content()函数进行创建界面元素。

    def _create_content(self):
"""创建主要内容面板""" # 创建左侧树控件
if self.use_left_panel:
self._merge_tree_panel() # "创建右侧主要内容面板
content_panel = self._create_content_panel()
self.setCentralWidget(content_panel)

它负责判断是否需要展示树列表,如果打开显示树的开关,就根据树形列表的集合进行构建左侧的树列表显示。

    def _merge_tree_panel(self):
"""合并左侧树控件"""
tree_panels = self.create_tree_panels()
if tree_panels is None or len(tree_panels.keys()) == 0:
return self.dock_widget = dock_widget = QDockWidget(self)
dock_widget.setWindowTitle("") # 左侧树控件 # 创建 QTabWidget,并存储self.tree_tab_widget
self.tree_tab_widget = tree_tab_widget = QTabWidget()
tree_tab_widget.setTabPosition(QTabWidget.TabPosition.South) # 添加树控件到 QTabWidget
for name, panel in tree_panels.items():
tree_tab_widget.addTab(panel, name) dock_widget.setWidget(tree_tab_widget) # 防止面板浮动
dock_widget.setFloating(False)
# 禁止关闭按钮
dock_widget.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures) # 将 QDockWidget 添加到主窗口的左侧
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock_widget)

上面代码就是在左侧构建一个 QDockWidget 的停靠区域,我们把所有树列表的集合放到其中容器的 QTabWidget 里面即可。

在抽象的父类里面,我们只需要给出一个默认的 create_tree_panels 实现函数即可,如下所示。

    def create_tree_panels(self) -> dict[str, QWidget]:
"""子类重写该方法,创建左侧树列表面板-可以多个树列表""" tree_panels: dict[str, QWidget] = {}
# 创建树控件
# tree_panels["Tab 1"] = QLabel(self)
# tree_panels["Tab 2"] = QLabel(self)
return tree_panels

而 create_tree_panels 具体的实现 我们是留给子类进行重写的,因为我们不清楚具体的显示,但是我们可以把它们逻辑上组合起来即可。

如对于上面展示的用户列表界面,这部分create_tree_panels  的代码实现如下所示。

    def create_tree_panels(self) -> dict[str, QWidget]:
"""子类重写该方法,创建左侧树列表面板-可以多个树列表"""
dict = {} self.tree_dept = ctrl.MyTreePanel(
self,
on_tree_selected_handler=self.OnDeptTreeSelected,
expand_all=True,
on_menu_handler=self.OnDeptTreeMenu,
)
self.tree_role = ctrl.MyTreePanel(
self,
on_tree_selected_handler=self.OnRoleTreeSelected,
expand_all=True,
on_menu_handler=self.OnRoleTreeMenu,
)
dict["按组织机构查看"] = self.tree_dept
dict["按角色查看"] = self.tree_role return dict

其中ctrl.MyTreePanel的控件是我们自定义的一个树列表控件,用于减少重复性的代码,抽象一个树列表的展示,有利于我们保持更好的控制,统一界面效果的处理。

在子类的构造函数处理上,我们只需要设置参数 use_left_panel = True,并且实现 create_tree_panels 函数即可。

2)查询条件控件内容

介绍完毕树列表的处理,我们再次来到基类的界面构建处理函数上。

    def _create_content(self):
"""创建主要内容面板""" # 创建左侧树控件
if self.use_left_panel:
self._merge_tree_panel() # "创建右侧主要内容面板
content_panel = self._create_content_panel()
self.setCentralWidget(content_panel)

其中的_create_content_panel 是我们构建主查询面板内容的,其中包括输入条件展示、常见按钮显示、以及列表、分页栏目等。

    def _create_content_panel(self) -> QWidget:
"""创建右侧主要内容面板"""
panel = QWidget(self)
# 创建一个垂直布局
main_layout = QVBoxLayout() # 创建一个折叠的查询条件框
search_bar = self._create_search_bar(panel)
main_layout.addWidget(search_bar) # 创建显示数据的表格
table_widget = self._create_grid(panel)
main_layout.addWidget(table_widget, 1) # 拉伸占用全部高度 # 创建一个分页控件
self.pager_bar = ctrl.MyPager(panel, self.items_per_page, self.update_grid)
main_layout.addWidget(self.pager_bar) # 设置布局
panel.setLayout(main_layout)
return panel

上面标注特殊的代码,就是对不同模块的逻辑进行分离实现,从而让我们关注点集中一些。其中的create_search_bar里面,主要封装了查询条件框、常规按钮、自定义按钮等内容。

    def _create_search_bar(self, parent: QWidget = None) -> QWidget:
"""创建折叠的查询条件框,包含查询条件输入框和常规按钮"""
panel = QWidget(parent)
# 创建一个垂直布局
layout = QVBoxLayout()
panel.setLayout(layout) # 添加查询条件控件
input_sizer = self.CreateConditionsWithSizer(panel)
layout.addLayout(input_sizer, 0)
layout.addSpacing(5) # 增加间距 # 添加常规按钮
btns_sizer = self._CreateCommonButtons(panel)
# 自定义按钮
self.CreateCustomButtons(panel, btns_sizer)
layout.addLayout(btns_sizer, 0) return panel

我在基类窗体的抽象类里面,定义了默认的布局规则,如下代码所示。

    def CreateConditionsWithSizer(self, parent: QWidget = None) -> QGridLayout:
"""子类可重写该方法,创建折叠面板中的查询条件,包括布局 QGridLayout"""
layout = QGridLayout()
layout.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
layout.setSpacing(5) # 增加间距 # 统一处理查询条件控件的添加,使用默认的布局方式
cols = 4 * 2
list = self.CreateConditions(parent) for i in range(len(list)):
control: QWidget = list[i]
control.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
layout.addWidget(control, i // cols, i % cols) return layout def CreateConditions(self, parent: QWidget = None) -> list[QWidget]:
"""子类可重写该方法,创建折叠面板中的查询条件输入框控件,不包括布局,使用默认的布局方式 QGridLayout"""
list = [QWidget]
# 示例代码:
lblName = QLabel("名称:")
self.txtName = ctrl.MyTextCtrl(parent, "请输入名称") list.append(lblName)
list.append(self.txtName) return list

如果我们不改变布局,那么我们主要实现 CreateConditions 函数即可。这个函数也是比较简单的,构建所需的输入几个条件即可。

如对于简单的客户信息界面,它的条件输入框里面就几个条件。

我们根据上面的界面效果,可以看到客户窗体子类实现 CreateConditions 函数的代码如下所示。

    def CreateConditions(self, parent: QWidget = None) -> list[QWidget]:
"""创建折叠面板中的查询条件输入框控件"""
# 创建控件,不用管布局,交给CreateConditionsWithSizer控制逻辑
# 默认的QGridLayout 为4*2=8列,每列间隔5px
self.txtName = ctrl.MyTextCtrl(parent)
self.txtAge = ctrl.MyNumericRange(parent)
self.txtCustomerType = ctrl.MyComboBox(parent) # ControlUtil 可以方便的创建文本标签和控件的组合,并返回所有的控件列表
util = ControlUtil(parent)
util.add_control("姓名:", self.txtName)
util.add_control("年龄:", self.txtAge)
util.add_control("客户类型:", self.txtCustomerType) return util.get_controls()

这样,具体实现部分,对于WxPython和PySide6/PyQt6来说,代码都是差不多的,因为我们用了自定义用户控件类,并使用辅助函数,让它们和标签更好的粘合起来。

对于自定义控件,我们对其封装,使之能够在开发使用习惯上更一致,下面是我们根据需要对常见的原生控件进行一些自定义控件的封装列表。

对于常规的按钮,我们根据权限集合进行判断是否显示即可,自定义按钮则留给子类进一步实现。

        # 添加常规按钮
btns_sizer = self._CreateCommonButtons(panel)
# 自定义按钮
self.CreateCustomButtons(panel, btns_sizer)

对于常规的按钮,代码如下所示。

而自定义按钮的处理,我们留给子类实现,父类给出一个默认的函数即可。

    def CreateCustomButtons(self, parent: QWidget, btns_sizer: QHBoxLayout) -> None:
"""子类可重写该方法,创建折叠面板中的自定义按钮"""
# 增加按钮
pass

3)表格数据显示

我们回到前面介绍的代码。

    def _create_content_panel(self) -> QWidget:
"""创建右侧主要内容面板"""
panel = QWidget(self)
# 创建一个垂直布局
main_layout = QVBoxLayout() # 创建一个折叠的查询条件框
search_bar = self._create_search_bar(panel)
main_layout.addWidget(search_bar) # 创建显示数据的表格
table_widget = self._create_grid(panel)
main_layout.addWidget(table_widget, 1) # 拉伸占用全部高度 # 创建一个分页控件
self.pager_bar = ctrl.MyPager(panel, self.items_per_page, self.update_grid)
main_layout.addWidget(self.pager_bar) # 设置布局
panel.setLayout(main_layout)
return panel

其中 _create_grid 就是我们创建表格内容的逻辑函数了,它负责创建一个QTableView 元素进行展示,表格数据的绑定,通过只定义模型MyTableModel 来绑定界面显示的。

    def _create_grid(self, parent: QWidget) -> QTableView:
"""创建显示数据的表格""" self.total_count: int = 0 self.table_model = ctrl.MyTableModel(
self.data,
self.display_columns,
self.column_mapping,
primary_key="id",
column_widths=self.column_widths,
replace_values_handler=self.replace_values, # 替换内容函数
forground_color_handler=self.paint_foreground, # 前景色渲染函数
)
self.table_view = QTableView(parent)
self.table_view.setModel(self.table_model) self._set_grid_options() # 绑定行选中事件
self.table_view.selectionModel().selectionChanged.connect(self.on_row_selected) # 异步绑定双击行事件
if self.has_edit or self.has_view:
self.table_view.doubleClicked.connect(self.on_row_double_clicked)

表格头部排序、右键菜单、表格特殊的选中和内容转义、背景色处理、导出Excel、导出PDF、打印预览等,我能都可以通过对表格的一些属性或者方法进行跟踪处理即可实现。这里由于篇幅原因,不在深入探讨。

4)分页信息展示

对于分页内容,表格显示是不负责的,因此我们需要根据模型对象,构建一个分页控件来显示,把它剥离基类列表的主界面,有利于减少我们的关注点分散,也有利于重用控件。

前面的逻辑代码中。

    def _create_content_panel(self) -> QWidget:
"""创建右侧主要内容面板"""
panel = QWidget(self)
# 创建一个垂直布局
main_layout = QVBoxLayout() # 创建一个折叠的查询条件框
search_bar = self._create_search_bar(panel)
main_layout.addWidget(search_bar) # 创建显示数据的表格
table_widget = self._create_grid(panel)
main_layout.addWidget(table_widget, 1) # 拉伸占用全部高度 # 创建一个分页控件
self.pager_bar = ctrl.MyPager(panel, self.items_per_page, self.update_grid)
main_layout.addWidget(self.pager_bar) # 设置布局
panel.setLayout(main_layout)
return panel

分页控件是独立的一个用户控件。

class MyPager(QWidget):
"""列表的分页控件""" def __init__(self, parent=None, items_per_page=10, on_update=None, total_count=0):
"""初始化 :param parent: 父控件
:param items_per_page: 每页的行数
:param on_update: 查询数据的回调函数,为异步函数
:param total_count: 总记录数
""" self.items_per_page = items_per_page
self.total_count = total_count
self.total_pages = (total_count + items_per_page - 1) // items_per_page
self.current_page = 0
self.on_update = on_update super().__init__(parent)

通过有效的隔离,使得我们每次只需要关注特定部分的处理,而具体的逻辑由基类统一控制,特殊的具体实现交给子类重写基类函数即可。

完成了上面的处理后,我们发现业务模块的子类需要实现的内容比较少了,大多数交给抽象父类实现了。

5)数据的初始化处理

完成了界面元素的创建后,我们还需要再基类中统一一些数据初始化的函数,如我们在构造函数里面创建好内容后,调用了init_ui的函数初始化界面元素。

# 创建泛型基类 BaseListFrame ,并继承 QMainWindow
class BaseListFrame(QMainWindow, Generic[ModelType]):
"""列表窗口的基类定义"""def __init__(
self,
parent,
model: Optional[ModelType] = None,
display_columns: str = display_columns,
column_mapping: dict = column_mapping,
items_per_page: int = items_per_page,
EVT_FLAGS: EventFlags = EVT_FLAGS,
show_menu_tips: bool = show_menu_tips,
menu_tips: str = DEFAULT_MENU_TIPS,
use_left_panel: bool = False,
column_widths={"id": 50},
plugins=None,
):
"""初始化窗体 :param parent: 父窗口
:param model: 实体类
:param display_columns: 显示的字段名称,逗号分隔,如:id,name,customid,authorize,note
:param column_mapping: 列名映射(字段名到显示名的映射)dict格式:{"name": "显示名称"}
:param items_per_page: 每页显示的行数
:param EVT_FLAGS: 设置可以显示的操作按钮
:param show_menu_tips: 是否显示提示信息
:param menu_tips: 设置菜单提示信息
:param use_left_panel: 是否使用树控件
:param column_widths: Grid列的宽度设置
"""
super().__init__(parent)
# 日志对象
self.log = settings.log.get_logger() # 初始化属性
self.model = model
self.display_columns = display_columns # 显示的字段名称,逗号分隔,如:id,name
self.column_mapping = column_mapping # 列名映射
self.items_per_page = items_per_page # 每页显示的行数
self.EVT_FLAGS = EVT_FLAGS # 设置可以显示的操作按钮
self.show_menu_tips = show_menu_tips # 是否显示提示信息
self.menu_tips = menu_tips # 设置菜单提示信息
self.use_left_panel = use_left_panel # 是否使用树控件
self.column_widths = column_widths # Grid列的宽度设置
self.plugins = plugins or {} # 单元格的渲染列表,格式:{"列名称": 插件实例}
self.columns_permit = {} # 字段权限
self.total_count = 0 # 记录总数
# 创建主要内容面板
self._create_content()# 调度异步任务, 使用@asyncSlot()装饰器后,你可以像同步函数一样调用异步方法
self.init_ui() @asyncSlot()
async def init_ui(self):
"""初始化界面"""
# 使用 @asyncSlot 装饰器后,你可以像同步函数一样调用异步方法,Qt 会自动管理异步任务的调度和执行,
# 不需要显式使用 await 或者 asyncio.create_task 来启动异步任务。
# 如果你在子类中重写了 init_ui,你仍然需要在子类中显式地添加 @asyncSlot() 装饰器。
# 在子类中,Python 会将其视为新的方法定义,因此你必须在子类中的方法上再次应用 @asyncSlot() 装饰器来确保它仍然被处理为异步槽。 await self.init_dict_items()
await self.init_treedata()
await self.update_grid() async def init_dict_items(self):
"""初始化字典数据-子类可重写"""
# await self.txtCustomerType.bind_dictType("客户类型")
pass async def init_treedata(self):
"""初始化树控件数据-子类可重写"""
pass async def update_grid(self) -> None:
"""更新表格的内容""" # 查询数据
await self.OnQuery() # 获取当前用户有权限查看的列
self.columns_permit = await self.get_columns_permit()
# 更新表格数据
self.table_model.UpdateData(self.data, self.columns_permit)
# 更新页码信息
self._update_pager()

而各个子类负责各自模块内容的初始化即可。

3、子类列表界面代码分析

由于父类已经抽象了很多相关的元素创建、数据初始化的逻辑函数,因此子类根据需要重写函数实现即可。

如对于简单的业务表,客户信息表,它的子类只需要实现下面几个函数即可。

CreateConditions函数负责查询条件的构建,前面介绍过。
    def CreateConditions(self, parent: QWidget = None) -> list[QWidget]:
"""创建折叠面板中的查询条件输入框控件"""
# 创建控件,不用管布局,交给CreateConditionsWithSizer控制逻辑
# 默认的QGridLayout 为4*2=8列,每列间隔5px self.txtName = ctrl.MyTextCtrl(parent)
self.txtAge = ctrl.MyNumericRange(parent)
self.txtCustomerType = ctrl.MyComboBox(parent) # ControlUtil 可以方便的创建文本标签和控件的组合,并返回所有的控件列表
util = ControlUtil(parent)
util.add_control("姓名:", self.txtName)
util.add_control("年龄:", self.txtAge)
util.add_control("客户类型:", self.txtCustomerType) return util.get_controls()

OnQuery函数负责提取输入条件,并提交服务端获取数据返回。

    async def OnQuery(self):
"""子类实现-发送查询请求, 需设置self.data,self.total_count""" # 获取默认查询参数,包括skipCount,maxResultCount,sorting
params = self.GetDefaultParams()
# 新的数据,可以从控件获取,也可以是动态生成的
search_params = { "name": self.txtName.GetValue()}
****#其他条件# 将 search_params 合并到 params 中
params.update(search_params) # 发送查询请求
data = await api.GetList(params)
if data.success:
result = data.result
self.data = result.items
self.total_count = result.totalCount

而OnAdd用于打开新增对话框。

    def OnAdd(self) -> None:
"""子类重写-打开新增对话框"""
dlg = FrmCustomerEdit(self, columns_permit=self.columns_permit)
if dlg.exec() == QDialog.DialogCode.Accepted:
# 新增成功,刷新表格
asyncio.run(self.update_grid())
dlg.deleteLater()

而 OnEditById 用于编辑对话框的打开

    def OnEditById(self, entity_id: Any | str):
"""子类重写-根据主键值打开编辑对话框"""
# 使用列表窗体获得的字段权限
dlg = FrmCustomerEdit(self, entity_id, columns_permit=self.columns_permit)
# 获取对话框结果
if dlg.exec() == QDialog.DialogCode.Accepted:
# 编辑成功,刷新表格
asyncio.run(self.update_grid())
dlg.deleteLater()

而删除对话框的处理,如下函数所示。

    async def OnDeleteByIdList(self, id_list: List[Any | str]):
"""子类重写-根据主键值删除记录""" # 发送删除请求
result = await api.DeleteByIds(id_list)
# print(result)
if result.success:
# 删除成功,刷新表格
await self.update_grid()
else:
error = result.errorInfo.message if result.errorInfo else "未知错误"
MessageUtil.show_error("删除失败:%s" % error)

以上就是我们对于基类列表界面的抽象,和具体子类的一些个性化函数重写的处理,以便实现更好的逻辑抽象并保证具体个性化页面内容的处理。

对于不同的页面,我们可以公用同一个列表界面的基类,可以简化子类的很多操作,并能够统一整体的界面效果,提供更多通用的功能入口,是一种比较好的设计模式。

使用PySide6/PyQt6实现Python跨平台通用列表页面的基类设计的更多相关文章

  1. 正确理解Widget::Widget(QWidget *parent) :QWidget(parent)这句话(初始化列表中无法直接初始化基类的数据成员,所以你需要在列表中指定基类的构造函数)

    最近有点忙,先发一篇我公众号的文章,以下是原文. /********原文********/ 最近很多学习Qt的小伙伴在我的微信公众号私信我,该如何理解下面段代码的第二行QWidget(parent) ...

  2. Python记通用列表操作之切片!

    ______________________________________除使用索引(indexing)来访问单个元素外,还可使用切片 (slicing) 来访问特定范围内的元素. 切片适用于提取序 ...

  3. python 用abc模块构建抽象基类Abstract Base Classes

    见代码: #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/08/01 16:58 from abc import ABCMet ...

  4. python基础之列表常用操作及知识点小结

    列表(list) List(列表) 是 Python 中使用最频繁的数据类型.列表可以完成大多数集合类的数据结构实现.它支持字符,数字,字符串甚至可以包含列表(所谓嵌套).列表用[ ]标识,是pyth ...

  5. 再一波Python实战项目列表

    前言: 近几年Python可谓是大热啊,很多人都纷纷投入Python的学习中,以前我们实验楼总结过多篇Python实战项目列表,不但有用还有趣,最主要的是咱们实验楼不但有详细的开发教程,更有在线开发环 ...

  6. python 字典,列表,集合,字符串,基础进阶

    python列表基础 首先当然是要说基础啦 列表list 1.L.append(object) -> None 在列表末尾添加单个元素,任何类型都可以,包括列表或元组等 2.L.extend(i ...

  7. python字符串、列表和文件对象总结

    1.字符串是字符序列.字符串文字可以用单引号或者双引号分隔. 2.可以用内置的序列操作来处理字符串和列表:连接(+).重复(*).索引([]),切片([:])和长度(len()).可以用for循环遍历 ...

  8. python学习之列表和元组

    配置环境:python 3.6 python编辑器:pycharm,代码如下: #!/usr/bin/python # -*- coding: UTF-8 -*- # list:是一种有序的集合,可以 ...

  9. Python学习3——列表和元组

    一.通用序列操作——索引.切片.相加.相乘.成员资格检查 1.索引,正序从0开始为第一个元素,逆序从-1开始,-1为最后一个元素 >>> greeting[0] 'h' >&g ...

  10. python通用读取vcf文件的类(可以直接复制粘贴使用)

    前言   处理vcf文件的时候,需要多种切割,正则匹配,如果要自己写其实会比较麻烦,并且每次还得根据vcf文件格式或者需要读取的值不同要修改相应的代码.因此很多人会选择一些python的vcf的库,但 ...

随机推荐

  1. sudo: source: command not found

    在Ubuntu上配置了jdk(非root用户),要使它的配置生效,在执行 sudo source /etc/profile 的时候提示 ,sudo: source: command not found ...

  2. pygame基础功能总结

    1.导入Pygame模块 (1) 模块并初始化 ① Import pygame ② Pygame.init() (2) 创建窗体 ① Window_size = (800,600)  长宽 ② Scr ...

  3. Qt/C++音视频开发55-加密保存到文件并解密播放

    一.前言 为了保证视频文件的安全性,有时候需要对保存的视频文件加密,然后播放的时候解密出来再播放,只有加密解密的秘钥一致时才能正常播放,用ffmpeg做视频文件的加密保存和解密播放比较简单,基于ffm ...

  4. Qt编写视频监控系统75-计算实时码率并显示

    一.前言 做监控摄像头的实时视频显示,一般还会要求统计实时码率显示在通道画面上,一个是为了测试下整个软件的性能,同时也看下当前到底是主码流还是子码流,设备到底是不是真的按照设定的码流大小来传输视频数据 ...

  5. TensorRT-YOLO:灵活易用的 YOLO 部署工具

    TensorRT YOLO TensorRT-YOLO 是一款专为 NVIDIA 设备设计的易用灵活.极致高效的YOLO系列推理部署工具.项目不仅集成了 TensorRT 插件以增强后处理效果,还使用 ...

  6. springboot的Web项目编译运行时提示错误:Field userService in com.cetc.UserManger.controller.UserController required a bean of type 'com.cetc.UserManger.service.UserService' that could not be found.

    错误描述: springboot的Web项目编译运行时提示错误:Field userService in com.cetc.UserManger.controller.UserController r ...

  7. CF1763C Another Array Problem

    人类智慧题.harmis_yz 不会. 题意 \(\tt{Link}\) 给定一个序列 \(\{a_n\}\),可以进行若干次操作,每次可以选择 \(i,j(1 \le i < j \le n) ...

  8. 使用Vue+ElementUI实现前端分页

    背景 项目中要做一个公共的附件展示列表,针对某个模块某条记录展示,因此附件不会是大数据量,采用前端分页,使用Vue.JS+ElementUI布局展示,axios请求数据. 步骤 一.Html页面中引入 ...

  9. .NET 异步 /Task

    老版本的写法经常是以BeginXXX, EndXXX, 或者xx.xxxAsycn(........) 新的支持 async异步关键字配合Task可读性和易用性比老板好多了. 新旧例子: using ...

  10. 关于 static 和 final 的一些理解

    今天主要回顾一下 static 和 final 这两个关键字. 1. static  -  静态 修饰符 - 用于修饰数据(变量.对象).方法.代码块以及内部类.         1.1 静态变量 用 ...