Thursday, November 23, 2017

MATLAB GUI Programming: Disabling Edit Uicontrols without Changing Their Appearance

Recently, I have been developing a MATLAB GUI for one of our offices that will allow them to solve complex optimization problems.  There are many inputs for the problem depending on the engineering details of that particular project, and a lot of quantities calculated from those entries  that need to be displayed near those inputs.

In previous projects, I just used the text uicontrol to handle this, but I always found the result aesthetically unappealing when a large amount of data needed to be summarized close to the user-entered data.  Figure 1 shows an example of this.  While it looks adequate for this particular case, I don't like the way it looks scaled up when there are dozens of user inputs.

Figure 1:  Using a text uicontrol to summarize information derived from user-entered data

I wanted to use the edit uicontrols to hold this information,  but by its nature the information in that box can be changed by the user.  Disabling the edit box grays out the box and text inside, which, in my opinion, doesn't look particularly good.  See figure 2.

Figure 2:  Examples of enabled and disabled edit uicontrols
Unfortunately, MATLAB doesn't have a way to disable the control without being grayed-out.  MATLAB's GUI capabilities are derived from Java Swing, and MATLAB only makes certain parts of the underlying Java object visible to the programmer.  Fortunately, one can access the handle to the Java object using the utility findjobj available in the Mathworks File Exchange.   Findjobj was written by Yair Altman who runs the excellent site Undocumented MATLAB, which largely deals, unsurprisingly, with undocumented features of MATLAB.

Once we have the object's Java handle, we can use its setEditable method to make the control uneditable.  An example is shown in the code below.


function edit_box
clear all; clc; close all;

f = figure();

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%  Main entry uipanel  %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
entry_panel = uipanel('Parent', f, 'Title', 'Parameters', ...
 'Position', [0.05, 0.55, 0.5, 0.4]);

number_label = uicontrol('Parent', entry_panel, ...
 'Style', 'text', ...
 'String', 'Number of spheres', ...
 'HorizontalAlignment', 'left', ...
 'Position', [10 120, 100, 17]);
number_entry = uicontrol('Parent', entry_panel', ...
 'Style', 'edit', ...
 'Position', [120 120, 75, 20]);

radius_label = uicontrol('Parent', entry_panel, ...
 'Style', 'text', ...
 'String', 'Radii of spheres', ...
 'HorizontalAlignment', 'left', ...
 'Position', [10 90, 100, 17]);
radius_entry = uicontrol('Parent', entry_panel', ...
 'Style', 'edit', ...
 'Position', [120 90, 75, 20]);
radius_unit_label = uicontrol('Parent', entry_panel, ...
 'Style', 'text', ...
 'String', 'cm', ...
 'HorizontalAlignment', 'left', ...
 'Position', [200 90, 40, 17]);

density_label = uicontrol('Parent', entry_panel, ...
 'Style', 'text', ...
 'String', 'Density of spheres', ...
 'HorizontalAlignment', 'left', ...
 'Position', [10 60, 100, 17]);
density_entry = uicontrol('Parent', entry_panel', ...
 'Style', 'edit', ...
 'Position', [120 60, 75, 20]);
density_units_jlabel = javaObjectEDT('javax.swing.JLabel', ...
 '<HTML>g &#47; cm<SUP>3</SUP></HTML>');
[density_units_label, hcontainer] = javacomponent(density_units_jlabel, ...
 [200 60, 40, 22], entry_panel);

%  Create a font for the JLabel that matches the default MATLAB font for text
%  uicontrols
label_font = java.awt.Font('MS Sans Serif', java.awt.Font.PLAIN, 11);
density_units_label.setFont(label_font);

update_button = uicontrol('Parent', entry_panel, ...
 'String', 'Update', ...
 'Callback', @update_fields, ...
 'Position', [10 15 75 30]);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%  Main entry uipanel  %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
summary_panel = uipanel('Parent', f, 'Title', 'Summary', ...
 'Position', [0.05, 0.05, 0.5, 0.4]);

volume_per_sphere_label = uicontrol('Parent', summary_panel, ...
 'Style', 'text', ...
 'String', 'Volume per sphere', ...
 'HorizontalAlignment', 'left', ...
 'Position', [10 120, 100, 17]);
volume_per_sphere_entry = uicontrol('Parent', summary_panel', ...
 'Style', 'edit', ...
 'Position', [120 120, 75, 20]);
volume_per_sphere_units_jlabel= javaObjectEDT('javax.swing.JLabel', ...
 '<HTML>cm<SUP>3</SUP></HTML>');
[volume_per_sphere_units_label, hcontainer] = javacomponent( ...
 volume_per_sphere_units_jlabel, ...
 [200 120, 40, 22], summary_panel);
volume_per_sphere_units_jlabel.setFont(label_font);

total_volume_label = uicontrol('Parent', summary_panel, ...
 'Style', 'text', ...
 'String', 'Total volume', ...
 'HorizontalAlignment', 'left', ...
 'Position', [10 90, 100, 17]);
total_volume_entry = uicontrol('Parent', summary_panel', ...
 'Style', 'edit', ...
 'Position', [120 90, 75, 20]);
total_volume_units_jlabel = javaObjectEDT('javax.swing.JLabel', ...
 '<HTML>cm<SUP>3</SUP></HTML>');
[total_volume_units_label, hcontainer] = javacomponent( ...
 total_volume_units_jlabel, ...
 [200 90, 40, 22], summary_panel);
total_volume_units_jlabel.setFont(label_font);

total_mass_label = uicontrol('Parent', summary_panel, ...
 'Style', 'text', ...
 'String', 'Total mass', ...
 'HorizontalAlignment', 'left', ...
 'Position', [10 60, 100, 17]);
total_mass_entry = uicontrol('Parent', summary_panel', ...
 'Style', 'edit', ...
 'Position', [120 60, 75, 20]);
total_mass_unit_label = uicontrol('Parent', summary_panel, ...
 'Style', 'text', ...
 'String', 'g', ...
 'HorizontalAlignment', 'left', ...
 'Position', [200 60, 40, 17]);

%  Make the edit entries in this section uneditable by accessing their Java
%  handle and setting editable to false.
jvolume_per_sphere_entry = findjobj(volume_per_sphere_entry);
jvolume_per_sphere_entry.setEditable(false);

jtotal_volume_entry = findjobj(total_volume_entry);
jtotal_volume_entry.setEditable(false);

jtotal_mass_entry = findjobj(total_mass_entry);
jtotal_mass_entry.setEditable(false);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%  Calback function  %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 function update_fields(source, events)
  %  Read inputs from edit boxes
  N = str2num( number_entry.String );
  r = str2num( radius_entry.String );
  rho = str2num( density_entry.String );

  %  Calculate the needed values
  V_sphere = 4/3 * pi * r^3;
  V_total = N * V_sphere;
  M = rho * V_total;

  %  Update the edit boxes
  volume_per_sphere_entry.String = num2str( V_sphere );
  total_volume_entry.String = num2str( V_total );
  total_mass_entry.String = num2str( M );
end

end

This code produces the window shown if Fig. 3.  The edit uicontrols in the Summary pane are not changeable by the user, though one can highlight the values and copy them to the clipboard if need be.

Figure 3.  Example of window where several edit uicontrols are set to be non-editable while not being grayed out.
Another annoying quirk of MATLAB GUIs is the inability to customize the text in text uicontrols.  I am not sure what classes the underlying Java object derives its properties from, but whatever it is, it is very limited.  In cases where we need to format the text-- in the above example we have units raised to a power-- we can just substitute a Java JLabel in place of the text uicontrol.  MATLAB and Java default to different fonts, so I changed the font of the JLabel to match that of the MATLAB control by creating a Java font object with the appropriate settings and passing it to the setFont method of the JLabel.  The first instance of this in the above code occurs at the lines:


label_font = java.awt.Font('MS Sans Serif', java.awt.Font.PLAIN, 11);
density_units_label.setFont(label_font);

This is an example of using Java directly in MATLAB which is more a forte of the Undocumented MATLAB site so I won't dwell on it here.

No comments:

Post a Comment