DEV Community

Shamith Nakka
Shamith Nakka

Posted on • Edited on

Facing issues while creating multiple instances of Dependent QComboBox

I'm working on the GUI part of the input dock on the truss_connection_bolted module on a project called Osdag and the whole GUI part is written using the PyQt5 module. My task is to finish the connecting members attribute on the Input dock which happens to be a set of QComboBox widgets on a single QTableWidget. I have successfully written most of the part by there is one section of QComboBox in the Table widget that depends on another QComboBox.

After long research, I was able to create a stud that shows the required functionality. Here is the following code snippet for the stub.

# ---------------
# More Code above
# ---------------

# Table Widget Creation
table_widget = QtWidgets.QTableWidget(self.dockWidgetContents)
table_widget.setObjectName(option[0])
table_widget.setRowCount(8)
table_widget.setColumnCount(6)

# Attributes dictionary 
selectable_options = {
  'Section profile': ['Angle', 'Star Angles', 'Back to Back Angles', 'Channel','Back to Back Channels'],
  'Connection Location': {
    'Angle': ['Long leg connected', 'Short leg connected'],
    'Channel': ['Web-connected']
  },
  'Section Size': ['20 x 20 x 3', '20 x 20 x 4', '25 x 25 x 3', '25 x 25 x 4', ...],
  'Material': [ 'E165', 'E250 (Fe 410W) A', 'E250 (Fe 410W) B', 'E250 (Fe 410W) C', ....]
}

# Setting Head Labels to the table widget
table_widget.setHorizontalHeaderLabels(
  list(selectable_options.keys()) + ["Angle with x-axis (Degrees)", "Factored Load (KN)"]
)

# Creating two indivdual QComboBox for testing out the dependency functionality
combo_box_secpro = QtWidgets.QComboBox()
combo_box_connloc = QtWidgets.QComboBox()

# Adding values to the "Section Profile" QComboBox
for item in selectable_options['Section profile']:
  value = None
  if item in ['Angle', 'Star Angles', 'Back to Back Angles']:
    value = selectable_options['Connection Location']['Angle']
  elif item in ['Channel', 'Back to Back Channels']:
    value = selectable_options['Connection Location']['Channel']
combo_box_secpro.addItem(item, value)

# Connecting "Section Profile" QComboBox to "Connection Location" QComboBox
combo_box_secpro.activated.connect(
  lambda: combo_box_connloc.clear() or combo_box_connloc.addItems(
            selectable_options['Connection Location'][ 'Angle' if combo_box_secpro.currentText() in ['Angle', 'Star Angles', 'Back to Back Angles'] else 'Channel']
            )
  )

# Placing both of those the QComboBoxes into table Widget
table_widget.setCellWidget(0,0,combo_box_secpro)
table_widget.setCellWidget(0,1,combo_box_connloc)

# ---------------
# More code below
# ---------------
Enter fullscreen mode Exit fullscreen mode

Output of stub code snippet

The above code snippet worked fine but, when I tried to loop the same logic... It's not working as Intended... The code snippet for the looping logic:

# ---------------------
# More other code above
# ---------------------

# Note that this is just a looped version of the above logic,
# so all the other declarations of the dictionary mentioned above are present here..

combo_box_dictionary = dict()  # Just in case, if we need the combo_box object for each option

for col, (key, value) in enumerate(selectable_options.items()):

  # Skipping 'Connection location' as I have handled it in 'Section profile'
  if key == 'Connection Location':
    continue

  # Creating 'Connection location' QComboBox that depend on 'Section profile' QComboBox
  elif key == 'Section profile':

    combo_box_section_profile_list = []
    combo_box_connection_location_list = []

    # Looping for 8 times for each row in table widget
    for row in range(8):

      # Creating QComboBox Object for both 'Section profile' & 'Connection location'
      combo_box_section_profile = QtWidgets.QComboBox()
      combo_box_connection_location = QtWidgets.QComboBox()

      # Adding items into 'Section profile' QComboBox
      for item in selectable_options['Section profile']:
        value = None
        if item in ['Angle', 'Star Angles', 'Back to Back Angles']:
          value = selectable_options['Connection Location']['Angle']
        elif item in ['Channel', 'Back to Back Channels']:
          value = selectable_options['Connection Location']['Channel']
        combo_box_section_profile.addItem(item, value)

      # Connecting 'Section profile' dependent functionality to 'Connection Location'
      combo_box_section_profile.activated.connect(
        lambda: combo_box_connection_location.clear() or combo_box_connection_location.addItems(
          selectable_options['Connection Location']['Angle' if combo_box_section_profile.currentText() in ['Angle', 'Star Angles', 'Back to Back Angles'] else 'Channel']
                 )
         )

      # Added Default Items into 'Connection Location'
      combo_box_connection_location.addItems(selectable_options['Connection Location']['Angle'])

      # Setting the QComboBoxes into their respective places
      table_widget.setCellWidget(row, col, combo_box_section_profile)
      table_widget.setCellWidget(row, col+1, combo_box_connection_location)

      # Storing their Object References per each row
      combo_box_section_profile_list.append(combo_box_section_profile)
      combo_box_connection_location_list.append(combo_box_connection_location)

    # Collectively storing Object References for both of them for all the rows
    combo_box_dictionary['Section profile'] = combo_box_section_profile_list
    combo_box_dictionary['Connection Location'] = combo_box_connection_location_list

  else:
    # Logic for adding simple QComboBoxes to the table widget
    combo_box_list = []
    for row in range(8):
      combo_box = QtWidgets.QComboBox()
      combo_box.addItems(value)
      table_widget.setCellWidget(row, col, combo_box)
      combo_box_list.append(combo_box)
      combo_box_dictionary[key] = combo_box_list

# ---------------------
# More other code below
# ---------------------
Enter fullscreen mode Exit fullscreen mode

Output:
The main problem

Note:

  • If you want to see the whole code you can find it within my GitHub in truss-connection-bolted-gui.
  • If you wish to setup the project within you system, make sure the following things;
    • Use Ubuntu 20.04 LTS version through your VM.
    • Follow the README.md instrustion to install the project environment.
    • And It's recommended to use Pycharm IDE.

I have spent a lot of time search for some answers but, could find one. Is there way to fix this problem ?

Other than my original implementation, I have tried searching for an alternative to the connecting method but couldn't find one...

Then I tried different approaches like storing the object references and then assigning the connecting method to link up the required functionality to the respective QComboBoxes. But, That didn't work out either...

for col, (key, value) in enumerate(selectable_options.items()):
  combo_box_list = []
  for row in range(8):
    combo_box = QtWidgets.QComboBox()
    if key == 'Connection Location':
      value = selectable_options['Connection Location']['Angle']
    combo_box.addItems(value)
    table_widget.setCellWidget(row, col, combo_box)
    combo_box_list.append(combo_box)
  combo_box_dictionary[key] = combo_box_list

for index, secpro_QComboBox in enumerate(combo_box_dictionary['Section profile']):
  connloc_QComboBox = combo_box_dictionary['Connection Location'][index]
  secpro_QComboBox.activated.connect(lambda:
    connloc_QComboBox.clear() or
    connloc_QComboBox.addItems(
      selectable_options['Connection Location']['Angle' if secpro_QComboBox.currentText() in ['Angle', 'Star Angles', 'Back to Back Angles'] else 'Channel']
     )
  )
Enter fullscreen mode Exit fullscreen mode

Output:
The main problem

I don't know what to do, it's giving me the same error. Can anyone figure out what's going on ?

Top comments (1)

Collapse
 
iamwatchdogs profile image
Shamith Nakka

Even though, My question is marked as duplicate and has been closed *( I've applied for review that even though it was a similar case, it's no way near my case )*I have resolved the issue with the advice given by @musicamante in Stackoverflow. Even though it was abstract, I got the idea to implement the same logic differently.

Stackoverflow Comment

In the comment, he suggested that I use an explicit function instead of complex code for creating the dependent QComboBox. So, I have taken the same logic which I initially created and placed it into a method and used it within the current method where the whole logic was supposed to be executed. This method only takes the required data dictionary and returns a pair of dependent QComboBoxes.

Method implementation:

def create_dependent_QComboBox(self, selectable_options):
  combo_box_section_profile = QtWidgets.QComboBox()
  combo_box_connection_location = QtWidgets.QComboBox()

  for item in selectable_options['Section profile']:
    value = None
    if item in ['Angles', 'Star Angles', 'Back to Back Angles']:
      value = selectable_options['Connection Location']['Angle']
    elif item in ['Channels', 'Back to Back Channels']:
      value = selectable_options['Connection Location']['Channel']
    else:
      value = ['Invalid Option']
  combo_box_section_profile.addItem(item, value)

  combo_box_section_profile.currentIndexChanged.connect(
    lambda: combo_box_connection_location.clear() or
                  combo_box_connection_location.addItems(
                      combo_box_section_profile.currentData()
                   )
    )

  combo_box_connection_location.addItems(
      selectable_options['Connection Location']['Angle']
  )

  return combo_box_section_profile, combo_box_connection_location

Enter fullscreen mode Exit fullscreen mode

Updated logic:

table_widget = QtWidgets.QTableWidget(self.dockWidgetContents)
table_widget.setObjectName(option[0])
table_widget.setRowCount(8)
table_widget.setColumnCount(6)

combo_box_dictionary = dict()  # Just in case, if we need the combo_box object for each option

table_widget.setHorizontalHeaderLabels(list(option[3].keys()))

for col, (key, value) in enumerate(option[3].items()):
  if key == 'Connection Location' or value is None:
    continue
  elif key == 'Section profile':
    combo_box_section_profile_list = []
    combo_box_connection_location_list = []
    for row in range(8):
      combo_box_section_profile, combo_box_connection_location = self.create_dependent_QComboBox(option[3])
      table_widget.setCellWidget(row, col, combo_box_section_profile)
      table_widget.setCellWidget(row, col+1, combo_box_connection_location)

      combo_box_section_profile_list.append(combo_box_section_profile)
      combo_box_connection_location_list.append(combo_box_connection_location)

      combo_box_dictionary['Section profile'] = combo_box_section_profile_list
      combo_box_dictionary['Connection Location'] = combo_box_connection_location_list

  else:
    combo_box_list = []
    for row in range(8):
      combo_box = QtWidgets.QComboBox()
      for item in value:
        combo_box.addItem(item, value)
        table_widget.setCellWidget(row, col, combo_box)
        combo_box_list.append(combo_box)
      combo_box_dictionary[key] = combo_box_list

table_widget.resizeRowsToContents()
table_widget.resizeColumnsToContents()
Enter fullscreen mode Exit fullscreen mode

Source link:

Pull Request: PR#333
Commit: a3ac7e8