In previous posts I showed how we can create custom Matlab app toolstrips using simple controls such as buttons and checkboxes. Today I will show how we can incorporate more complex controls into our toolstrip: button groups, edit-boxes, spinners, sliders etc.
Toolstrips can be a bit complex to develop so I’m proceeding slowly, with each post in the miniseries building on the previous posts. I encourage you to review the earlier posts in the Toolstrip miniseries before reading this post.
The first place to search for potential toostrip components/controls is in Matlab’s built-in toolstrip demos. The
showcaseToolGroup
demo displays a large selection of generic components grouped by function. These controls’ callbacks do little less than simply output a text message in the Matlab console. On the other hand, the showcaseMPCDesigner
demo shows a working demo with controls that interact with some docked figures and their plot axes. The combination of these demos should provide plenty of ideas for your own toolstrip implementation. Their m-file source code is available in the %matlabroot%/toolbox/matlab/toolstrip/+matlab/+ui/+internal/+desktop/ folder. To see the available toolstrip controls in action and how they could be integrated, refer to the source-code of these two demos.All toolstrip controls are defined by classes in the %matlabroot%/toolbox/matlab/toolstrip/+matlab/+ui/+internal/+toolstrip/ folder and use the
matlab.ui.internal.toolstrip
package prefix, for example:
% Alternative 1: hButton = matlab.ui.internal.toolstrip.Button; % Alternative 2: import matlab.ui.internal.toolstrip.* hButton = Button; |
For the remainder of today’s post it is assumed that you are using one of these two alternatives whenever you access any of the toolstrip classes.
Top-level toolstrip controls
Control | Description | Important properties | Callbacks | Events |
---|---|---|---|---|
EmptyControl |
Placeholder (filler) in container column | (none) | (none) | (none) |
Label |
Simple text label (no action) | Icon, Text (string) | (none) | (none) |
Button |
Push-button | Icon, Text (string) | ButtonPushedFcn | ButtonPushed |
ToggleButton |
Toggle (on/off) button | Icon, Text (string), Value (logical true/false), ButtonGroup (a ButtonGroup object) |
ValueChangedFcn | ValueChanged |
RadioButton |
Radio-button (on/off) | Text (string), Value (logical true/false), ButtonGroup (a ButtonGroup object) |
ValueChangedFcn | ValueChanged |
CheckBox |
Check-box (on/off) | Text (string), Value (logical true/false) | ValueChangedFcn | ValueChanged |
EditField |
Single-line editbox | Value (string) | ValueChangedFcn | ValueChanged, FocusGained, FocusLost |
TextArea |
Multi-line editbox | Value (string) | ValueChangedFcn | ValueChanged, FocusGained, FocusLost |
Spinner |
A numerical spinner control of values between min,max | Limits ([min,max]), StepSize (integer), NumberFormat (‘integer’ or ‘double’), DecimalFormat (string), Value (numeric) | ValueChangedFcn | ValueChanged, ValueChanging |
Slider |
A horizontal slider of values between min,max | Limits ([min,max]), Labels (cell-array), Ticks (integer), UseSmallFont (logical true/false, R2018b onward), ShowButton (logical true/false, undocumented), Steps (integer, undocumented), Value (numeric) | ValueChangedFcn | ValueChanged, ValueChanging |
ListBox |
List-box selector with multiple items | Items (cell-array), SelectedIndex (integer), MultiSelect (logical true/false), Value (cell-array of strings) | ValueChangedFcn | ValueChanged |
DropDown |
Single-selection drop-down (combo-box) selector | Items (cell-array), SelectedIndex (integer), Editable (logical true/false), Value (string) | ValueChangedFcn | ValueChanged |
DropDownButton |
Button that has an associated drop-down selector | Icon, Text (string), Popup (a PopupList object) |
DynamicPopupFcn | (none) |
SplitButton |
Split button: main clickable part next to a drop-down selector | Icon, Text (string), Popup (a PopupList object) |
ButtonPushedFcn, DynamicPopupFcn | ButtonPushed, DropDownPerformed (undocumented) |
Gallery |
A gallery of selectable options, displayed in-panel | MinColumnCount (integer), MaxColumnCount (integer), Popup (a GalleryPopup object), TextOverlay (string) |
(none) | (none) |
DropDownGalleryButton |
A gallery of selectable options, displayed as a drop-down | MinColumnCount (integer), MaxColumnCount (integer), Popup (a GalleryPopup object), TextOverlay (string) |
(none) | (none) |
In addition to the control properties listed in the table above, all toolstrip controls share some common properties:
- Description – a string that is shown in a tooltip when you hover the mouse over the control
- Enabled – a logical value (default: true) that controls whether we can interact with the control. A disabled control is typically grayed-over. Note that the value is a logical true/false, not ‘on’/’off’
- Tag – a string that can be used to uniquely identify/locate the control via their container’s find(tag) and findAll(tag) methods. Can contain spaces and special symbols – does not need to be a valid Matlab identifier
- Children – contains a list of sub-component (if any); useful with complex controls
- Parent – the handle of the container that contains the control
- Type – the type of control, typically its class-name
- Mnemonic – an undocumented string property, currently unused (?)
- Shortcut – an undocumented string property, currently unused (?)
The EmptyControl
, Button
, ToggleButton
and CheckBox
controls were discussed in an earlier post of this miniseries. The bottom 6 selection controls (ListBox
, DropDown
, DropDownButton
, SplitButton
, Gallery
and DropDownGalleryButton
) will be discussed in the next post. The rest of the controls are described below.
Button groups
A ButtonGroup binds several CheckBox
and ToggleButton
components such that only one of them is selected (pressed) at any point in time. For example:
hSection = hTab.addSection('Radio-buttons'); hColumn = hSection.addColumn(); % Grouped RadioButton controls hButtonGroup = ButtonGroup; hRadio = RadioButton(hButtonGroup, 'Option choice #1'); hRadio.ValueChangedFcn = @ValueChangedCallback; hColumn.add(hRadio); hRadio = RadioButton(hButtonGroup, 'Option choice #2'); hRadio.ValueChangedFcn = @ValueChangedCallback; hRadio.Value = true; hColumn.add(hRadio); |
ButtonGroup
object does not have a SelectionChangedFcn callback property (or corresponding event). Instead, we need to set the ValueChangedFcn callback property (or listen to the ValueChanged event) separately for each individual control. This is really a shame – I think it would make good design sense to have a SelectionChangedFcn callback at the ButtonGroup
level, as we do for uibuttongroup (in addition to the individual control callbacks).Also note that the internal documentation of
ButtonGroup
has an error – it provides an example usage with RadioButton
that has its constructor inputs switched: the correct constructor is RadioButton(hButtonGroup,labelStr)
. On the other hand, for ToggleButton
, the hButtonGroup input is the [optional] 3rd input arg of the constructor: ToggleButton(labelStr,Icon,hButtonGroup)
. I think that it would make much more sense for the RadioButton
constructor to follow the documentation and the style of ToggleButton
and make the hButtonGroup input the last (2nd, optional) input arg, rather than the 1st. In other words, it would make more sense for RadioButton(labelStr,hButtonGroup), but unfortunately this is currently not the case.
Label, EditField and TextArea
A Label
control is a simple non-clickable text label with an optional Icon, whose text is controlled via the Text property. The label’s alignment is controlled by the containing column’s HorizontalAlignment property.
An EditField
is a single-line edit-box. Its string contents can be fetched/updated via the Value property, and when the user updates the edit-box contents the ValueChangedFcn callback is invoked (upon each modification of the string, i.e. every key-click). This is a pretty simple control actually.
The EditField
control has a hidden (undocumentented) settable property called PlaceholderText, which presumably aught to display a gray initial prompt within the editbox. However, as far as I could see this property has no effect (perhaps, as the name implies, it is a place-holder for a future functionality…).
A TextArea
is another edit-box control, but enables entering multiple lines of text, unlike EditField
which is a single-line edit-box. TextArea
too is a very simple control, having a settable Value string property and a ValueChangedFcn callback. Whereas EditField
controls, being single-line, would typically be included in 2- or 3-element toolstrip columns, the TextArea
would typically be placed in a single-element column, so that it would span the entire column height.
A peculiarity of toolstrip columns is that unless you specify their Width property, the internal controls are displayed with a minimal width (the width is only controllable at the column level, not the control-level). This is especially important with EditField
and TextArea
controls, which are often empty by default, causing their assigned width to be minimal (only a few pixels). This is corrected by setting their containing column’s Width:
% EditField controls column1 = hSection.addColumn('HorizontalAlignment','right'); column1.add(Label('Yaba:')) column1.add(Label('Daba doo:')) column2 = hSection.addColumn('Width',70); column2.add(EditField); column2.add(EditField('Initial text')); % TextArea control column3 = hSection.addColumn('Width',90); hEdit = TextArea; hEdit.ValueChangedFcn = @ValueChangedCallback; column3.add(hEdit); |
Spinner
Spinner
is a single-line numeric editbox that has an attached side-widget where you can increase/decrease the editbox value by a specified amount, subject to predefined min/max values. If you try to enter an illegal value, Matlab will beep and the editbox will revert to its last acceptable value. You can only specify a NumberFormat of ‘integer’ or ‘double’ (default: ‘integer’) and a DecimalFormat which is a string composed of the number of sub-decimal digits to display and the format (‘e’ or ‘f’). For example, DecimalFormat=’4f’ will display 4 digits after the decimal in floating-point format (‘e’ means engineering format). Here is a short usage example (notice the different ways that we can set the callbacks):
hColumn = hSection.addColumn('Width',100); % Integer spinner (-100 : 10 : 100) hSpinner = Spinner([-100 100], 0); % [min,max], initialValue hSpinner.Description = 'this is a tooltip description'; hSpinner.StepSize = 10; hSpinner.ValueChangedFcn = @ValueChangedCallback; hColumn.add(hSpinner); % Floating-point spinner (-10 : 0.0001 : 10) hSpinner = Spinner([-10 10], pi); % [min,max], initialValue hSpinner.NumberFormat = 'double'; hSpinner.DecimalFormat = '4f'; hSpinner.StepSize = 1e-4; addlistener(hSpinner,'ValueChanged', @ValueChangedCallback); addlistener(hSpinner,'ValueChanging',@ValueChangingCallback); hColumn.add(hSpinner); |
Slider
Slider
is a horizontal ruler on which you can move a knob from the left (min Value) to the right (max Value). The ticks and labels are optional and customizable. Here is a simple example showing a plain slider (values between 0-100, initial value 70, ticks every 5, labels every 20, step size 1), followed by a custom slider (notice again the different ways that we can set the callbacks):
hColumn = hSection.addColumn('Width',200); hSlider = Slider([0 100], 70); % [min,max], initialValue hSlider.Description = 'this is a tooltip'; tickVals = 0 : 20 : 100; hSlider.Labels = [compose('%d',tickVals); num2cell(tickVals)]'; % {'0',0; '20',20; ...} hSlider.Ticks = 21; % =numel(0:5:100) hSlider.ValueChangedFcn = @ValueChangedCallback; hColumn.add(hSlider); hSlider = Slider([0 100], 40); % [min,max], initialValue hSlider.Labels = {'Stop' 0; 'Slow' 20; 'Fast' 50; 'Too fast' 75; 'Crash!' 100}; try hSlider.UseSmallFont = true; catch, end % UseSmallFont was only added in R2018b hSlider.Ticks = 11; % =numel(0:10:100) addlistener(hSlider,'ValueChanged', @ValueChangedCallback); addlistener(hSlider,'ValueChanging',@ValueChangingCallback); hColumn.add(hSlider); |
Toolstrip miniseries roadmap
The next post will discuss complex selection components, including listbox, drop-down, split-button, and gallery.
Following that, I plan to discuss toolstrip collapsibility, the ToolPack framework, docking layout, DataBrowser panel, QAB (Quick Access Bar), underlying Java controls, and adding toolstrips to figures – not necessarily in this order.
Matlab toolstrips can be a bit complex, so I plan to proceed in small steps, each post building on top of its predecessors.
If you would like me to assist you in building a custom toolstrip or GUI for your Matlab program, please let me know.
Hi @Yair,
First of all — kudos on the amazing work. I think that the miniseries format has a substantial added-value, as one can connect the different pieces to a complete picture.
Regarding this series: do you think Mathworks(c) will incorporate the toolstrip widgets into
appdesigner
? I mean, for the lazy—but advanced—user, it seems only natural to have these widgets without all the nuisance of placing them programmatically.— Yaroslav
@Yaroslav – thanks.
It would indeed make a lot of sense for the toolstrip functionality to be added to App Designer, but I don’t know whether this is what MW plans or what the timeline for this might be. For example, MW might one day release a separate independent tool to create apps (with toolstrip, Data Browser, docking etc.), that would not be a direct part of App Designer. In fact, MW might even decide to monetize such a tool as a new commercial toolbox. I hope MW won’t do this, but it would certainly be just as viable an option as adding the functionality to App Designer. Unfortunately, I see a hint for this direction in the fact that the Matlab Compiler Toolbox is currently prevented from compiling and deploying user-created apps (only figures and uifigures). Maybe it’s related and maybe not – I hope not, but I fear that it may indeed be. In such a case, I hope that MW would at least leave the programmatic route as part of the core Matlab.
In any case, I believe it is unlikely that the toolstrip functionality will ever be added to GUIDE, because I believe MW plans to discontinue GUIDE as soon as it can get the new uifigures and App Designer to a reasonable state. This is the reason that MW never added uitree to GUIDE, only to App Designer. And this is entirely understandable, after all most R&D resources should be devoted to future tools, not outdated ones (and GUIDE is at least a decade, if not two, out-of-date). Therefore, for legacy (Java-based) figures and apps, the only option for adding a toolstrip would be programmatically.
Hello @Yair,
I was able to compile my ToolGroup app however the standalone executable would only open for a second display itself and then shutdown immediately. I believe this is because Compiler is prevented to do things correctly when it comes to ToolGroup apps. Or could I have done something wrong?
Thank you.
@Batuhan – I don’t know what exactly happened in your specific case, but it could be that you are not storing the ToolGroup handle in a persistent location, as I explained in an earlier post at the beginning of this miniseries:
“An annoying quirk with ToolGroups is that they automatically close when their reference handle is deleted from Matlab memory. The specific behavior changes depending on the contents of the container and the Matlab release, but in general it’s safest to preserve the hToolGroup variable, to prevent the window from closing, when this variable goes out of scope, when the function (in which we create the ToolGroup) returns. …”