Using spinners in Matlab GUI

One of the few standard Java Swing controls that does not have any Matlab uicontrol counterpart is JSpinner. JSpinner is basically an editbox with two tiny adjacent up/down buttons. Spinners are similar in functionality to a combo-box (a.k.a. drop-down or pop-up menu), where a user can switch between several pre-selected values. They are often used when the list of possible values is too large to display in a combo-box menu. Like combo-boxes, spinners too can be editable (meaning that the user can type a value in the editbox) or not (the user can only “spin” the value using the up/down buttons).

JSpinner uses an internal data model, similarly to JTree, JTable and other complex controls. The default model is SpinnerNumberModel, which defines a min/max value (unlimited=[] by default) and step-size (1 by default). Additional predefined models are SpinnerListModel (which accepts a cell array of possible string values) and SpinnerDateModel (which defines a date range and step unit).

Here’s a basic code snippet showing how to display a simple numeric spinner for numbers between 20 and 35, with an initial value of 24 and increments of 1:

jModel = javax.swing.SpinnerNumberModel(24,20,35,1);
jSpinner = javax.swing.JSpinner(jModel);
jhSpinner = javacomponent(jSpinner, [10,10,60,20], gcf);

The spinner value can be set using the edit-box or by clicking on one of the tiny arrow buttons, or programmatically by setting the Value property. The spinner object also has related read-only properties NextValue and PreviousValue. The spinner’s model object has the corresponding Value (settable), NextValue (read-only) and PreviousValue (read-only) properties. In addition, the model also has the settable Maximum, Minimum and StepSize properties.

To attach a data-change callback, set the spinner’s StateChangedCallback property.

I have created a small Matlab demo, SpinnerDemo, which demonstrates usage of JSpinner in Matlab figures. Each of the three predefined models (number, list, and date) is presented, and the spinner values are inter-connected via their callbacks. The Matlab code is modeled after the Java code that is used to document JSpinner in the official documentation. Readers are welcome to download this demo from the Matlab File Exchange and reuse its source code.

Java's SpinnerDemo

Java's SpinnerDemo

  
My Matlab SpinnerDemo

My Matlab SpinnerDemo

As can be seen from the screenshot, SpinnerDemo also demonstrates how to attach a label to a GUI control with an associated accelerator key (Alt-D in the screenshot example, which sets the focus to the Date control).

An internal component in Matlab, namely com.mathworks.mwswing.MJSpinner, extends javax.swing.JSpinner, but in this particular case I cannot see any big advantage of using the internal MJSpinner rather than the standard JSpinner. On the contrary, using JSpinner will likely improve forward compatibility – MathWorks may well change MJSpinner in the future, but it cannot do anything to the standard Swing JSpinner. In other cases, internal Matlab controls do offer significant advantages over the standard Swing controls, but not here it would seem. In any case, the SpinnerDemo utility uses MJSpinner, but you can safely use JSpinner instead (line #86).

The internal Matlab controls are discussed in detail in Chapter 5 of my Matlab-Java book, and MJSpinner is specifically discussed in section 5.2.1.

Another JSpinner derivative is JIDE’s com.jidesoft.grid.SpinnerCellEditor, which can be used as the cell-editor component in tables. An example of this was shown in the article about Advanced JIDE Property Grids (and section 5.7.5 in the book). You may also be interested in the com.jidesoft.combobox.DateSpinnerComboBox, which presents a control that includes both a date-selection combo-box and a spinner (section 5.7.2):

A property grid with spinner control

A property grid with spinner control

  
JIDE's DateSpinnerComboBox

JIDE's DateSpinnerComboBox

Categories: GUI, Java, Low risk of breaking in future versions

Tags: , ,

Bookmark and SharePrint Print

16 Responses to Using spinners in Matlab GUI

  1. OrO says:

    Great post, very useful. I did not know this function.

  2. ramiro massol says:

    hi Yair
    great jcontrol. Do you happen to know how to force the jspinner to accept only numeric data and at the same time allow to clear the text-box-like field?

    best

    • @Ramiro – good to see you here :-)

      You can force the spinner to accept only numeric data by simply using the SpinnerNumberModel, as I have shown in the article and in my demo application, but then you cannot clear the value because it’s an invalid numeric value. If you think about it, there’s really no reason for a spinner to have no value, because this control is supposed to represent a set of pre-defined values.

  3. Hi, great post!
    I’ve found it very useful and far better (more functional and, of course, better looking) than Spinner (from 2004, I think) I’ve found in official file exchange.

  4. Brittney Marimow says:

    Thank you for the GREAT post. One question, is there a way to place this spinner within a “Parent” box. I am working on a GUI that has 5 different boxes on a single tab and would like to place this spinner in the fourth box.

    THANK YOU

    • @Brittney – the 3rd (optional) input parameter of the javacomponent function is the parent container handle. It can be the figure handle (default value: current figure or gcf), or a uipanel handle. When you say “box” I assume you mean a uipanel, so simply specify its handle as the 3rd input arg of javacomponent.

      Additional information: https://undocumentedmatlab.com/blog/javacomponent

  5. Xiangrui Li says:

    This is very useful, Yair!

    I am trying to find a way to store hFig into a spinner handle, so I can tell which figure in case of multiple figures. Here is code for the test:

    function spinnerTest
    % create a spinner with callback
    jModel = javax.swing.SpinnerNumberModel(1, 1, 9, 1);
    jSpinner = javax.swing.JSpinner(jModel);
    h = javacomponent(jSpinner);
    set(h, 'StateChangedCallback', @spinnerCallBack);
     
    % add Parent or Ancestor property for matlab gcbo
    schema.prop(h, 'Parent', 'mxArray'); % also tried Ancestor
    h.Parent = gcf;
     
    %% callback
    function spinnerCallBack(h, evt)
    [hSpinner, hFig] = gcbo; % try to get figure handle
    fprintf('Current value: %g\n', hSpinner.getValue);
    hFig % this is empty with either Parent or Ancestor property added

    I also tried to set datatype as handle, and it seems h.Parent accepts only java handle, not figure/uicontrol handle:

    schema.prop(h, 'Parent', 'handle');
    h.Parent = gcf;
    Parameter must be a handle.

    My current solution is like this in a callback function for both spinner and other uicontrol:

    [h, hFig] = gcbo; % works for matlab uicontrol
    if isempty(hFig) % for java spinner
       hFig = get(h, 'Parent'); % retrieve stored property by schema.prop
    end

    Is there a way to let gcbo return figure handle from java spinner? Octave gcbo uses ‘Parent’, and no idea about Matlab gcbo. Maybe I am too greedy :)

    Thanks.
    Xiangrui

  6. Alain Barraud says:

    hello yair,

    consider the following spinner

    Model = javax.swing.SpinnerNumberModel(125,15,225,.5);
    jSpinner = javax.swing.JSpinner(Model);
    [jhSpinner,hghandle] = javacomponent(jSpinner)
    jhSpinner.Font=java.awt.Font('Serif', 3, 50);

    At this step all is right!

    Now if I want to come back to non bold non italic

    jhSpinner.Font=java.awt.Font('Serif', 0, 50);

    is without any effect!!?? Any idea?

    Another curious point values appear as 140 140,5 141 … and not 140.5 ??

    Thanks
    Alain

    • @Alain – apparently the JSpinner component creates its internal text-field using the parent control’s Font property value when it is first rendered, and then ignores changes to that parent property value. Instead, you can modify the text-field’s Font property (and its other property values) directly:

      jhSpinner.getEditor.getTextField.setFont(java.awt.Font('Serif', 0, 20));
    • Alain Barraud says:

      @yair
      Thanks a lot. It works fine. I had a similar problem with Spinner BackgroundColor and ForegroudColor. Now using for exemple

      val = num2cell([0 0.8 0.8]);
      jhSpinner.getEditor.getTextField.setBackground(java.awt.Color(val{:}));

      gives the expected result.

      Why a non standard “Text: ‘125,5’” property although the Value property is
      “Value: 1.2550e+02” ?

      jhSpinner.getEditor.getTextField.getFormatter
      ans =
      javax.swing.JSpinner$NumberEditorFormatter@a798948

      Is there a way to modify something here?
      Cheers
      Alain

    • @Alain – see my SpinnerDemo (or read JSpinner‘s documentation) to see how to modify the text formatting.

    • Alain Barraud says:

      @yair
      I have found the reason. By default the decimal format is local dependent!!
      In France it is , and not .
      Ok I’ll look at your demo.
      However a last point as in your demo, if the user enters a value outside min or max (for example 2030 as year) the spinner is out of use.
      Is there a special callback property to use in order to check this case.

      Alain

  7. Asimov says:

    Hi, Yair!

    I tried to add a StateChangedCallback to the JSpinner, and if the value is modified, it will display the current value on the command line.

    import javax.swing.*
    import java.awt.*
    f = figure;
     
    sm = SpinnerNumberModel(1.1, 0, 9, 0.3); % default, min, max, step
    js = JSpinner(sm);
    [jspinner, mspinner] = javacomponent(js);
    set(jspinner,'StateChangedCallback', 'disp(jspinner.getValue)');

    However, I find a strange phenomenon, the callback is sometimes invoked twice.

    The codes above will output someting like this:

        1.4000 (click up)
        1.1000 (click down)
        1.1000
        0.8000 (click down)
        0.8000
        0.5000 (click down)

    Notice that although I only click once, the disp sometimes output two same results.

    So I try to check whether the value is changed at the first time. However, something stranger happens.

    I replace the StateChangedCallback by the line below.

    set(jspinner,'StateChangedCallback', 'disp([jspinner.getPreviousValue jspinner.getValue])');

    Which outputs:

        1.4000    1.7000 (click up)
        1.1000    1.4000 (click down)
        1.4000    1.7000 (click up)
        1.4000    1.7000

    Notice that even the value is changed at line 3, it still disp twice.

    And the jspinner.getPreviousValue seems to not remember the result of the first time.

    So I use a temperate value to check whether the value is the same with the previous. (Why I use a tmp value? Because the jspinner.getPreviousValue failed in the case above.)

    tmp = 0;
    set(jspinner,'StateChangedCallback', ...
        'if tmp~=jspinner.getValue;disp(tmp);tmp = jspinner.getValue; end');

    This time, the output works fine, and it never outputs two same lines.

    I guess the problem may be something related to the thread of java/matlab events, but I do not hava enough knowledge about it. The problem could be solved by adding a temp value and add an if-end sentence, but it would be better to know the reason.

    Could you please help me find the reason of this problem?

    By the way, your MATLAB-Java book and many blogs has helped me a lot!

    Thank you so much!

    • @Asimov – Java events often fire twice: first when the event processing starts, and then when the even processing ends. You can process only one of them by adding code in your callback function to prevent re-entrancy. For example:

      % Variant1
      function myCallbackFcn1(hObject,eventData,varargin)
         persistent inCallback
         if ~isempty(inCallback),  return;  end
         inCallback = true;
         try
             % do something useful here
         catch
             % error trapping here
         end
         pause(0.001); drawnow;  % give all other GUI events a chance to bail out above
         inCallback = [];
      end  % myCallbackFcn1

      Additional information: https://undocumentedmatlab.com/blog/controlling-callback-re-entrancy

      If you found my book useful, then please post a favorable review of it on Amazon – thanks in advance!

Leave a Reply

Your email address will not be published. Required fields are marked *