Custom Widgets for Parameter Packs
Sometimes a parameter pack is used multiple times in a parameter node wrapper. Take the following example:
from slicer.parameterNodeWrapper import *
@parameterPack
class Point:
x: float
y: float
@parameterPack
class BoundingBox:
# can nest parameterPacks
topLeft: Point
bottomRight: Point
@parameterNodeWrapper
class ParameterNodeType:
box1: BoundingBox
box2: BoundingBox
in this example it could be useful to have a PointWidget
that directly represented the Point
class. The widget could be reused multiple times (even across multiple files) to ensure a consistent experience for inputting points.
Here is an example on how that could be done. The PointWidget
is being created in code here, but this could also apply to a custom widget created in a .ui
file.
class PointWidget(qt.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setLayout(qt.QHBoxLayout(self))
self.xWidget = qt.QDoubleSpinBox()
# set the parameterPack parameter this widget corresponds to
self.xWidget.setProperty("SlicerPackParameterName", "x")
self.yWidget = qt.QDoubleSpinBox()
# set the parameterPack parameter this widget corresponds to
self.yWidget.setProperty("SlicerPackParameterName", "y")
self.layout().addWidget(self.xWidget)
self.layout().addWidget(self.yWidget)
topLeft1Widget = PointWidget()
topLeft2Widget = PointWidget()
param = ParameterNodeType(slicer.mrmlScene.AddNewNodeByClass("vtkMRMLScriptedModuleNode"))
param.connectParametersToGui({
"box1.topLeft": topLeft1Widget,
"box2.topLeft": topLeft2Widget,
})
Setting the "SlicerPackParameterName"
property on a child widget of the custom widget is enough for the GUI connection infrastructure to make the binding.
The infrastructure uses parent-child relationships to work through nested values.
Here is an example of a BoundingBoxWidget
that is completely separate from PointWidget
and uses parent-child relationships to match the nesting structure of the parameter packs.
class BoundingBoxWidget(qt.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setLayout(qt.QVBoxLayout(self))
# Making a widget for the topLeft point. This particular QWidget doesn't
# do anything special for the UI, but it parents the x and y widgets for the
# top left points
topLeftWidget = qt.QWidget()
self.layout.addWidget(topLeftWidget)
topLeftWidget.setProperty("SlicerPackParameterName", "topLeft")
topLeftWidget.setLayout(qt.QHBoxLayout())
topLeftXWidget = qt.QDoubleSpinBox()
topLeftXWidget.setProperty("SlicerPackParameterName", "x")
topLeftYWidget = qt.QDoubleSpinBox()
topLeftYWidget.setProperty("SlicerPackParameterName", "y")
topLeftWidget.layout().addWidget(topLeftXWidget)
topLeftWidget.layout().addWidget(topLeftYWidget)
# Making a widget for the bottomRight point. This particular QWidget doesn't
# do anything special for the UI, but it parents the x and y widgets for the
# top left points
bottomRightWidget = qt.QWidget()
self.layout.addWidget(bottomRightWidget)
bottomRightWidget.setProperty("SlicerPackParameterName", "bottomRight")
bottomRightWidget.setLayout(qt.QHBoxLayout())
bottomRightXWidget = qt.QDoubleSpinBox()
bottomRightXWidget.setProperty("SlicerPackParameterName", "x")
bottomRightYWidget = qt.QDoubleSpinBox()
bottomRightYWidget.setProperty("SlicerPackParameterName", "y")
bottomRightWidget.layout().addWidget(bottomRightXWidget)
bottomRightWidget.layout().addWidget(bottomRightYWidget)