Qt中跨Widget获取公司ID实现过滤的最佳实践问询
我开发了一个自定义下拉组件CompanyDropDown,用来获取数据库里的公司名称。它的用法类似QLineEdit,区别是输入时会弹出公司名称列表,选中后会携带公司ID发出companyChanged信号。
这个组件下方是一个QTabWidget,把选中公司的信息拆分到不同标签页,每个标签页对应独立组件,比如W1、W2和W3。
以W1为例,它包含两个过滤子组件FilterA、FilterB,一个“应用”按钮Apply,还有一个QTableView。QTableView的模型是QAbstractTableModel的子类。点击Apply时,过滤组件的数据会传给模型方法,从数据库查询公司数据并展示在表格里。
MainWidget包含CompanyDropDown和QTabWidget,通过把CompanyDropDown.companyChanged信号连接到W1、W2、W3来实现组件间的通信。
在CompanyDropDown中选中公司时,公司ID会转发到其他组件并应用过滤,代码示例如下:
class MainWidget(QWidget): def __init__(self): self.w1 = W1() self.w2 = W2() self.w3 = W3() self.tabWidget = QTabWidget() self.tabWidget.addTab(self.w1, 'W1') self.tabWidget.addTab(self.w2, 'W2') self.tabWidget.addTab(self.w3, 'W3') self.companyDropDown = CompanyDropDown() self.companyDropDown.companyChanged.connect(self.w1.onCompanyChanged) self.companyDropDown.companyChanged.connect(self.w2.onCompanyChanged) self.companyDropDown.companyChanged.connect(self.w3.onCompanyChanged) ... # 布局定义 class W1(QWidget): def __init__(self): self.filterA = FilterA() self.filterB = FilterB() self.apply = QPushButton('Apply') self.apply.clicked.connect(self.applyFilter) self.tableView = QTableView() self.tableView.setModel(MyCustomTableModel()) ... # 布局定义 @pyqtSlot(int) def onCompanyChanged(self, companyId: int): a = self.filterA.data() b = self.filterB.data() self.tableView.model().populate(companyId, a, b) def applyFilter(self): ... class MyCustomTableModel(QAbstractTableModel): def populate(self, companyId: int, a: Any, b: Any): ...
上述逻辑可行,但当只有FilterA或FilterB的过滤数据变化时,applyFilter()方法无法获取公司ID,代码示例如下:
class W1(QWidget): def applyFilter(self): companyId = ... # 从何处获取? a = self.filterA.data() b = self.filterB.data() self.tableView.model().populate(companyId, a, b)
一个直观的解决方案是在W1中存储公司ID,在onCompanyChanged调用时更新,但W2、W3也需要做同样的操作,不够简洁。我不想在多个组件中存储公司ID,因为它本可以直接从CompanyDropDown获取,比如通过CompanyDropDown.companyId()方法,就像获取FilterA.data()一样。请问这个场景下的最佳实践是什么?
我想到的一个思路是把populate()的调用委托给MainWidget:
class W1(QWidget): filterA_Changed = pyqtSignal(...) filterB_Changed = pyqtSignal(...) def model(self) -> MyCustomTableModel: return self.tableView.model() def a(self): return self.filterA.data() def b(self): return self.filterB.data() class MainWidget(QWidget): def __init__(self): self.w1.filterA_Changed.connect(self.onW1FilterAChanged) self.w1.filterB_Changed.connect(self.onW1FilterBChanged) def onW1FilterAChanged(self, a: Any): b = self.w1.b() model = self.w1.model() model.populate(self.companyDropDown.companyId(), a, b) def onW1FilterBChanged(self, b: Any): a = self.w1.a() model = self.w1.model() model.populate(self.companyDropDown.companyId(), a, b)
方案1:集中式状态管理(推荐)
把当前选中的公司ID作为父级MainWidget维护的单一状态,子组件通过父级引用获取,避免多组件重复存储。
实现代码:
class MainWidget(QWidget): def __init__(self): self.current_company_id = None self.companyDropDown = CompanyDropDown() self.companyDropDown.companyChanged.connect(self.update_company_id) # 其他初始化代码... def update_company_id(self, company_id: int): self.current_company_id = company_id # 同步通知子组件更新 self.w1.onCompanyChanged(company_id) self.w2.onCompanyChanged(company_id) self.w3.onCompanyChanged(company_id) def get_current_company_id(self) -> int: return self.current_company_id class W1(QWidget): def __init__(self, parent: MainWidget): super().__init__(parent) self.parent = parent # 其他初始化代码... def applyFilter(self): company_id = self.parent.get_current_company_id() if company_id is None: return # 处理未选中公司的边界情况 a = self.filterA.data() b = self.filterB.data() self.tableView.model().populate(company_id, a, b)
这种方式遵循单一数据源原则,所有子组件统一从父级获取公司ID,避免数据冗余和不一致。
方案2:简化信号委托逻辑
基于你提出的委托思路,优化信号设计,不用为每个过滤组件单独定义信号,只需要一个“过滤应用”信号传递所有过滤参数,由MainWidget负责组合公司ID和过滤条件:
实现代码:
class W1(QWidget): filter_applied = pyqtSignal(object, object) # 携带两个过滤参数的统一信号 def __init__(self): super().__init__() self.apply.clicked.connect(self.emit_filter_signal) # 其他初始化代码... def emit_filter_signal(self): a = self.filterA.data() b = self.filterB.data() self.filter_applied.emit(a, b) class MainWidget(QWidget): def __init__(self): self.w1.filter_applied.connect(self.on_w1_filter_applied) # 其他初始化代码... def on_w1_filter_applied(self, a, b): company_id = self.companyDropDown.companyId() self.w1.tableView.model().populate(company_id, a, b)
这种方式让子组件只关注自身过滤逻辑,MainWidget作为中枢负责整合所有必要参数,代码更简洁。
方案3:将公司ID注入模型
让子组件的模型直接维护当前公司ID,子组件仅在公司切换时更新模型参数,后续过滤无需再传递公司ID:
实现代码:
class MyCustomTableModel(QAbstractTableModel): def __init__(self): super().__init__() self.company_id = None def set_company_id(self, company_id: int): self.company_id = company_id def populate(self, a: Any, b: Any): if self.company_id is None: return # 使用self.company_id、a、b执行数据库查询 ... class W1(QWidget): def onCompanyChanged(self, companyId: int): self.tableView.model().set_company_id(companyId) self.applyFilter() def applyFilter(self): a = self.filterA.data() b = self.filterB.data() self.tableView.model().populate(a, b)
这种方式让模型的职责更内聚,子组件调用过滤时只需传递过滤条件,逻辑更清晰。
内容的提问来源于Stack Exchange,提问作者Giovanni L

