动态添加 QTableView 到动态创建的标签页 (QTabWidget) [已解决]

Posted

技术标签:

【中文标题】动态添加 QTableView 到动态创建的标签页 (QTabWidget) [已解决]【英文标题】:Dynamically add QTableView to dynamically created tab pages (QTabWidget) [SOLVED] 【发布时间】:2021-04-27 17:19:21 【问题描述】:

我正在尝试在运行时创建一系列 QTableView 并添加到多页 QTabWidget 的新创建页面中。

一切似乎都很好,但 QTableView 没有出现。 QTabWidget 被归零(重置为无页面)并完美地翻新(...)(至少看起来像这样),具体取决于组合框的选择(以及其中相关的字典)。

我还使用委托回调在 QTableView 中包含一列复选框(感谢 https://***.com/a/50314085/7710452),它可以单独工作。

这里是代码。 主窗口

编辑 根据 eyllanesc 的建议,这里是独立模块(我认为有问题的部分的详细信息请跳到文章末尾):

"""
    qt5 template
"""
import os
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import uic
from configparser import ConfigParser, ExtendedInterpolation
from lib.SearchControllers import findGuis, get_controller_dict, show_critical, show_exception
import resources.resources
from lib.CheckBoxesDelegate import CheckBoxDelegate
myForm_2, baseClass = uic.loadUiType('./forms/setup.ui')
class MainWindow(baseClass):

    def __init__(self, config_obj: ConfigParser,
                 config_name: str,
                 proj_name: str,
                 *args,
                 **kwargs):

        super().__init__(*args, **kwargs)
        self.ui = myForm_2()
        self.ui.setupUi(self)

        # your code begins here
        self.setWindowTitle(proj_name + " Setup")
        self.ui.logo_lbl.setPixmap(qtg.QPixmap(':/logo_Small.png'))
        self.config_obj = config_obj
        self.config_name = config_name
        self.proj_filename = proj_name
        self.proj_config = ConfigParser(interpolation=ExtendedInterpolation())
        self.proj_config.read(proj_name)
        self.guis_dict = 
        self.components = 
        self.cdp_signals = 
        self.root_path = self.config_obj['active']['controllers']
        self.tableViews = []
        self.tabs = []
        self.iniControllersBox()
        self.setActSignals()
        self.load_bulk()
        self.set_signals_table()
        self.update_CurController_lbl()
        self.update_ControllersTab()    # here is where the action gets hot

        # your code ends here

        self.show() # here crashes if I passed the new tab to the instance of
                    # QTabView. otherwise it shows empty tabs
#########################################################

    def load_bulk(self):
        # get the list of running components into a dictionary
        for i in self.list_controllers:
            i_path = os.path.join(self.root_path, i)
            print(i)
            self.components[i] = get_controller_dict(i_path,
                                                     self.config_obj,
                                                     'Application.xml',
                                                     'Subcomponents/Subcomponent',
                                                     'Name',
                                                     'src')

            for j in self.components[i]:
                print(j)
                signals_key = (i , j)
                tgt = os.path.join(self.root_path, self.components[i][j])
                self.cdp_signals[signals_key] = get_controller_dict(i_path,
                                                                    self.config_obj,
                                                                    self.components[i][j],
                                                                    'Signals/Signal',
                                                                    'Name',
                                                                    'Type',
                                                                    'Routing')


    def set_signals_table(self):
        self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(0, qtw.QTableWidgetItem('GUI caption'))
        self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(1, qtw.QTableWidgetItem('Monitored Signal'))

    def setActSignals(self):
        self.ui.controllersBox.currentIndexChanged.connect(self.update_guis_list)
        self.ui.controllersBox.currentIndexChanged.connect(self.update_CurController_lbl)
        self.ui.controllersBox.currentIndexChanged.connect(self.update_ControllersTab)

    def update_ControllersTab(self):
        self.ui.componentsTab.clear()   # this is the QTabWidget
        self.tabs = []
        self.tableViews = []
        curr_controller = self.ui.controllersBox.currentText()
        for i in self.components[curr_controller]:
            if len(self.cdp_signals[curr_controller, i]) == 0:
                continue
            self.tabs.append(qtw.QWidget())
            tabs_index = len(self.tabs) - 1
            header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
            model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
            model.setHorizontalHeaderLabels(header_labels)
# in the next line I try to create a new QTableView passing
# the last tab as parameter, in the attempt to embed the QTableView
# into the QWidget Tab 

            self.tableViews.append(qtw.QTableView(self.tabs[tabs_index])) 
            tbw_Index = len(self.tableViews) - 1
            self.tableViews[tbw_Index].setModel(model)
            delegate = CheckBoxDelegate(None)
            self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
            rowCount = 0
            for row in self.cdp_signals[curr_controller, i]:
                for col in range(len(self.cdp_signals[curr_controller, i][row])):
                    index = model.index(rowCount, col, qtc.QModelIndex())
                    model.setData(index, self.cdp_signals[curr_controller, i][row][col])
            try:
                self.ui.componentsTab.addTab(self.tabs[tabs_index], i) # no problems, some controllers ask up to
            except Exception as ex:
                print(ex)

    def update_CurController_lbl(self):
        self.ui.active_controller_lbl.setText(self.ui.controllersBox.currentText())

    def iniControllersBox(self):
        self.list_controllers = [os.path.basename(f.path) for f in os.scandir(self.root_path) if f.is_dir() and str(
            f.path).upper().endswith('NC')]
        self.ui.controllersBox.addItems(self.list_controllers)
        for i in range(self.ui.controllersBox.count()):
            self.ui.controllersBox.setCurrentIndex(i)
            newKey = self.ui.controllersBox.currentText()
            cur_cntrlr = os.path.join(self.config_obj['active']['controllers'], self.ui.controllersBox.currentText())
            self.guis_dict[newKey] = findGuis(cur_cntrlr, self.config_obj)
        self.ui.controllersBox.setCurrentIndex(0)
        self.update_guis_list()

    def update_guis_list(self, index=0):
        self.ui.GuisListBox.clear()
        self.ui.GuisListBox.addItems(self.guis_dict[self.ui.controllersBox.currentText()])

if __name__ == '__main__':
    config = ConfigParser()
    config.read('./config.ini')
    app = qtw.QApplication([sys.argv])
    w = MainWindow(config, './config.ini',
                   './test_setup_1.proj')
                   
    sys.exit(app.exec_())

这里是外部添加复选框列:

class CheckBoxDelegate(QtWidgets.QItemDelegate):
    """
    A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
    """
    def __init__(self, parent):
        QtWidgets.QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        """
        Important, otherwise an editor is created if the user clicks in this cell.
        """
        return None

    def paint(self, painter, option, index):
        """
        Paint a checkbox without the label.
        """
        self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)

    def editorEvent(self, event, model, option, index):
        '''
        Change the data in the model and the state of the checkbox
        if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
        '''
        if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
            return False

        if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
            # Change the checkbox-state
            self.setModelData(None, model, index)
            return True

        if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
            return False

        return False


    def setModelData (self, editor, model, index):
        '''
        The user wanted to change the old state in the opposite.
        '''
        model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)

第一张图显示QTDesigner中的布局,第二张图是避免崩溃时的结果(空标签)。

QTabWidget 在归零或放大到我需要的尽可能多的选项卡方面没有问题,只是我不知道如何显示 QTabview。我的方法是尝试将 QTabView 嵌入标签页中,将其作为参数传递给创建新 QTabView 的行。

由于我使用的是相当复杂的字典,调用 XML 解析器来填充它们,更不用说配置文件了,我知道即使是我的脚本的这个版本也很难重现/运行。

如果有人有耐心专注于 update_ControllersTab 方法,并告诉我我在处理 QWidgets 时做错了什么,那就太好了。

同样的基本思想是在用户选择不同的控制器(左侧的组合框)时清除 QTabWidget:

    self.ui.componentsTab.clear()   # this is the QTabWidget
    self.tabs = []                  # list to hold QTabView QWidgets (pages) throughout the scope
    self.tableViews = []            # list to hold QTabView(s) thorughout the scope

计算我需要多少个选项卡(页面)和嵌入的 TabView 并选择新的控制器。 然后对于需要的每个选项卡:

创建一个新标签(页面)

    self.tabs.append(qtw.QWidget())
    tabs_index = len(self.tabs) - 1

使用模型创建一个新的 QTabView:

    header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
    model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
    model.setHorizontalHeaderLabels(header_labels)
    self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
    tbw_Index = len(self.tableViews) - 1
    self.tableViews[tbw_Index].setModel(model)

用数据填充 TableView,然后最后添加选项卡小部件(据称嵌入 QTableView 到 QTabWidget(i 参数是我的数据库名称中的字符串:

    self.ui.componentsTab.addTab(self.tabs[tabs_index], i)

__init__ 也调用此方法进行初始化,显然一切都没有错误,直到最后一个 'init' 语句:

`self.show()`

此时应用崩溃:

Process finished with exit code 1073741845

另一方面,如果在这里而不是尝试嵌入 QTableView:

self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))

我省略了参数,即:

self.tableViews.append(qtw.QTableView())

应用程序不再崩溃,但当然没有 QtableViews 显示,只有空标签页:

【问题讨论】:

请提供minimal reproducible example 【参考方案1】:

这听起来很愚蠢,问题出在...在第一列中创建复选框的委托类(请参阅https://***.com/a/50314085/7710452)

我把这两行注释掉了:

    delegate = CheckBoxDelegate(None)
    self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)

还有……宾果游戏!

CheckBoxDelegate 在帖子中显示的示例(单个 QTableView 表单)中工作正常。我还修改了添加列和行,并在没有问题的情况下来回移动复选框列。在那个独立的。但是一旦我添加了类并设置了委托,我又回到了零点,应用程序崩溃了:

Process finished with exit code 1073741845

所以我现在只剩下这个问题了。感谢阅读本文的人。

【讨论】:

复选框委托的问题是我没有初始化复选框被绘制为任何值的列,它必须初始化为零(未选中)或非零(通常为 1 ) 检查。将委托的 index.data() 参数留空始终为 None: 导致错误。【参考方案2】:

问题已解决,请参阅上面的评论。

【讨论】:

以上是关于动态添加 QTableView 到动态创建的标签页 (QTabWidget) [已解决]的主要内容,如果未能解决你的问题,请参考以下文章

MFC用CMFCTabCtrl类动态创建标签页,怎样添加消息响应

动态创建标签页

动态添加数据到QTableView

easyui 动态添加标签页,总结

RzPageControl(pagecontrol)实现多标签的动态添加,切换,关闭

利用js动态生成一个简单的商品详情页