I'd like to create a slide widget in dm-script, which can show a slider in the UI. The value of the slider position is integer. But I didn't find any useful information in the manual.
If it is useful to have a slider that is not actually contained within the dialog UI window, then a useable workaround is to use an appropriately formatted LinePlot display with a single ROI of unit width as slider, as illustrated in the following sample code:
interface DialogWithSliderInterface
{
void SetSliderFieldValue(Object self, Number newSliderValue);
}
class SliderFieldView
{
Number sliderSize;
Number parentDialogID;
ImageDocument sliderViewDoc;
ROI sliderROI;
Object Init(Object self, Object parentDialog, String valueLabel, Number initialValue, Number sliderSizeIn)
{
parentDialogID = parentDialog.ScriptObjectGetID();
sliderSize = sliderSizeIn;
// Create slider view as LinePlot with single ROI of fixed width 0
String sliderViewName = valueLabel + " slider view";
Image sliderImage := IntegerImage(sliderViewName, 1, 0, sliderSize + 1);
sliderImage = 1;
sliderViewDoc = NewImageDocument(sliderViewName);
sliderROI = NewROI();
sliderROI.ROISetRange(initialValue, initialValue + 1);
sliderROI.ROISetName(valueLabel);
sliderROI.ROISetLabel(" ");
sliderROI.ROISetResizable(0);
sliderROI.ROISetMoveable(1);
sliderROI.ROISetDeletable(0);
sliderROI.ROISetVolatile(0);
ImageDisplay sliderView = sliderViewDoc.ImageDocumentAddImageDisplay(sliderImage, 3);
sliderView.LinePlotImageDisplaySetDoAutoSurvey(0, 0);
sliderView.LinePlotImageDisplaySetContrastLimits(0, 1);
sliderView.ImageDisplaySetCaptionOn(0);
sliderView.LinePlotImageDisplaySetFrameOn(0);
sliderView.ImageDisplayAddROI(sliderROI);
sliderView.ImageDisplaySetROISelected(sliderROI, 1);
// Connect a listener to the ROI
ConnectObject(sliderROI.ROIGetID(), "changed", valueLabel, self, "DoSliderChange");
sliderViewDoc.ImageDocumentShowAtRect(450, 200, 500, 600);
sliderViewDoc.ImageDocumentClean();
return self;
}
void ShowView(Object self)
{
sliderViewDoc.ImageDocumentShow();
sliderViewDoc.ImageDocumentClean();
}
void SetSlider(Object self, Number newSliderValue)
{
Number sliderValue = newSliderValue.Clip(0, sliderSize).Round();
sliderROI.ROISetRange(sliderValue, sliderValue + 1);
self.ShowView();
}
void DoSliderChange(Object self, ROI changedROI)
{
if (changedROI.ROIGetID() == sliderROI.ROIGetID())
{
Number sliderValue, sliderEnd;
sliderROI.ROIGetRange(sliderValue, sliderEnd);
Object parentDialog = GetScriptObjectFromID(parentDialogID);
parentDialog.SetSliderFieldValue(sliderValue);
sliderViewDoc.ImageDocumentClean();
}
}
void DisconnectROI(Object self)
{
DisconnectObject(sliderROI.ROIGetID(), "changed", sliderROI.ROIGetName());
}
~SliderFieldView(Object self)
{
sliderViewDoc.ImageDocumentClose(0);
}
}
class DialogWithSliderField : UIFrame
{
TagGroup sliderFieldSpec;
Object sliderFieldView;
Object Init(Object self, Number sliderSize)
{
// Create integer field that will have slider view associated with it
Number initialValue = sliderSize / 2;
Number fieldWidth = Log10(sliderSize) + 2;
String fieldLabel = "Parameter 1";
TagGroup integerFieldGroupSpec = DLGCreateIntegerField(fieldLabel, sliderFieldSpec, initialValue, fieldWidth);
integerFieldGroupSpec.DLGExternalPadding(8, 8);
sliderFieldSpec.DLGChangedMethod("DoSliderFieldChange");
// Create and attach slider view
sliderFieldView = Alloc(SliderFieldView).Init(self, fieldLabel, initialValue, sliderSize);
// Create the dialog specification, adding the integer field group
String dialogTitle = "Slider Field UI";
TagGroup dialogSpec = DLGCreateDialog(dialogTitle);
TagGroup dialogItems = dialogSpec.DLGGetItems();
dialogItems.DLGAddElement(integerFieldGroupSpec);
// Add a "Show Slider View" button
TagGroup showSliderButtonSpec = DLGCreatePushButton("Show Slider View", "ShowSliderView");
dialogItems.DLGAddElement(showSliderButtonSpec);
// Create and display dialog
self = self.super.Init(dialogSpec);
self.Display(dialogTitle);
DocumentWindow viewWindow = self.GetFrameWindow();
viewWindow.WindowSetFramePosition(200, 200);
return self;
}
void SetSliderFieldValue(Object self, Number newSliderValue)
{
Number sliderValue = sliderFieldSpec.DLGGetValue();
if (newSliderValue != sliderValue)
sliderFieldSpec.DLGValue(newSliderValue);
}
void DoSliderFieldChange(Object self, TagGroup fieldSpec)
{
sliderFieldView.SetSlider(fieldSpec.DLGGetValue());
}
void ShowSliderView(Object self)
{
sliderFieldView.ShowView();
}
Number AboutToCloseDocument(Object self, Number verify)
{
sliderFieldView.DisconnectROI();
return 0;
}
}
void main()
{
Object sliderDialog = Alloc(DialogWithSliderField).Init(100);
}
main();
There are quite a few details and subtleties involved in this example. Although it may appear overly complex, it probably represents a minimal useful implementation of this idea since one normally would like the slider setting to be linked to a field in a standard dialog UI. It's the linkage between the DialogWithSliderField
and SliderFieldView
objects that adds most of the complexity, as well as some details related to managing the slider display window and its ROI listener. The most significant issues are as follows:
It is important to store only the ID of the parent dialog object within the SliderFieldView
object. If one were to save the actual object reference, this would set up a reference cycle between the two objects and they would never be deallocated, even if one manually closes both windows.
Note the explicit call to DisconnectROI
in the AboutToCloseDocument
method of DialogWithSliderField
. This is important to break the reference cycle between the SliderFieldView
object and its connected ROI listener. Without this, the view object would never get deallocated, even when its window is manually closed.
In order for a newly entered value in the dialog window to take effect, it is necessary to press the tab key or to click somewhere in the DM workspace. For some reason, simply hitting enter does not trigger the change event for the numeric field.
DM treats dialog UI windows and image display windows completely differently, so the former 'floats' above all the workspaces, while the slider view window will be attached to the current workspace. This also means that the coordinate systems used to position the dialog window and the slider view window do not line up with each other.
It turns out that setting the width of the slider ROI to 0 does not work because DM doesn't seem to like zero-width ROIs on line plots. Even if the ROI is set as non-resizable, interacting with a zero-width ROI inevitably leads to it having a non-zero width. Setting the width to a single channel seems to work just fine.
Note that the slider view ImageDocument is not deallocated when it is closed in the DM UI, because two live objects continue to reference it (its host SliderFieldView
object and its ROI listener). Thus a button has been added to the dialog UI to make the slider view ImageDocument visible again if it has been closed (or hidden or minimized).
There are further subtleties, but those are left as exercises and puzzles for serious readers of this answer!