0

I am trying to have a series of QTableView created at runtime and added to newly created pages of a multipage QTabWidget.

All seems to go fine, but the QTableView don't show up. The QTabWidget gets zeroed (reset to no pages) and refurbished (...) flawlessly (at least it looks like so) depending on the selection of a combobox (and the dictionaries therein related).

I am also using a delegate callback to include a column of checkboxes to the QTableView (thanks to https://stackoverflow.com/a/50314085/7710452), which works fine stand alone.

Here is the code. Main Window

EDIT as recommended by eyllanesc, here is the standalone module (jump to the end of the post for details on the part I think is problematic):

"""
    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_())

and here the external to add the checkboxes column:

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)

the rough setup of the UI in qtdesigner is like this:

what I am achieving so far is this:

The 1st picture shows the layout in QTDesigner, the 2nd the result (emtpy tabs) when avoiding the crashing.

the QTabWidget has no problems in zeroing, or scale up, back to as many tab as I need, it's just that I have no clue on how to show the QTabview. My approach was to try to embed the QTabView in the tabpage passing it as parameter to the line creating the new QTabView.

Since I am using rather convoluted dictionaries, calling an XML parser to fill them up, not to mention the config files, I know even this version of my script is hardly reproduceable/runnable.

If someone had the patience of focusing on the update_ControllersTab method though, and tell me what I am doing wrong handling the QWidgets, it'd be great.

Again the basic idea is to clear the QTabWidget any time the user selects a different controller (combo box on the left):

    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

count how many tabs (pages) and hence embedded TabViews I need with the new controllers selected. and then for each tab needed:

create a new tab (page)

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

create a new QTabView using a model:

    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)

populate the TableView with data, and then finally add the tab widget (with the suppposedly embedded QTableView to the QTabWidget (the i argument is a string from my dbases Names:

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

This method is called also by the __init__ to initialize and apparently all goes error free, until the last 'init' statement:

`self.show()`

at which point the app crashes with:

Process finished with exit code 1073741845

on the other hand, if here instead of trying to embed the QTableView:

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

I omit the parameter, that is:

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

the app doesn't crash anymore, but of course no QtableViews are shown, only empty tabpages: enter image description here

Cœur
  • 37,241
  • 25
  • 195
  • 267
Caligola
  • 59
  • 9

2 Answers2

0

As stupid as this may sound the problem is in... the delegate class that creates the checkboxes in the first column (see https://stackoverflow.com/a/50314085/7710452)

I commented out those two lines:

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

and... bingo!

the CheckBoxDelegate works fine in the example shown in the post (a single QTableView form). I also tinkered around adding columns and rows, and moving the checkbox column back and forth with no problems. In that standalone. But as soon as I add the class and set the delegate, i am back at square zero, app crashing with:

Process finished with exit code 1073741845

so I am left with this problem now. Thnx to whomever read this.

Caligola
  • 59
  • 9
  • the problem with the checkbox delegate was that I wasn't initializing the column where the checkbox is drawn to any value, it has to be initialized to either zero (unchecked) or non-zero (typically 1) checked. Leaving that empty the `index.data()` parameter of the delegate was always None: which caused the error. – Caligola Apr 30 '21 at 10:45
0

Problem solved, see comment to post above.

Caligola
  • 59
  • 9