使用PySide6/PyQt6實(shí)現(xiàn)Python跨平臺通用列表頁面的基類設(shè)計
我在隨筆《使用PySide6/PyQt6實(shí)現(xiàn)Python跨平臺GUI框架的開發(fā)》中介紹過PySide6/PyQt6 框架架構(gòu)的整體設(shè)計,本篇隨筆繼續(xù)深入探討框架的設(shè)計開發(fā)工作,主要針對通用列表頁面的基類設(shè)計進(jìn)行介紹,分析基類的各個模塊的功能,以及介紹如何抽象一些公用的邏輯,實(shí)現(xiàn)對子類頁面的簡化處理。
1、通用列表界面的設(shè)計
大多數(shù)情況下,界面的表現(xiàn)邏輯可以使用不同的規(guī)則進(jìn)行抽象,如自定義控件、列表界面、彈出對話框界面等,我們把它抽象出來進(jìn)行不同的處理。子類界面進(jìn)行一定程度的擴(kuò)張即可獲得更好的使用、更簡化的代碼。

對于列表和對話框界面的封裝,能夠簡化對泛型模型數(shù)據(jù)的統(tǒng)一處理,因此可以簡化繼承子類的代碼,提供代碼維護(hù)開發(fā)和維護(hù)的效率。

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

樹列表或者表格控件,右鍵可以彈出相關(guān)的右鍵菜單

列表包含有有樹形列表、條件查詢框、通用條件(查詢、新增、編輯、刪除、導(dǎo)出)等、列表展示、分頁導(dǎo)航、右鍵菜單等內(nèi)容。這些都是在基類中進(jìn)行了統(tǒng)一的抽象處理,子類根據(jù)需要調(diào)整屬性或重寫相關(guān)函數(shù)即可實(shí)現(xiàn)個性化的界面定義。
2、通用列表界面的分析處理
如果我們需要設(shè)計通用列表界面窗體的基類,那么我們需要盡可能的減少子類的代碼,把常用的功能封裝在基類里面,以及特殊的內(nèi)容,可以通過封裝邏輯,下發(fā)具體實(shí)現(xiàn)給子類進(jìn)行重寫實(shí)現(xiàn)即可。
前面我們介紹過,常用列表包含有有樹形列表、條件查詢框、通用條件(查詢、新增、編輯、刪除、導(dǎo)出)等、列表展示、分頁導(dǎo)航、右鍵菜單等內(nèi)容,另外還有詳細(xì)的需要接受一些子類的列表字段顯示和中文參考,以及表格處理的功能按鈕的權(quán)限控制等方面。
由于我們需要子類傳入的相關(guān)DTO類型,因此我們定義泛型類型來傳入處理。
基類定義如下所示。
ModelType = TypeVar("ModelType") # 定義泛型基類 # 創(chuàng)建泛型基類 BaseListFrame ,并繼承 QMainWindow class BaseListFrame(QMainWindow, Generic[ModelType]):
另外我們初始化函數(shù),需要接受子類的一些信息,用于對顯示內(nèi)容進(jìn)行精準(zhǔn)的控制處理,因此構(gòu)造函數(shù)__init__里面定義好相關(guān)的參數(shù),如下所示。
# 創(chuàng)建泛型基類 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: 實(shí)體類 :param display_columns: 顯示的字段名稱,逗號分隔,如:id,name,customid,authorize,note :param column_mapping: 列名映射(字段名到顯示名的映射)dict格式:{"name": "顯示名稱"} :param items_per_page: 每頁顯示的行數(shù) :param EVT_FLAGS: 設(shè)置可以顯示的操作按鈕 :param show_menu_tips: 是否顯示提示信息 :param menu_tips: 設(shè)置菜單提示信息 :param use_left_panel: 是否使用樹控件 :param column_widths: Grid列的寬度設(shè)置 """
1)樹列表的控制和實(shí)現(xiàn)
我們在init函數(shù)里面,主要通過_create_content()函數(shù)進(jìn)行創(chuàng)建界面元素。
def _create_content(self): """創(chuàng)建主要內(nèi)容面板""" # 創(chuàng)建左側(cè)樹控件 if self.use_left_panel: self._merge_tree_panel() # "創(chuàng)建右側(cè)主要內(nèi)容面板 content_panel = self._create_content_panel() self.setCentralWidget(content_panel)
它負(fù)責(zé)判斷是否需要展示樹列表,如果打開顯示樹的開關(guān),就根據(jù)樹形列表的集合進(jìn)行構(gòu)建左側(cè)的樹列表顯示。
def _merge_tree_panel(self): """合并左側(cè)樹控件""" 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("") # 左側(cè)樹控件 # 創(chuàng)建 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) # 禁止關(guān)閉按鈕 dock_widget.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures) # 將 QDockWidget 添加到主窗口的左側(cè) self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock_widget)
上面代碼就是在左側(cè)構(gòu)建一個 QDockWidget 的停靠區(qū)域,我們把所有樹列表的集合放到其中容器的 QTabWidget 里面即可。
在抽象的父類里面,我們只需要給出一個默認(rèn)的 create_tree_panels 實(shí)現(xiàn)函數(shù)即可,如下所示。
def create_tree_panels(self) -> dict[str, QWidget]: """子類重寫該方法,創(chuàng)建左側(cè)樹列表面板-可以多個樹列表""" tree_panels: dict[str, QWidget] = {} # 創(chuàng)建樹控件 # tree_panels["Tab 1"] = QLabel(self) # tree_panels["Tab 2"] = QLabel(self) return tree_panels
而 create_tree_panels 具體的實(shí)現(xiàn) 我們是留給子類進(jìn)行重寫的,因為我們不清楚具體的顯示,但是我們可以把它們邏輯上組合起來即可。
如對于上面展示的用戶列表界面,這部分create_tree_panels 的代碼實(shí)現(xiàn)如下所示。
def create_tree_panels(self) -> dict[str, QWidget]: """子類重寫該方法,創(chuàng)建左側(cè)樹列表面板-可以多個樹列表""" 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["按組織機(jī)構(gòu)查看"] = self.tree_dept dict["按角色查看"] = self.tree_role return dict
其中ctrl.MyTreePanel的控件是我們自定義的一個樹列表控件,用于減少重復(fù)性的代碼,抽象一個樹列表的展示,有利于我們保持更好的控制,統(tǒng)一界面效果的處理。
在子類的構(gòu)造函數(shù)處理上,我們只需要設(shè)置參數(shù) use_left_panel = True,并且實(shí)現(xiàn) create_tree_panels 函數(shù)即可。

2)查詢條件控件內(nèi)容
介紹完畢樹列表的處理,我們再次來到基類的界面構(gòu)建處理函數(shù)上。
def _create_content(self): """創(chuàng)建主要內(nèi)容面板""" # 創(chuàng)建左側(cè)樹控件 if self.use_left_panel: self._merge_tree_panel() # "創(chuàng)建右側(cè)主要內(nèi)容面板 content_panel = self._create_content_panel() self.setCentralWidget(content_panel)
其中的_create_content_panel 是我們構(gòu)建主查詢面板內(nèi)容的,其中包括輸入條件展示、常見按鈕顯示、以及列表、分頁欄目等。
def _create_content_panel(self) -> QWidget: """創(chuàng)建右側(cè)主要內(nèi)容面板""" panel = QWidget(self) # 創(chuàng)建一個垂直布局 main_layout = QVBoxLayout() # 創(chuàng)建一個折疊的查詢條件框 search_bar = self._create_search_bar(panel) main_layout.addWidget(search_bar) # 創(chuàng)建顯示數(shù)據(jù)的表格 table_widget = self._create_grid(panel) main_layout.addWidget(table_widget, 1) # 拉伸占用全部高度 # 創(chuàng)建一個分頁控件 self.pager_bar = ctrl.MyPager(panel, self.items_per_page, self.update_grid) main_layout.addWidget(self.pager_bar) # 設(shè)置布局 panel.setLayout(main_layout) return panel
上面標(biāo)注特殊的代碼,就是對不同模塊的邏輯進(jìn)行分離實(shí)現(xiàn),從而讓我們關(guān)注點(diǎn)集中一些。其中的create_search_bar里面,主要封裝了查詢條件框、常規(guī)按鈕、自定義按鈕等內(nèi)容。
def _create_search_bar(self, parent: QWidget = None) -> QWidget: """創(chuàng)建折疊的查詢條件框,包含查詢條件輸入框和常規(guī)按鈕""" panel = QWidget(parent) # 創(chuàng)建一個垂直布局 layout = QVBoxLayout() panel.setLayout(layout) # 添加查詢條件控件 input_sizer = self.CreateConditionsWithSizer(panel) layout.addLayout(input_sizer, 0) layout.addSpacing(5) # 增加間距 # 添加常規(guī)按鈕 btns_sizer = self._CreateCommonButtons(panel) # 自定義按鈕 self.CreateCustomButtons(panel, btns_sizer) layout.addLayout(btns_sizer, 0) return panel
我在基類窗體的抽象類里面,定義了默認(rèn)的布局規(guī)則,如下代碼所示。
def CreateConditionsWithSizer(self, parent: QWidget = None) -> QGridLayout: """子類可重寫該方法,創(chuàng)建折疊面板中的查詢條件,包括布局 QGridLayout""" layout = QGridLayout() layout.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) layout.setSpacing(5) # 增加間距 # 統(tǒng)一處理查詢條件控件的添加,使用默認(rèn)的布局方式 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]: """子類可重寫該方法,創(chuàng)建折疊面板中的查詢條件輸入框控件,不包括布局,使用默認(rèn)的布局方式 QGridLayout""" list = [QWidget] # 示例代碼: lblName = QLabel("名稱:") self.txtName = ctrl.MyTextCtrl(parent, "請輸入名稱") list.append(lblName) list.append(self.txtName) return list
如果我們不改變布局,那么我們主要實(shí)現(xiàn) CreateConditions 函數(shù)即可。這個函數(shù)也是比較簡單的,構(gòu)建所需的輸入幾個條件即可。
如對于簡單的客戶信息界面,它的條件輸入框里面就幾個條件。

我們根據(jù)上面的界面效果,可以看到客戶窗體子類實(shí)現(xiàn) CreateConditions 函數(shù)的代碼如下所示。
def CreateConditions(self, parent: QWidget = None) -> list[QWidget]: """創(chuàng)建折疊面板中的查詢條件輸入框控件""" # 創(chuàng)建控件,不用管布局,交給CreateConditionsWithSizer控制邏輯 # 默認(rèn)的QGridLayout 為4*2=8列,每列間隔5px self.txtName = ctrl.MyTextCtrl(parent) self.txtAge = ctrl.MyNumericRange(parent) self.txtCustomerType = ctrl.MyComboBox(parent) # ControlUtil 可以方便的創(chuàng)建文本標(biāo)簽和控件的組合,并返回所有的控件列表 util = ControlUtil(parent) util.add_control("姓名:", self.txtName) util.add_control("年齡:", self.txtAge) util.add_control("客戶類型:", self.txtCustomerType) return util.get_controls()
這樣,具體實(shí)現(xiàn)部分,對于WxPython和PySide6/PyQt6來說,代碼都是差不多的,因為我們用了自定義用戶控件類,并使用輔助函數(shù),讓它們和標(biāo)簽更好的粘合起來。
對于自定義控件,我們對其封裝,使之能夠在開發(fā)使用習(xí)慣上更一致,下面是我們根據(jù)需要對常見的原生控件進(jìn)行一些自定義控件的封裝列表。

對于常規(guī)的按鈕,我們根據(jù)權(quán)限集合進(jìn)行判斷是否顯示即可,自定義按鈕則留給子類進(jìn)一步實(shí)現(xiàn)。
# 添加常規(guī)按鈕 btns_sizer = self._CreateCommonButtons(panel) # 自定義按鈕 self.CreateCustomButtons(panel, btns_sizer)
對于常規(guī)的按鈕,代碼如下所示。

而自定義按鈕的處理,我們留給子類實(shí)現(xiàn),父類給出一個默認(rèn)的函數(shù)即可。
def CreateCustomButtons(self, parent: QWidget, btns_sizer: QHBoxLayout) -> None: """子類可重寫該方法,創(chuàng)建折疊面板中的自定義按鈕""" # 增加按鈕 pass
3)表格數(shù)據(jù)顯示
我們回到前面介紹的代碼。
def _create_content_panel(self) -> QWidget: """創(chuàng)建右側(cè)主要內(nèi)容面板""" panel = QWidget(self) # 創(chuàng)建一個垂直布局 main_layout = QVBoxLayout() # 創(chuàng)建一個折疊的查詢條件框 search_bar = self._create_search_bar(panel) main_layout.addWidget(search_bar) # 創(chuàng)建顯示數(shù)據(jù)的表格 table_widget = self._create_grid(panel) main_layout.addWidget(table_widget, 1) # 拉伸占用全部高度 # 創(chuàng)建一個分頁控件 self.pager_bar = ctrl.MyPager(panel, self.items_per_page, self.update_grid) main_layout.addWidget(self.pager_bar) # 設(shè)置布局 panel.setLayout(main_layout) return panel
其中 _create_grid 就是我們創(chuàng)建表格內(nèi)容的邏輯函數(shù)了,它負(fù)責(zé)創(chuàng)建一個QTableView 元素進(jìn)行展示,表格數(shù)據(jù)的綁定,通過只定義模型MyTableModel 來綁定界面顯示的。
def _create_grid(self, parent: QWidget) -> QTableView: """創(chuàng)建顯示數(shù)據(jù)的表格""" 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, # 替換內(nèi)容函數(shù) forground_color_handler=self.paint_foreground, # 前景色渲染函數(shù) ) 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)
表格頭部排序、右鍵菜單、表格特殊的選中和內(nèi)容轉(zhuǎn)義、背景色處理、導(dǎo)出Excel、導(dǎo)出PDF、打印預(yù)覽等,我能都可以通過對表格的一些屬性或者方法進(jìn)行跟蹤處理即可實(shí)現(xiàn)。這里由于篇幅原因,不在深入探討。
4)分頁信息展示
對于分頁內(nèi)容,表格顯示是不負(fù)責(zé)的,因此我們需要根據(jù)模型對象,構(gòu)建一個分頁控件來顯示,把它剝離基類列表的主界面,有利于減少我們的關(guān)注點(diǎn)分散,也有利于重用控件。

前面的邏輯代碼中。
def _create_content_panel(self) -> QWidget: """創(chuàng)建右側(cè)主要內(nèi)容面板""" panel = QWidget(self) # 創(chuàng)建一個垂直布局 main_layout = QVBoxLayout() # 創(chuàng)建一個折疊的查詢條件框 search_bar = self._create_search_bar(panel) main_layout.addWidget(search_bar) # 創(chuàng)建顯示數(shù)據(jù)的表格 table_widget = self._create_grid(panel) main_layout.addWidget(table_widget, 1) # 拉伸占用全部高度 # 創(chuàng)建一個分頁控件 self.pager_bar = ctrl.MyPager(panel, self.items_per_page, self.update_grid) main_layout.addWidget(self.pager_bar) # 設(shè)置布局 panel.setLayout(main_layout) return panel
分頁控件是獨(dú)立的一個用戶控件。
class MyPager(QWidget): """列表的分頁控件""" def __init__(self, parent=None, items_per_page=10, on_update=None, total_count=0): """初始化 :param parent: 父控件 :param items_per_page: 每頁的行數(shù) :param on_update: 查詢數(shù)據(jù)的回調(diào)函數(shù),為異步函數(shù) :param total_count: 總記錄數(shù) """ 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)
通過有效的隔離,使得我們每次只需要關(guān)注特定部分的處理,而具體的邏輯由基類統(tǒng)一控制,特殊的具體實(shí)現(xiàn)交給子類重寫基類函數(shù)即可。
完成了上面的處理后,我們發(fā)現(xiàn)業(yè)務(wù)模塊的子類需要實(shí)現(xiàn)的內(nèi)容比較少了,大多數(shù)交給抽象父類實(shí)現(xiàn)了。
5)數(shù)據(jù)的初始化處理
完成了界面元素的創(chuàng)建后,我們還需要再基類中統(tǒng)一一些數(shù)據(jù)初始化的函數(shù),如我們在構(gòu)造函數(shù)里面創(chuàng)建好內(nèi)容后,調(diào)用了init_ui的函數(shù)初始化界面元素。
# 創(chuàng)建泛型基類 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: 實(shí)體類 :param display_columns: 顯示的字段名稱,逗號分隔,如:id,name,customid,authorize,note :param column_mapping: 列名映射(字段名到顯示名的映射)dict格式:{"name": "顯示名稱"} :param items_per_page: 每頁顯示的行數(shù) :param EVT_FLAGS: 設(shè)置可以顯示的操作按鈕 :param show_menu_tips: 是否顯示提示信息 :param menu_tips: 設(shè)置菜單提示信息 :param use_left_panel: 是否使用樹控件 :param column_widths: Grid列的寬度設(shè)置 """ 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 # 每頁顯示的行數(shù) self.EVT_FLAGS = EVT_FLAGS # 設(shè)置可以顯示的操作按鈕 self.show_menu_tips = show_menu_tips # 是否顯示提示信息 self.menu_tips = menu_tips # 設(shè)置菜單提示信息 self.use_left_panel = use_left_panel # 是否使用樹控件 self.column_widths = column_widths # Grid列的寬度設(shè)置 self.plugins = plugins or {} # 單元格的渲染列表,格式:{"列名稱": 插件實(shí)例} self.columns_permit = {} # 字段權(quán)限 self.total_count = 0 # 記錄總數(shù) # 創(chuàng)建主要內(nèi)容面板 self._create_content()# 調(diào)度異步任務(wù), 使用@asyncSlot()裝飾器后,你可以像同步函數(shù)一樣調(diào)用異步方法 self.init_ui() @asyncSlot() async def init_ui(self): """初始化界面""" # 使用 @asyncSlot 裝飾器后,你可以像同步函數(shù)一樣調(diào)用異步方法,Qt 會自動管理異步任務(wù)的調(diào)度和執(zhí)行, # 不需要顯式使用 await 或者 asyncio.create_task 來啟動異步任務(wù)。 # 如果你在子類中重寫了 init_ui,你仍然需要在子類中顯式地添加 @asyncSlot() 裝飾器。 # 在子類中,Python 會將其視為新的方法定義,因此你必須在子類中的方法上再次應(yīng)用 @asyncSlot() 裝飾器來確保它仍然被處理為異步槽。 await self.init_dict_items() await self.init_treedata() await self.update_grid() async def init_dict_items(self): """初始化字典數(shù)據(jù)-子類可重寫""" # await self.txtCustomerType.bind_dictType("客戶類型") pass async def init_treedata(self): """初始化樹控件數(shù)據(jù)-子類可重寫""" pass async def update_grid(self) -> None: """更新表格的內(nèi)容""" # 查詢數(shù)據(jù) await self.OnQuery() # 獲取當(dāng)前用戶有權(quán)限查看的列 self.columns_permit = await self.get_columns_permit() # 更新表格數(shù)據(jù) self.table_model.UpdateData(self.data, self.columns_permit) # 更新頁碼信息 self._update_pager()
而各個子類負(fù)責(zé)各自模塊內(nèi)容的初始化即可。
3、子類列表界面代碼分析
由于父類已經(jīng)抽象了很多相關(guān)的元素創(chuàng)建、數(shù)據(jù)初始化的邏輯函數(shù),因此子類根據(jù)需要重寫函數(shù)實(shí)現(xiàn)即可。
如對于簡單的業(yè)務(wù)表,客戶信息表,它的子類只需要實(shí)現(xiàn)下面幾個函數(shù)即可。

def CreateConditions(self, parent: QWidget = None) -> list[QWidget]: """創(chuàng)建折疊面板中的查詢條件輸入框控件""" # 創(chuàng)建控件,不用管布局,交給CreateConditionsWithSizer控制邏輯 # 默認(rèn)的QGridLayout 為4*2=8列,每列間隔5px self.txtName = ctrl.MyTextCtrl(parent) self.txtAge = ctrl.MyNumericRange(parent) self.txtCustomerType = ctrl.MyComboBox(parent) # ControlUtil 可以方便的創(chuàng)建文本標(biāo)簽和控件的組合,并返回所有的控件列表 util = ControlUtil(parent) util.add_control("姓名:", self.txtName) util.add_control("年齡:", self.txtAge) util.add_control("客戶類型:", self.txtCustomerType) return util.get_controls()
OnQuery函數(shù)負(fù)責(zé)提取輸入條件,并提交服務(wù)端獲取數(shù)據(jù)返回。
async def OnQuery(self): """子類實(shí)現(xiàn)-發(fā)送查詢請求, 需設(shè)置self.data,self.total_count""" # 獲取默認(rèn)查詢參數(shù),包括skipCount,maxResultCount,sorting params = self.GetDefaultParams() # 新的數(shù)據(jù),可以從控件獲取,也可以是動態(tài)生成的 search_params = { "name": self.txtName.GetValue()} ****#其他條件# 將 search_params 合并到 params 中 params.update(search_params) # 發(fā)送查詢請求 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): """子類重寫-根據(jù)主鍵值打開編輯對話框""" # 使用列表窗體獲得的字段權(quán)限 dlg = FrmCustomerEdit(self, entity_id, columns_permit=self.columns_permit) # 獲取對話框結(jié)果 if dlg.exec() == QDialog.DialogCode.Accepted: # 編輯成功,刷新表格 asyncio.run(self.update_grid()) dlg.deleteLater()
而刪除對話框的處理,如下函數(shù)所示。
async def OnDeleteByIdList(self, id_list: List[Any | str]): """子類重寫-根據(jù)主鍵值刪除記錄""" # 發(fā)送刪除請求 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)
以上就是我們對于基類列表界面的抽象,和具體子類的一些個性化函數(shù)重寫的處理,以便實(shí)現(xiàn)更好的邏輯抽象并保證具體個性化頁面內(nèi)容的處理。
對于不同的頁面,我們可以公用同一個列表界面的基類,可以簡化子類的很多操作,并能夠統(tǒng)一整體的界面效果,提供更多通用的功能入口,是一種比較好的設(shè)計模式。
專注于代碼生成工具、.Net/Python 框架架構(gòu)及軟件開發(fā),以及各種Vue.js的前端技術(shù)應(yīng)用。著有Winform開發(fā)框架/混合式開發(fā)框架、微信開發(fā)框架、Bootstrap開發(fā)框架、ABP開發(fā)框架、SqlSugar開發(fā)框架、Python開發(fā)框架等框架產(chǎn)品。
??轉(zhuǎn)載請注明出處:撰寫人:伍華聰??http://www.iqidi.com?
????
浙公網(wǎng)安備 33010602011771號