Smart listbox & editbox scrollbars

A good friend recently asked me for examples where using Java in Matlab programs provides a significant benefit that would offset the risk of using undocumented/unsupported functionality, which may possibly stop working in some future Matlab release. Today I will discuss a very easy Java-based hack that in my opinion improves the appearance of Matlab GUIs with minimal risk of a catastrophic failure in a future release.

The problem with Matlab listbox and multi-line editbox controls in the current (non web-based) GUI, is that they use a scrollbar whose behavior policy is set to VERTICAL_SCROLLBAR_ALWAYS. This causes the vertical scrollbar to appear even when the listbox does not really require it. In many cases, when the listbox is too narrow, this also causes the automatic appearance of a horizontal scrollbar. The end result is a listbox that displays 2 useless scrollbars, that possibly hide some listbox contents, and are a sore to the eyes:

Standard (left) and smart (right) listbox scrollbars

Standard (left) and smart (right) listbox scrollbars

   
default scrollbars (VERTICAL_SCROLLBAR_ALWAYS)

default scrollbars (VERTICAL_SCROLLBAR_ALWAYS)

non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)     non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)

non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)

By default, Matlab implements a vertical scrollbar policy of VERTICAL_SCROLLBAR_ALWAYS for sufficiently tall uicontrols (>20-25 pixels, which practically means always) and VERTICAL_SCROLLBAR_NEVER for shorter uicontrols (this may possibly be platform-dependent).

A similar problem happens with the horizontal scrollbar: Matlab implements a horizontal scrollbar policy of HORIZONTAL_SCROLLBAR_NEVER for all editboxes and also for narrow listboxes (<35 pixels), and HORIZONTAL_SCROLLBAR_AS_NEEDED for wide listboxes.

In many cases we may wish to modify the settings, as in the example shown above. The solution to this is very easy, as I explained back in 2010.

All we need to do is to retrieve the control’s underlying Java reference (a Java JScrollPane object) and change the policy value to VERTICAL_SCROLLBAR_AS_NEEDED:

% Create a multi-line (Max>1) editbox uicontrol
hEditbox = uicontrol('style','edit', 'max',5, ...);
 
try  % graceful-degradation for future compatibility
   % Get the Java scroll-pane container reference
   jScrollPane = findjobj(hEditbox);
 
   % Modify the scroll-pane's scrollbar policies
   % (note the equivalent alternative methods used below)
   set(jScrollPane,'VerticalScrollBarPolicy',javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);   %VERTICAL_SCROLLBAR_AS_NEEDED=20
   jScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);  %HORIZONTAL_SCROLLBAR_AS_NEEDED=30
catch
   % Never mind...
end

Note that updating the uicontrol handle Position property has the side-effect of automatically reverting the scrollbar policies to their default values (HORIZONTAL_SCROLLBAR_NEVER and VERTICAL_SCROLLBAR_ALWAYS/NEVER). This also happens whenever the uicontrol is resized interactively (by resizing its container figure window, for example). It is therefore advisable to set jScrollPane’s ComponentResizedCallback property to “unrevert” the policies:

cbFunc = @(h,e) set(h,'VerticalScrollBarPolicy',20, 'HorizontalScrollBarPolicy',30);
hjScrollPane = handle(jScrollPane,'CallbackProperties');
set(hjScrollPane,'ComponentResizedCallback',cbFunc);

smart_scrollbars utility

I created a new utility called smart_scrollbars that implements all of this, which you can download from the Matlab File Exchange. The usage in Matlab code is very simple:

% Fix scrollbars for a specific listbox
hListbox = uicontrol('style','list', ...);
smart_scrollbars(hListbox)
 
% Fix scrollbars for a specific editbox
hEditbox = uicontrol('style','edit', 'max',5, ...);
smart_scrollbars(hEditbox)
 
% Fix all listbox/editbox scrollbars in a panel or figure
smart_scrollbars              % fixes all scrollbars in current figure (gcf)
smart_scrollbars(hFig)        % fixes all scrollbars in a specific figure
smart_scrollbars(hContainer)  % fixes all scrollbars in a container (panel/tab/...)

Performance considerations

Finding the underlying JScrollPane reference of Matlab listboxes/editboxes can take some time. While the latest version of findjobj significantly improved the performance of this, it can still take quite a while in complex GUIs. For this reason, it is highly advisable to limit the search to a Java container of the control that includes as few internal components as possible.

In R2014b or newer, this is easily achieved by wrapping the listbox/editbox control in a tightly-fitting invisible uipanel. The reason is that in R2014b, uipanels have finally become full-fledged Java components (which they weren’t until then), but more to the point they now contain a property with a direct reference to the underlying JPanel. By using this panel reference we limit findjobj‘s search only to the contained scrollpane, and this is much faster:

% Slower code:
hListbox = uicontrol('style','list', 'parent',hParent, 'pos',...);
smart_scrollbars(hListbox)
 
% Much faster (using a tightly-fitting transparent uipanel wrapper):
hPanel = uipanel('BorderType','none', 'parent',hParent, 'pos',...);  % same position/units/parent as above
hListbox = uicontrol('style','list', 'parent',hPanel, 'units','norm', 'pos',[0,0,1,1], ...);
smart_scrollbars(hListbox)

The smart_scrollbars utility detects cases where there is a potential for such speedups and reports it in a console warning message:

>> smart_scrollbars(hListbox)
Warning: smart_scrollbars can be much faster if the list/edit control is wrapped in a tightly-fitting uipanel (details)

If you wish, you can suppress this warning using code such as the following:

oldWarn = warning('off', 'YMA:smart_scrollbars:uipanel');
smart_scrollbars(hListbox)
warning(oldWarn);  % restore warnings

Musings on future compatibility

Going back to my friend’s question at the top of today’s post, the risk of future compatibility was highlighted in the recent release of Matlab R2016a, which introduced web-based uifigures and controls, for which the vast majority of Java hacks that I presented in this blog since 2009 (including today’s hack) will not work. While the full transition from Java-based to web-based GUIs is not expected anytime soon, this recent addition highlighted the risk inherent in using unsupported functionality.

Users can take a case-by-case decision whether any improved functionality or appearance using Java hacks is worth the extra risk: On one hand, such hacks have been quite stable and worked remarkably well for the past decade, and will probably continue working into 2020 or so (or longer if you keep using a not up-to-the-moment Matlab release, or if you create compiled applications). On the other hand, once they stop working sometime in R2020a (or whenever), major code rewrites may possibly be required, depending on the amount of dependency of your code on these hacks.

There is an obvious tradeoff between improved GUIs now and for the coming years, versus increased maintainability cost a few years in the future. Each specific GUI will have its own sweet spot on the wide spectrum between using no such hacks at all, through non-critical hacks that provide graceful functionality degradation if they ever fail, to major Java-based functionality that would require complete rework. It is certainly NOT an all-or-nothing decision. Users who take the conservative approach of using no unsupported feature at all, lose the opportunity to have professional grade Matlab GUIs today and in the upcoming years. Decisions, decisions, …

In any case, we can reduce the risk of using such hacks today by carefully wrapping all their code in try-catch blocks. This way, even if the code fails in some future Matlab release, we’d still be left with a working implementation based on fully-supported functionality. This is the reason why I’ve used such a block in the code snippet above, as well as in my smart_scrollbars utility. What this means is that you can safely use smart_scrollbars in your code today and if the worst happens and it stops working in a few years, then it will simply do nothing without causing any error. In other word, future compatibility in the form of graceful degradation. I strongly advise using such defensive coding techniques whenever you use unsupported features.

Categories: GUI, Java, Low risk of breaking in future versions, UI controls, Undocumented feature

Tags: , , ,

Bookmark and SharePrint Print

6 Responses to Smart listbox & editbox scrollbars

  1. Matthew Hopcroft says:

    Interesting post and thank you for uploading smart_scrollbars.
    If you developed a gui with GUIDE, where would you put the call to smart_scrollbars? In the OpeningFcn?

  2. Collin says:

    Yair,

    Can you tell me what the constructor syntax for a com.mathworks.mlwidgets.cwd.CwdDisplayPanel is, or how to go about determining it?

    Collin

  3. Claudio says:

    I’m having trouble in R2016a. The code I used to change the scroll policy on a listbox is:

    jS = findjobj(findobj('Tag','lbox'));
    jS.setVerticalScrollBarPolicy(21);
    hjScrollPane = handle(jS,'CallbackProperties');
    set(hjScrollPane,'ComponentResizedCallback',cbFunc);

    Where cbFunc is defined as a handle to a function re applying the properties. Everything works fine (the callback is set fine and so on, I checked with findjobj GUI), until I resize the window, where the policy change reverts, as explained here.

    The problem seems to be that “ComponentResizedCallback” is not getting called, I tried for instance cbFunc = @(h,e) disp('called'); and it really isn’t being called.

    • @Claudio – your best bet is to set the callback not on the Java component’s ComponentResizedCallback but rather on the containing panel’s SizeChangedFcn. For example:

      % If the original control is a Java component placed using javacomponent():
      [jhComponent, hContainer] = javacomponent(jComponent, ...);
      set(hContainer, 'Units','normalized', 'SizeChangedFcn',cbFunc);
       
      % If the original control is a Matlab component placed using uicontrol() etc.:
      hPanel = uipanel('BorderType','none', 'SizeChangedFcn',cbFunc, ...);
      hComponent = uicontrol(hPanel, ...)

Leave a Reply

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