Destroy qt.QPushButton

I have successfully created a module based on the SimpleFilters module. It works fine if I have one “custom” filter. By this, I mean that there is only one element in the
combo box.

When I add more than one, I need to handle the switching between the different layouts. Emptying the layout is done by calling the destroy function. I implemented the same procedure in a class mimicking the capabilities of the FilterParameters class in a… bit duck-taped way.

It worked fine - until I tried to remove a qt.QPushButton type widget. Doing this completely crashes the Slicer, exiting with no trace of the error. Adding ‘try-catch’ can not catch the error. If I execute Slicer from a terminal, all I see is

Switch to module:  "Welcome"
Switch to module:  "CustomFilters"
Switch to module:  ""
Switch to module:  ""
error: [/local_data/Programs/Slicer-5.2.2-linux-amd64/bin/SlicerApp-real] exit abnormally - Report the problem.

Qt does not really have a good solution for completely deleting widgets. You may try to do it by removing it from its layout then calling deleteLater(), but I recommend simply hiding it: button.visible = False.

1 Like

The fun part is that I just tried to simulate a similar scenario with a dummy “filter” whose layout contains just a single button, and the crash did not occur. Then, when I started to add more elements, the error occurred in the same way.

This will not cause a crash:

class DummyFilter(CustomFilter):
  filter_name = "Dummy Filter"
  short_description = "Just a dummy"
  tooltip = "Just a dummy"

  def __init__(self):
    super().__init__()
    self.filter_name = DummyFilter.filter_name
    self.short_description = DummyFilter.short_description
    self.tooltip = DummyFilter.tooltip   
    
  def createUI(self, parent):
    parametersFormLayout = super().createUI(parent)
    UI = CustomFilterUI(parent = parametersFormLayout)

    dummy_btn = qt.QPushButton("Dummy btn")
    UI.widgets.append(dummy_btn)
    
    UI.addWidgetWithToolTip(dummy_btn,{"tip":"Dummy btn clicked"})
    dummy_btn.connect('clicked(bool)', self.on_dummy_btn)
    
    self.UI = UI
    return UI
    
  def on_dummy_btn(self):
      print("Dummy btn clicked")
 
  def execute(self, ui = None):
    super().execute(ui = ui)

While this will:

class DummyFilter(CustomFilter):
  filter_name = "Dummy Filter"
  short_description = "Just a dummy"
  tooltip = "Just a dummy"

  def __init__(self):
    super().__init__()
    self.filter_name = DummyFilter.filter_name
    self.short_description = DummyFilter.short_description
    self.tooltip = DummyFilter.tooltip
    
  def createUI(self, parent):
    parametersFormLayout = super().createUI(parent)
    UI = CustomFilterUI(parent = parametersFormLayout)

    dummy_btn = qt.QPushButton("Dummy btn")
    UI.widgets.append(dummy_btn)
    
    UI.addWidgetWithToolTip(dummy_btn,{"tip":"Dummy btn clicked"})
    dummy_btn.connect('clicked(bool)', self.on_dummy_btn)
    
    # clip widget    
    UI.clip_widget = ctk.ctkRangeWidget()
    UI.widgets.append(UI.clip_widget)
    
    UI.addWidgetWithToolTipAndLabel(UI.clip_widget,{"tip":"Values outside the 'clip range' will be set to the given 'clip range'",
                      "label":"Input clip range"})
    UI.clip_widget.enabled = False
    
    UI.clip_widget.connect("valuesChanged(double,double)",
                                lambda min,max, widget=UI.clip_widget, name = 'clip': self.onRangeChanged(name,widget,min,max))
    
    self.UI = UI
    return UI
  
  
    
  def on_dummy_btn(self):
      print("Dummy btn clicked")
      
  def onRangeChanged(self, name, widget, min_val, max_val):
    if name == "clip":
      print(f"clip: {min_val} - {max_val}")
 
  def execute(self, ui = None):
    super().execute(ui = ui)

Then I noticed, that in my duck-taped code, I forgot to implement storing of the connections made on-flight. Oddly, this will not result in a crush - if there is only one widget on the layout.

So… to make the previously mentioned destroy function work properly, one should make sure that every connection is stored in the widgetConnections list.