0

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.

Note: Before proceeding to the remaining code: This is not a typical code snippet for a small program. This is used in a huge application containing multiple modules. So, you just can't you can use it within a class and create a method to handle the connect() and access a QComboBox using self.secpro_QComboBox. You can't just do that, please take a look at the project files mainly ui_template.py#L631.

After long research, I created a stub that shows the required functionality. Here is the following code snippet for the stub.

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

class Window(QMainWindow):

  # other code

  def setupUi(self, MainWindow, main, folder):

  # other code

  # 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 QComboBoxes into the table Widget
  table_widget.setCellWidget(0,0,combo_box_secpro)
  table_widget.setCellWidget(0,1,combo_box_connloc)

# ---------------
# More code below
# ---------------

Output: 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 within same class & method,
# 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 codes below
# ---------------------

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 set up the project within you system, make sure the following things;
    • Use Ubuntu 20.04 LTS version through your VM.
    • Follow the README.md instruction to install the project environment.
    • And it's recommended to use Pycharm IDE.

I have spent a lot of time searching for some answers but, could find one. Is there a 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 assigning the connecting method to link 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(
    connloc_QComboBox.clear() or
    connloc_QComboBox.addItems(lambda:
      selectable_options['Connection Location']['Angle' if secpro_QComboBox.currentText() in ['Angle', 'Star Angles', 'Back to Back Angles'] else 'Channel']
     )
  )

Output: Output Remains same...

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

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Shamith N
  • 1
  • 2
  • Lambdas are evaluated at runtime, `combo_box_connection_location` will always refer to the last occurrence of the for loop. I would strongly discourage doing such a complex implementation with all those nested for loops, you should consider using explicit functions to create those widgets, so that connections (and variables) will be enclosed in their scope, and your code will also be much more readable. Your last attempt is wrong because connections expect callables, but you're actually calling those functions, and `None` (their returned value) is an invalid argument for `connect()`. – musicamante Jul 18 '23 at 16:06
  • Thank you, @musicamante. I was able to solve the issue. I took your advice and made an explicit function (or) you can say a method to do the `connect()` the dependent `QComboBox` functionality to all the instances... All I had to do was to create a method that would take the data dictionary as a parameter and returns a pair of Dependent `QComboBox`es. Then, I use those `QComboBox`es to place them within the `QTableWidget` Sources: commit [a3ac7e8](https://github.com/osdag-admin/Osdag/pull/333/commits/a3ac7e834f599c5c2fda58916566d102cf41c921) – Shamith N Jul 21 '23 at 12:52

0 Answers0