Archive for the ‘Medium risk of breaking in future versions’ Category

Matlab and the Event Dispatch Thread (EDT)

Wednesday, March 10th, 2010

Once again I welcome guest blogger Matt Whitaker, with the long awaited EDT article.

Java Swing’s Event Dispatch Thread (EDT)
or: why does my GUI foul up?

Matlab for the most part is a single threaded environment. That is, all commands are executed sequentially along a single execution thread. The main exception to this are the Handle Graphics (GUI) components whose operations execute on the Java Event Dispatch Thread (EDT). EDT effects are reflected even in mundane Matlab GUI operations.

If we execute the code below we will probably see nothing until the loop completes and the figure appears with the text label showing ‘10000′:

h = figure;
txt = uicontrol('Parent',h, 'Style','text', 'String','1');
for n = 1:10000
    set(txt,'String',int2str(n))
end %for

By adding a couple of drawnow commands we get the figure and text label to render and then we see the count progress to 10000.

h = figure;
txt = uicontrol('Parent',h, 'Style','text', 'String','1');
drawnow;
for n = 1:10000
    set(txt,'String',int2str(n));
    drawnow;
end %for

The drawnow function allows the EDT queue to be flushed and the pending graphics operations to be evaluated. This will also happen with pause and several other commands.

If we want to use Swing (or AWT) components in our user interfaces we need to take this multi-threaded environment into account. The Swing toolkit designers decided to make all the Swing components thread un-safe in order to decrease their complexity. As a consequence, all access to Swing components should be done from the event dispatch thread (EDT), to ensure the operations are executed sequentially, at the exact order in which they were dispatched. Any action on a Swing component done on another thread (Matlab’s main processing thread in our case) risks a race-condition or deadlock with the EDT, which could (and often does) result in weird, non-deterministic and non-repetitive behavior – all of which should be avoided in any application which should behave in a precisely deterministic manner.

In Java, the usual pattern to accomplish EDT dispatching is to create a Runnable object, encapsulate the GUI code in the run method of the Runnable object, then pass the Runnable object to the static EventQueue.invokeLater (or EventQueue.invokeAndWait if we need to block operations to get a return value) method.

Runnable runnable = new Runnable()
{
    public void run()
    {
        //GUI Code here
    }
}
 
EventQueue.invokeLater(runnable);

There are several functions in Matlab that implement this programming pattern for us: javaObjectEDT, javaMethodEDT, awtinvoke, awtcreate and javacomponent. JavaMethodEDT and javaObjectEDT were introduced in version R2008b (7.7) and are minimally and only partially documented although they have reasonably complete help comments. The other three are semi-documented (meaning they are unsupported but if you edit or type their m-file you’ll see a fairly detailed help section), and although there is some overlap in their functionality they are still available.

javaObjectEDT and javaMethodEDT

javaObjectEDT is the the preferred method since R2008b of creating swing components to be used on the EDT. An object created with javaObjectEDT will have all of its subsequent method calls run on the EDT. This is termed Auto Delegation. Auto-delegation greatly simplifies and increases the readability of code. Note that objects created as a result of method calls may not be implemented on the EDT.

If you have an existing Java object, you can pass it to javaObjectEDT at any time - all its subsequent calls will then onward run on the EDT. Note that this useful functionality is an under-documented javaObjectEDT feature: it is not mentioned in the main help section but only implied from the example.

% Create a button on the EDT
btn = javaObjectEDT('javax.swing.JButton');
% this will run on EDT since btn was javaObjectEDT-created
btn.setText('Button');
 
% Create a button NOT on the EDT
btn2 = javax.swing.JButton;
% Dangerous! call will run on main Matlab thread
btn2.setText('Button2');
% modify btn2 so its methods will start running on the EDT
javaObjectEDT(btn2);
btn2.setText('Button2');

The following example shows the use of javaObjectEDT and javaMethodEDT in a more complex situation using a JTable:

function tableExample
hFig = figure;
drawnow; %need to get figure rendered
 
%use Yair's createTable to add a javax.swing.JTable
%http://www.mathworks.com/matlabcentral/fileexchange/14225-java-based-data-table
%wrap ceateTable in javaObjectEDT to put the ensuing method calls on the EDT
f = java.awt.Font(java.lang.String('Dialog'),java.awt.Font.PLAIN,14);
headers = {'Selected','File','Analysis Routine','Task Status'};
tbl = javaObjectEDT(createTable(hFig,headers,[],false,'Font',f));
 
%set column 1 to use check boxes and set up a change callback
tbl.setCheckBoxEditor(1);
jtable = javaObjectEDT(tbl.getTable); %get the underlying Java Table. IMPORTANT: we need to put jtable on the EDT
columnModel = javaObjectEDT(jtable.getColumnModel); %now we can now do direct calls safely on jtable
selectColumn = javaObjectEDT(columnModel.getColumn(0));
selectColumnCellEditor = selectColumn.getCellEditor;
chk = javaMethodEDT('getComponent',selectColumnCellEditor);
set(chk,'ItemStateChangedCallback',@chkChange_Callback);
 
%make column three a combo drop down
analysisTable = {'Analysis1';'Analysis2';'Analysis3'};
cb = javaObjectEDT('com.mathworks.mwswing.MJComboBox',analysisTable);
cb.setEditable(false);
cb.setFont(f);
set(cb,'ItemStateChangedCallback',@cbChange_Callback);
editor = javaObjectEDT('javax.swing.DefaultCellEditor',cb);
analysisColumn = javaObjectEDT(columnModel.getColumn(2));
analysisColumn.setCellEditor(editor);
 
%set some column with restrictions
selectColumn.setMaxWidth(100);
analysisColumn.setPreferredWidth(300);
 
%set the data
SELECTED = java.awt.event.ItemEvent.SELECTED;
tbl.setData({false,'file1','Analysis2','Analysis2';...
             true,'file2','Analysis3','Analysis3'});
drawnow;
 
    function cbChange_Callback(src,ev) %#ok
        jRow = jtable.getSelectedRow;
        stateChange = javaMethodEDT('getStateChange',ev);
        if stateChange == SELECTED
            newData = javaMethodEDT('getItem',ev);
            model = jtable.getModel;
            javaMethodEDT('setValueAt',model,newData,jRow,3);
        end %if
    end %cbChange
 
    function chkChange_Callback(src,ev) %#ok
        chkBox = javaMethodEDT('getItem',ev);
        if logical(javaMethodEDT('isSelected',chkBox))
            beep; %put useful code here
        else
            beep;
            pause(0.1)
            beep; %put useful code here
        end %if
    end %chkChange_Callback
 
end %tableExample

If you are running Matlab R2008a or later, javacomponent uses the javaObjectEDT function to create the returned objects so you do not have to do anything further to these objects to have their calls dispatched on the EDT. Users need to take care that objects added directly to the components created by javacomponent are on the EDT as well as specialized sub-components (e.g. CellRenderers and CellEditors). The overhead of calling javaMethodEDT is fairly small so if in doubt, use it.

javaObjectEDT and its kin first appeared in R2008a, although they only became supported in R2008b. Unfortunately, using them on R2008a sometimes causes hangs and all sorts of other mis-behaviors. This problem was fixed in the R2008b release, when javaObjectEDT became a fully-supported function. The problem with using javaObjectEDT in our application is that if it ever runs on an R2008a platform it might hang! (on Matlab release R2007b and earlier we will get an informative message saying that the javaObjectEDT function does not exist)

For this reason, I am using the following method in my projects:

function result = javaObjEDT(varargin)
%Placeholder of Matlab's buggy javaObjectEDT function on R2008a
 
% Programmed by Yair M. Altman: altmany(at)gmail.com
% $Revision: 1.2 $  $Date: 2009/01/25 11:31:08 $
 
  try
      try
          result = varargin{1};
      catch
          result = [];
      end
      v = version;
      if str2double(v(1:3)) > 7.6
          result = builtin('javaObjectEDT',varargin{:});
      end
  catch
      % never mind
  end
end

Note that javaMethodEDT has the method name as its first input argument, and the object name or reference as its second arg. This is inconsistent with many other Matlab/Java functions, which normally accept the target object as the first argument (compare: invoke, awtinvoke, notify etc.). It also means that we cannot use the familiar obj.javaMethodEDT(methodName) format.

One final note: when javaObjectEDT and javaMethodEDT first appeared in R2008a, they were complemented by the javaObjectMT and javaMethodMT functions, which create and delegate Java objects on the main Matlab computational thread. Their internal documentation says that there are cases when execution must occur on the MT rather than EDT, although I am personally not aware of any such case.

awtcreate and awtinvoke

For users with versions prior to R2008b the user must use the awtcreate function to create objects on the EDT. One huge disadvantage of this older function is that if you have to pass java objects in the parameter list you must use the very cumbersome JNI style notation. For example, for the simple task of setting a button label, one has to use:

btn = awtcreate('javax.swing.JButton');
awtinvoke(btn,'setText(Ljava/lang/String;)','click me')

The other disadvantage is that creating the object using awtcreate does not ensure that its subsequent method calls will be executed on the EDT. The awtinvoke function must be used for each call.

Also, both awtcreate and awtinvoke have some limitations due to bugs in the private parseJavaSignature function (for example, invoking methods which accept a java.lang.Object) which forces one to use the direct call to the method, using the main Matlab thread. This can result in the undesired effects described above. In this situation the best workaround is to call pause(0.01) to allow the event queue to clear.

Versions of javacomponent earlier than R2008a use awtcreate and objects created by these versions must have their subsequent methods called by awtinvoke to be used on the EDT.

A very rare CSSM thread discusses the usage of awtcreate and awtinvoke with some very interesting remarks by MathWorks personnel.

There is an interesting option in awtinvoke that was not carried over into the newer javaMethodEDT. This option allows the user to pass a function handle in the argument list along with its parameters. This option creates an undocumented com.mathworks.jmi.Callback object that has a delayed callback. The delayed callback is dispatched on the EDT so that it will be called once the java method used in awtinvoke is finished. Note that the actual function will still execute on the main Matlab thread the delayed callback will just control when it is called. However this may be useful at times. It is possible to put this functionality into a separate function we can call to delay execution until the event queue is cleared.

%CALLBACKONEDTQUEUE will place a callback on the EDT to asynchronously
%run a function.
%CALLBBACKONEDTQUEUE(FCN) will run function handle FCN once all previous
%methods dispatched to the EDT have completed.
%CALLBBACKONEDTQUEUE(FCN,ARG1,ARG2,...) ill run function handle FCN with
%arguments ARG1,ARG2...once all previous methods dispatched to the EDT
%have completed.
%Note that the function is still executing on the main Matlab thread. This
%function just delays when it will be called.
function callbackOnEDTQueue(varargin)
    validateattributes(varargin{1},{'function_handle'},{});
    callbackObj = handle(com.mathworks.jmi.Callback,'callbackProperties');
    set(callbackObj,'delayedCallback',{@cbEval,varargin(:)});
    callbackObj.postCallback;
 
    function cbEval(src,evt,args) %#ok
        feval(args{:});
    end %cbEval
end %callbackOnEDTQueue
Bookmark and Share

Setting desktop tab completions

Wednesday, March 3rd, 2010

This site has lately focused on quite detailed Java-related topics. Next week I will present the promised EDT article, which will dive into even deeper Java territory. So I thought to take a short break and present an entirely pure-Matlab non-Java undocumented feature, which is simple and yet quite useful.

A few months ago, a CSSM reader asked whether it is possible to customize Matlab tab-completion for user-defined functions (see related). A similar question on StackOverflow provided the necessary solution lead:

Apparently, Matlab has a file called TC.xml in its [matlabroot '/toolbox/local/'] folder that contains the definitions of the tab-completable functions and their arguments. In order for a user-defined function’s arguments to support tab-completion, a new entry needs to be added to this XML file.

TC.xml & TC.xsd

The full syntax of the TC.xml file can be found in the TC.xsd file, which is located in the same folder as TC.xml. Here are some sample definitions from my TC.xml file (which might vary across Matlab releases):

<binding name="addpath" ctype="DIR"/>
<binding name="help"    ctype="FUN SUBFUN"/>
<binding name="clear"   ctype="FUN VAR"/>
 
<binding name="whos"    ctype="VAR">
  <arg previous="-file" ctype= "MATFILE"/>
</binding>
 
<binding name="open">
  <arg argn="1" ctype="VAR MATFILE FIGFILE MFILE MDLFILE FILE"/>
</binding>
 
<binding name="openfig">
  <arg argn="1" ctype="FIGFILE"/>
  <arg argn="2" ctype="VAR" value="new visible invisible reuse"/>
</binding>
 
<binding name="mlint"   ctype="FUN">
  <arg argn="2:10" ctype="VAR" value="-struct -string -id"/>
</binding>

The first example defines that an unlimited number of addpath arguments are all of type DIR. Therefore, when completing any argument of this function in the Command-Window, Matlab will present only relevant DIR (=folder) elements in the pop-up window (lexically sorted):

Tab-completion of type DIR

Tab-completion of type DIR

Similarly, help defines all its arguments to be a function or sub-function type, so the popup-up will only be populated with the function names currently visible in the desktop:

Tab-completion of types FUN & SUBFUN

Tab-completion of types FUN & SUBFUN

Similarly, clear defines all its arguments as function names or variables. Note that the list of available functions and variables may change depending on the current execution stack position. The full list of supported types is defined in the TC.xsd file. It is: VAR, FUN, SUBFUN, DIR, FILE, MFILE, MATFILE, FIGFILE and MDLFILE.

The whos function defines all its arguments as VAR, except the single MATFILE argument that follows a ‘-file’ argument (look at whos’s help page to understand why).

The open function defines tab completion only for its first argument (with plenty of possible types…). Likewise, openfig defines its first argument as a FIGFILE, and its second as VAR with a few extra special-purpose strings that are added to the popup-up menu.

Finally, the mlint example shows that multiple arguments can be defined using a single XML definition element. In this case, args #2-10 are defined as VAR (with three extra special-purpose strings), while arg #1 and 11+ are defined as FUN.

The careful user can edit the TC.xml file using any text editor (I strongly suggest saving a backup first):

edit(fullfile(matlabroot,'toolbox/local/TC.xml'))

User-defined functions can easily be added to TC.xml, and we can even add/modify the built-in Matlab functions that are already defined. Note that changes to TC.xml only take effect after a Matlab restart. From then on, all future Matlab sessions will use the modification, so a really simple one-time edit can improve our workflow for a long time - at least until we upgrade Matlab, when we’ll need to redo our edits…

TabComplete utility

In order to facilitate TC.xml editing, I have created a utility called TabComplete, which is now available on the Matlab File Exchange. The use of this utility is very simple. For example:

tabcomplete test file 'DIR +data -data nodata' VAR

defines a user-defined function test that accepts a FILE argument, followed by a DIR argument with three special-purpose strings, followed by any number of VAR arguments. If I wished to define specific argument types without any default type, I would use:

tabcomplete test file 'DIR +data -data nodata' ''

Using TabComplete for user-defined functions

Using TabComplete for user-defined functions

TabComplete can also be used to retrieve the current list of tab-completion definitions:

>> definitions = tabcomplete;
>> definitions(1)
ans = 
    functionName: 'addpath'
     defaultType: 'DIR'
     extraValues: ''
        platform: ''
    functionArgs: []
 
>> definitions(54)
ans = 
    functionName: 'openfig'
     defaultType: ''
     extraValues: ''
        platform: ''
    functionArgs: [1x2 struct]
>> definitions(54).functionArgs(1)
ans = 
    previousArg: ''
        argType: 'FIGFILE'
    extraValues: ''
>> definitions(54).functionArgs(2)
ans = 
    previousArg: ''
        argType: 'VAR'
    extraValues: 'new visible invisible reuse'

TabComplete has a few limitations: it does not support the -previous option described above (you can do this by manually editing TC.xml). There are also some inherent limitations in Matlab’s TC functionality: changes take effect only after a Matlab restart (there might be a way to reload the definitions in the current Matlab session, but I do not know of any); the list of standard types cannot be modified; and the default type does not support extra special-purpose strings as do the numbered arguments.

There is another very annoying limitation: by default, TC.xml only supports lowercase function names. This is stupid, since Matlab has many function names with UPPERCASE characters, and certainly user-defined function names also do. Luckily, this last limitation can easily be overcome by editing the TC.xsd file (note that this is the TC.XSD file, not the TC.XML file). Instead of:

<xsd:simpleType name="tcBindingNameType">
  <xsd:restriction base="xsd:token">
    <xsd:pattern value='[A-Za-z_0-9]+(/[a-z_0-9]+)?'/>
  </xsd:restriction>
</xsd:simpleType>

Change the xsd:pattern definition element to:

    <!-- Yair 21/2/2010: added A-Z -->
    <xsd:pattern value='[A-Za-z_0-9]+(/[A-Za-z_0-9]+)?'/>

(note the way comments can be added to the XSD/XML files)

P.S. an entirely different customization, for user-defined class members, was presented by Michal Kutil.

Bookmark and Share

Inactive Control Tooltips & Event Chaining

Wednesday, February 24th, 2010

Once again, I welcome guest blogger Matt Whitaker, who continues his series of articles.

In my last post I explored some tips on tooltips. One of these tips involved displaying tooltips on disabled uicontrols. I explained that displaying tooltips on inactive controls is problematic since Matlab appears to intercept mouse events to these inactive controls, so even setting the tooltip on the underlying Java object will not work: The java object appears not to receive the mouse-hover event and therefore does not “know” that it’s time to display the tooltip.

When Yair and I deliberated this issue, he pointed me to his comment on a previous article showing an undocumented Java technique (Java also has some…) for forcing a tooltip to appear using the ActionMap of the uicontrol’s underlying Java object to get at a postTip action. We discussed using a WindowButtonMotionFcn callback to see if the mouse was above the inactive control, then triggering the forced tooltip display. Yair then went on to remind me and I quote: “you’ll need to chain existing WindowButtonMotionFcn callbacks and take into account ModeManagers that override them.”

Frankly, having written code previously that handles callback chaining, I would rather poke myself in the eye with a fork!

The Image Processing Toolbox has the nice pair of iptaddcallback and iptremovecallback functions that largely handle these issues. But for general Matlab, there seemed to be no alternative until I remembered that events trigger callbacks. I decided to use a listener for the WindowButtonMotion event to detect the mouse motion. Event listeners were briefly explained two weeks ago and deserve a dedicated future article. The advantage of using an event listener is that we don’t disturb any existing WindowButtonMotionFcn callback. We still need to be somewhat careful that our listeners don’t do conflicting things, but it’s a lot easier than trying to manage everything through the single WindowButtonMotionFcn.

A demonstration of this appears below with some comments following (note that this code uses the FindJObj utility):

function inactiveBtnToolTip
  %Illustrates how to make a tooltip appear on an inactive control
  h = figure('WindowButtonMotionFcn',@windowMotion,'Pos',[400,400,200,200]);
  col = get(h,'color');
  lbl = uicontrol('Style','text', 'Pos',[10,160,120,20], ...
                  'Background',col, 'HorizontalAlignment','left');
  btn = uicontrol('Parent',h, 'String','Button', ...
                  'Enable','inactive', 'Pos',[10,40,60,20]);
  uicontrol('Style','check', 'Parent',h, 'String','Enable button tooltip', ...
            'Callback',@chkTooltipEnable, 'Value',1, ...
            'Pos',[10,80,180,20], 'Background',col);
  drawnow;
 
  %create the tooltip and postTip action
  jBtn = findjobj(btn);
  import java.awt.event.ActionEvent;
  javaMethodEDT('setToolTipText',jBtn,'This button is inactive');
  actionMap = javaMethodEDT('getActionMap',jBtn);
  action = javaMethodEDT('get',actionMap,'postTip');
  actionEvent = ActionEvent(jBtn, ActionEvent.ACTION_PERFORMED, 'postTip');
 
  %get the extents plus 2 pixels of the control to compare to the mouse position
  btnPos = getpixelposition(btn)+[-2,-2,4,4]; %give a little band around the control
  left = btnPos(1);
  right = sum(btnPos([1,3]));
  btm = btnPos(2);
  top =  sum(btnPos([2,4]));
 
  % add a listener on mouse movement events
  tm = javax.swing.ToolTipManager.sharedInstance; %tooltip manager
  pointListener = handle.listener(h,'WindowButtonMotionEvent',@figMouseMove);
 
  %inControl is a flag to prevent multiple triggers of the postTip action
  %while mouse remains in the button
  inControl = false;
 
  function figMouseMove(src,evtData) %#ok
    %get the current point
    cPoint = evtData.CurrentPoint;
 
    if cPoint(1) >= left && cPoint(1) <= right &&...
       cPoint(2) >= btm  && cPoint(2) <= top
 
      if ~inControl %we just entered
        inControl = true;
        action.actionPerformed(actionEvent); %show the tooltip
      end %if
    else
      if inControl %we just existed
        inControl = false;
        %toggle to make it disappear when leaving button
        javaMethodEDT('setEnabled',tm,false);
        javaMethodEDT('setEnabled',tm,true);
      end %if
    end %if
  end %gpMouseMove
 
  function windowMotion(varargin)
    %illustrate that we can still do a regular window button motion callback
    set(lbl,'String',sprintf('Mouse position: %d, %d',get(h,'CurrentPoint')));
    drawnow;
  end %windowMotion
 
  function chkTooltipEnable(src,varargin)
    if get(src,'Value')
      set(pointListener,'Enable','on');
    else
      set(pointListener,'Enable','off');
    end %if
  end %chkTooltipEnable
end %inactiveBtnToolTip

Tooltip on an inactive button

Tooltip on an inactive button

Comments on the code:

  1. The code illustrates that we can successfully add an additional listener to listen for mouse motion events while still carrying out the original WindowButtonMotionFcn callback. This makes chaining callbacks much easier.
  2. The handle.listener object has an Enable property that we can use to temporarily turn the listener on and off. This can be seen in the chkTooltipEnable() callback for the check box in the code above. If we wanted to permanently remove the listener we would simply use delete(pointListener). Note that addlistener adds a hidden property to the object being listened to, so that the listener is tied to the object’s lifecycle. If you create a listener directly using handle.listener you are responsible for it’s disposition. Unfortunately, addlistener fails for HG handles on pre-R2009 Matlab releases, so we use handle.listener directly.
  3. The code illustrates a good practice when tracking rapidly firing events like mouse movement of handling reentry into the callback while it is still processing a previous callback. Here we use a flag called inControl to prevent the postTip action being continuously fired while the mouse remains in the control.
  4. I was unable to determine if there is any corresponding action for the postTip to dismiss tips so I resorted to using the ToolTipManager to toggle its own Enable property to cleanly hide the tooltip as the mouse leaves the control.

Each Matlab callback has an associated event with it. Some of the ones that might be immediately useful at the figure-level are WindowButtonDown, WindowButtonUp, WindowKeyPress, and WindowKeyRelease. They can all be accessed through handle.listener or addlistener as in the code above.

Unfortunately, events do not always have names that directly correspond to the callback names. In order to see the list of available events for a particular Matlab object, use the following code, which relies on another undocumented function - classhandle. Here we list the events for gcf:

>> get(get(classhandle(handle(gcf)),'Events'),'Name')
ans = 
    'SerializeEvent'
    'FigureUpdateEvent'
    'ResizeEvent'
    'WindowKeyReleaseEvent'
    'WindowKeyPressEvent'
    'WindowButtonUpEvent'
    'WindowButtonDownEvent'
    'WindowButtonMotionEvent'
    'WindowPostChangeEvent'
    'WindowPreChangeEvent'

Note that I have made extensive use of the javaMethodEDT function to execute Java methods that affect swing components on Swing’s Event Dispatch Thread. I plan to write about this and related functions in my next article.

Bookmark and Share

Additional uicontrol tooltip hacks

Wednesday, February 17th, 2010

Once again I’d like to welcome guest blogger Matthew Whitaker. Today Matt will discuss uicontrol tooltips hacks as the first of several posts that follow a logical thread culminating in the long promised article on the EDT.

A while back Yair wrote a cool article, Spicing up Matlab uicontrol tooltips, describing use of HTML formatting and images in uicontrol tooltips. I want to share some limitations I’ve seen with tooltips and their solution using the Matlab control’s underlying Java object.

Situation 1: Displaying a tooltip on disabled controls

One issue with the stock Matlab uicontrol tooltips is that if you turn the uicontrol’s Enable property to ‘inactive’ or ‘off’, its tooltip no longer displays. This is the behavior that we normally want, but occasionally we wish to display a tooltip on a disabled control, for example, to explain why the control is disabled.

You can use the findjobj utility to find the Java handle for the uicontrol. This handle can then be used to set the tooltip text. The tooltip will display if you disable the control using its Java handle’s Enabled property rather than the Matlab handle’s Enable property:

hButton = uicontrol('String','Button');
drawnow;
jButton= findjobj(hButton);
set(jButton,'Enabled',false);
set(jButton,'ToolTipText','This is disabled for a reason');

As customary for Java objects, its properties can also be set using their corresponding accessor methods:

javaMethodEDT('setEnabled',jButton,false);
javaMethodEDT('setToolTipText',jButton,'Button is disabled for a reason');

tooltip on a disabled uicontrol

tooltip on a disabled uicontrol

Unfortunately, this hack does not work for ‘inactive’ controls. There is no direct Java analogy for inactive controls - it’s a Matlab extension. It appears that Matlab somehow intercepts the mouse events associated with inactive controls. My next post will show how event callback can be used to display tooltips for inactive controls.

As an alternative for inactive edit-box controls, we can simulate the inactive behavior by setting the Java object’s Editable property (or by using its setEditable() accessor method), then setting the tooltip. Note that the extremely-useful Java Editable property is unavailable in the Matlab handle, for some inexplicable reason:

hEditbox = uicontrol('String','Edit Text','Style','edit');
drawnow;
jEditbox = findjobj(hEditbox);
set(jEditbox,'Editable',false);
set(jEditbox,'ToolTipText','Text is inactive for a reason');

tooltip on a non-editable editbox

tooltip on a non-editable editbox

Situation 2: Displaying a tooltip on truncated text

If we want to conditionally display a tooltip for an editbox uicontrol when the text exceeds the control’s width, we can use the TipWhenTruncatedEnabled property (or its corresponding setTipWhenTruncatedEnabled() method). This will display a tooltip with the editbox contents if the string is shown truncated. This saves the user having to scroll through the control to see its contents. I often use this for edit controls that may contain long path names:

hEditbox(1) = uicontrol('Style','edit','Units','norm','Pos',[0.1,0.8,0.4,0.05], 'String','Too Short');
hEditbox(2) = uicontrol('Style','edit','Units','norm','Pos',[0.1,0.7,0.2,0.05], 'String','Long Enough to Display a Tool Tip');
drawnow;
jEditbox1 = findjobj(hEditbox(1));
jEditbox2 = findjobj(hEditbox(2));
set(jEditbox1,'TipWhenTruncatedEnabled',true);  % property-based alternative
javaMethod('setTipWhenTruncatedEnabled',jEditbox2,true);  % method-based alternative

TipWhenTruncatedEnabled tooltip

TipWhenTruncatedEnabled tooltip

The TipWhenTruncatedEnabled property property is also available for multi-line editboxes, but has (obviously) no effect when scrollbars are present. Also note that setting the TipWhenTruncatedEnabled property to true overrides any previous tooltip that might have been set for the editbox.

Finally, note that the TipWhenTruncatedEnabled property can also be set for the editbox component of popup-menu (aka drop-down) controls, after they have been set to be editable using their Java Editable property (note that both properties are false by default for Matlab uicontrols). In the following screenshot, the drop-down’s editbox component contained an HTML snippet, that is shown unformatted within the edit-box and HTML-formatted in the de-truncated tooltip:

de-truncated HTML-format tooltip

de-truncated HTML-format tooltip

Situation 3: Controlling tooltip timing

As you have probably noticed, there is a slight delay between the time your mouse enters the control and when the tooltip actually appears. If you display a tooltip over a control for sufficiently long the tooltip will then disappear. Sometimes the default delays are too slow or fast for your application. These times can be controlled through the javax.swing.ToolTipManager. The ToolTipManager sets these parameters globally (including for your Matlab desktop components), but they are not persistent between sessions.

Some examples using the ToolTipManager:

btn = uicontrol('String','Button','Tooltip','This is a button.','Pos',[100,100,75,25]);
txt = uicontrol('Style','edit','String','Edit Text','Tooltip','This is editable text','Pos',[100,50,75,25]);
 
tm = javax.swing.ToolTipManager.sharedInstance; %static method to get ToolTipManager object
initialDelay = javaMethodEDT('getInitialDelay',tm); %get the delay before display in milliseconds (=750 on my system)
javaMethodEDT('setInitialDelay',tm,0); %set tooltips to appear immediately
 
dismissDelay = javaMethodEDT('getDismissDelay',tm); %get the delay before the tooltip disappears (=10000 (10 sec) on my system)
javaMethodEDT('setDismissDelay',tm,2000); %set the dismiss delay to 2 seconds
 
javaMethodEDT('setEnabled',tm,false); %turns off all tooltips in system (including the Matlab desktop)
javaMethodEDT('setEnabled',tm,true);
 
javaMethodEDT('setInitialDelay',tm,initialDelay);
javaMethodEDT('setDismissDelay',tm,dismissDelay);

Note that I have made extensive use of the undocumented built-in javaMethodEDT function to execute Java methods that affect Swing components on the Swing Event Dispatch Thread (EDT). I plan to write about EDT following my next post on event callback chaining.

Bookmark and Share

Continuous slider callback

Monday, February 8th, 2010

Every few months, a CSSM forum reader asks how to set up a continuously-invoked slider callback: Matlab’s slider uicontrol invokes the user callback only when the mouse button is released, and not continuously while the slider’s thumb is dragged. This functionality was again referred-to yesterday, and I decided it merits a dedicated post.

There are three distinct simple ways to achieve continuous callbacks:

Using Java callbacks

As explained in an earlier article, Matlab uicontrols are basically Java Swing objects that possess a large number of useful callbacks. Matlab sliders’ underlying Java objects, which are really not JSliders but JScrollBars, have an AdjustmentValueChangedCallback property that is useful for our purposes and is accessible using the FindJObj utility. Simply download FindJObj from the File Exchange, and then:

hSlider = uicontrol('style','slider', ...);
jScrollBar = findjobj(hSlider);
jScrollBar.AdjustmentValueChangedCallback = @myCbFcn;
% or: set(jScrollBar,'AdjustmentValueChangedCallback',@myCbFcn)

Where myCbFcn is the Matlab callback function that will be invoked continuously when the arrow buttons are depressed or the slider’s thumb is dragged.

Using an event listener

An alternative to the Java route is to use Matlab’s undocumented handle.listener function to listen to the slider’s Action event, as follows:

hListener = handle.listener(hSlider,'ActionEvent',@myCbFcn);

This alternative is used by Matlab’s own imscrollpanel function:

if isJavaFigure
   % Must use these ActionEvents to get continuous events fired as slider
   % thumb is dragged. Regular callbacks on sliders give only one event
   % when the thumb is released.
   hSliderHorListener = handle.listener(hSliderHor,...
      'ActionEvent',@scrollHorizontal);
   hSliderVerListener = handle.listener(hSliderVer,...
      'ActionEvent',@scrollVertical);
   setappdata(hScrollpanel,'sliderListeners',...
      [hSliderHorListener hSliderVerListener]);
else
   % Unfortunately, the event route is only available with Java Figures,
   % so platforms without Java Figure support get discrete events only
   % when the mouse is released from dragging the slider thumb.
   set(hSliderHor,'callback',@scrollHorizontal)
   set(hSliderVer,'callback',@scrollVertical)
end

Using a property listener

The handle.listener function can also be used to listen to property value changes. In our case, set a post-set listener, that gets triggered immediately following Value property updates, as follows:

hhSlider = handle(hSlider);
hProp = findprop(hhSlider,'Value');  % a schema.prop object
hListener = handle.listener(hhSlider,hProp,'PropertyPostSet',@myCbFcn);

In addition to ‘PropertyPostSet’, we could also listen on ‘PropertyPreSet’, which is triggered immediately before the property is modified. There are also corresponding ‘*Get’ options. In relatively old Matlab releases (I believe R2007b and earlier, but I’m not certain), the option names were simply ‘PostSet’, ‘PreSet’ etc., without the ‘Property’ prefix.

Do you know of any other way to achieve continuous callbacks? If so, I would be delighted to hear in the comments section below.

Bookmark and Share

Customizing listbox & editbox scrollbars

Monday, February 1st, 2010

A few days ago, a CSSM forum reader asked how to modify Matlab’s listbox scrollbars. Another user asked how to configure line-wrapping. I thought this is a good opportunity to describe how listbox and editbox scrollbars can be customized. The timing is particularly opportune, after I have recently described how the Matlab Editbox can be customized by accessing its underlying Java object using the FindJObj utility.

Both the listbox and the multi-line editbox uicontrols share a similar design: a multi-line Java control embedded within a JViewport within a JScrollPane (note that for historical reasons, the Java view-port class is called JViewport rather than the more standard camel-cased JViewPort). In addition to the view-port, the containing scroll-pane also contains two scrollbars (horizontal and vertical), as expected from standard Java scroll-panes.

JScrollPane components

JScrollPane components

Scrollbar policies

Control of the scroll-pane’s scrollbar behavior is done via the JScrollPane’s VerticalScrollBarPolicy and HorizontalScrollBarPolicy properties.

VerticalScrollBarPolicy accepts the self-explanatory values of:

  • VERTICAL_SCROLLBAR_ALWAYS (=22)
  • VERTICAL_SCROLLBAR_NEVER (=21)
  • and VERTICAL_SCROLLBAR_AS_NEEDED (=20)

HorizontalScrollBarPolicy accepts:

  • HORIZONTAL_SCROLLBAR_ALWAYS (=32)
  • HORIZONTAL_SCROLLBAR_NEVER (=31)
  • and HORIZONTAL_SCROLLBAR_AS_NEEDED (=30)

All these properties are static enumerated constants that can be referred using either their Java notation (e.g., JScrollPane.VERTICAL_SCROLLBAR_ALWAYS) or their equivalent numeric values. Using the non-numeric format is better, since it is more readable and the numeric values may change, but the choice is yours.

By default, Matlab implements a VerticalScrollBarPolicy of VERTICAL_SCROLLBAR_ALWAYS for sufficiently tall uicontrols (>20-25 pixels, which practically means always) and VERTICAL_SCROLLBAR_NEVER for shorter uicontrols.

For the horizontal scrollbar, Matlab implements a HorizontalScrollBarPolicy of HORIZONTAL_SCROLLBAR_NEVER for all editboxes and for narrow listboxes (<35 pixels), and HORIZONTAL_SCROLLBAR_AS_NEEDED for wide listboxes.

These settings are generally satisfactory. However, in some cases users may wish to modify the settings. For example, the default VerticalScrollBarPolicy setting of VERTICAL_SCROLLBAR_ALWAYS causes the vertical scrollbar to appear even when unneeded (the entire editbox content is visible). Also, we may wish to have a horizontal scrollbar on narrow listboxes and editboxes, something that the standard HORIZONTAL_SCROLLBAR_NEVER prevents. In both cases, a *_SCROLLBAR_AS_NEEDED policy might be more appropriate.

To modify these settings, we simply need to get the uicontrol’s underlying Java reference handle (using the FindJObj utility), and modify the appropriate property. For example:

% Create a multi-line (Max>1) editbox uicontrol
hEditbox = uicontrol('style','edit', 'max',5, ...);
 
% 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',20);  % or: jScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
jScrollPane.setHorizontalScrollBarPolicy(30);  % or: jScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED

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)

Note that updating the uicontrol handle (hEditbox)’s 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:

cbStr = sprintf('set(gcbo,''VerticalScrollBarPolicy'',%d)', ...
                jScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
hjScrollPane = handle(jScrollPane,'CallbackProperties');
set(hjScrollPane,'ComponentResizedCallback',cbStr);

Line-wrapping

By default, line-wrapping is turned on, effectively disabling horizontal scrolling (which is why Matlab set the HorizontalScrollBarPolicy to HORIZONTAL_SCROLLBAR_NEVER. However, in some cases it may be more useful to turn line-wrapping off and horizontal scrolling on using the TextArea’s setWrapping() method. Here’s a usage example:

jViewPort = jScrollPane.getViewport;
jEditbox = jViewPort.getComponent(0);
jEditbox.setWrapping(false);  % do *NOT* use set(...)!!!
newPolicy = jScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED;
set(jScrollPane,'HorizontalScrollBarPolicy',newPolicy);

multi-line editbox with wrapping on

multi-line editbox with wrapping on

multi-line editbox with wrapping off

multi-line editbox with wrapping off

Notes:

  1. setWrapping() only works for the default EditorKit, and fails for HTMLEditorKit – This is due to HTML’s inherent wrapping behavior, as can easily be seen in any browser webpage.
  2. while setWrapping() may seem like a regular setter method for a Wrapping property, in reality it is not. Actually, set(jEditbox,’wrapping’,flag) may crash Matlab. So, always use the setWrapping(flag) method variant, which is entirely safe.
Bookmark and Share

GUI integrated browser control

Wednesday, December 9th, 2009

Last week, I described the built-in PopupPanel object, and showed how it can be used to present popup messages with HTML content and even entire webpages. I explained that PopupPanel uses an internal browser object to achieve this. In fact, Matlab’s browser object predates PopupPanel by many years and quite a few releases. This browser object can be used as a stand-alone component that we can easily embed in our Matlab GUI applications.

Here is a simple example in which a Matlab Listbox uicontrol is used to select the contents of an adjacent browser component:

% Create a blank figure window
f=figure('Name','Browser GUI demo','Num','off','Units','norm');
 
% Add the browser object on the right
jObject = com.mathworks.mlwidgets.html.HTMLBrowserPanel;
[browser,container] = javacomponent(jObject, [], f);
set(container, 'Units','norm', 'Pos',[0.3,0.05,0.65,0.9]);
 
% Add the URLs listbox on the left
urls = {'www.cnn.com','www.bbc.co.uk','myLocalwebpage.html',...
        'www.Mathworks.com', 'UndocumentedMatlab.com'};
hListbox = uicontrol('style','listbox', 'string',urls, ...
        'units','norm', 'pos',[0.05,0.05,0.2,0.9], ...
        'userdata',browser);
 
% Set the listbox's callback to update the browser contents
cbStr=['strs = get(gcbo,''string''); ' ...
      'url = strs{get(gcbo,''value'')};' ...
      'browser = get(gcbo,''userdata''); ' ...
      'msg=[''<html><h2>Loading '' url '' - please wait''];'...
      'browser.setHtmlText(msg); pause(0.1); drawnow;'...
      'browser.setCurrentLocation(url);'];
set(hListbox,'Callback',cbStr);

Browser object integrated in Matlab GUI (click for large image)

Browser object integrated in Matlab GUI

In this simple example, we can see how the Java browser object can easily be controlled by Matlab. Specifically, we use two modes of the browser: first we present an HTML message (’Loading www.cnn.com - please wait‘) and then replacing this content with the actual webpage, if accessible. If the webpage is not accessible, an error message is displayed:

Browser message when webpage is missing (click for large image)

Browser message when webpage is missing

We can easily expand this simple example to display any HTML message or webpage, in a seamless integration within our GUI.

Now, who ever said that Matlab GUI looks static or boring???

In an unrelated note, I would like to extend good wishes to Ken Orr, who has left the Mathworks Desktop development team to join Apple a few days ago. You probably know Ken from his good work on the Desktop and the official Matlab Desktop blog. Hopefully, in his new position Ken will be able to influence Mac Java in a way that will reduce the numerous recurring issues that afflict Matlab Mac releases.

Bookmark and Share

Customizing print setup

Wednesday, November 25th, 2009

Last week, while consulting a client, I watched him use a Matlab application that processed data and presented the results in a figure window. I saw that he constantly needed to go to File/Print-Preview menu option to customize the figure’s print setup before being able to print. He would constantly click the “Fill Page” button, then go to the Color tab and set RGB rather than Black-and-White, and finally go to the Advanced tab to prevent printing UI controls. Only then would he actually print the figure using the menu (File/Print) or the toolbar Print button.

Print Preview window

Print Preview window

This is absurd, I thought to myself - there must be a better way. Unfortunately, Matlab only supports two documented ways to modify the print setup:

  • the print function enables setting print settings when actually printing the figure. It accepts a long list of optional parameters that specify a wide range of printing options. But if I want to allow users to print at their own time, using the default figure menu/toolbar, I need to separate the actions of (1) setting the print setup and (2) doing the actual printout action. Since print does both of these together, we can’t use it. Moreover, if we don’t call print, the setup remains at the default settings and the menu/toolbar printouts use this default setup.
  • the printopt.m file stores the user’s default print setup. This file can be modified (use the printopt function). However, it affects all Matlab printouts, and cannot be configured on a figure-by-figure basis.

Of course, lack of a documented method never stopped us before. Sure enough, after a short search I discovered the hidden potential of the undocumented/unsupported setprinttemplate function. This function, called internally by Matlab’s print-related functions, is responsible for setting the figure’s initial print setup. Once I had this key, unlocking the problem was easy. Here is the bottom line:

The figure’s default print setup is stored in the figure’s hidden ApplicationData property, accessible via the getappdata and setappdata functions or directly via get/set, as explained in a previous post. More importantly, the figure-specific setup is stored in another hidden property, PrintTemplate. Both of these setup data are stored in structure format, which is not available when a figure is first created, but only after printing or print-preview. Note that printtemplate.m contains detailed explanations about the meaning of some fields - to see them, simply display the file:

>> type printtemplate.m  % or: edit printtemplate.m

Unless we set PrintTemplate ourselves (or call print or printpreview to do this), printing the figure will use the default print setup. To set a non-standard setup, we just need to create the PrintTemplate structure with our non-default setup options. Matlab will then automatically use them when printing the figure. Here is a checklist for doing so:

  1. First, create a figure and get its default print setup data. Since this data is unavailable in newly-created figures, simply open the figure’s Print-Preview window without changing anything - this will create the missing print setup structure that we can retrieve:

    % Create a simple figure
    >> hFig = figure;  surf(peaks);
     
    % Open the print-preview window to create setup data
    >> printpreview(hFig);
     
    % Retrieve the original (default) print setup data
    >> oldSetup = get(hFig,'PrintTemplate')
    oldSetup = 
           VersionNumber: 2
                    Name: ''
               FrameName: ''
             DriverColor: 1
         AxesFreezeTicks: 0
               tickState: {}
        AxesFreezeLimits: 0
                limState: {}
                   Loose: 0
                    CMYK: 0
                  Append: 0
               Adobecset: 0
                 PrintUI: 1
                Renderer: 'auto'
          ResolutionMode: 'auto'
                     DPI: 0
                FileName: 'untitled'
             Destination: 'printer'
             PrintDriver: ''
               DebugMode: 0
              StyleSheet: 'default'
                FontName: ''
                FontSize: 0
            FontSizeType: 'screen'
               FontAngle: ''
              FontWeight: ''
               FontColor: ''
               LineWidth: 0
           LineWidthType: 'screen'
            LineMinWidth: 0
               LineStyle: ''
               LineColor: ''
            PrintActiveX: 0
               GrayScale: 0
                 BkColor: 'white'
                 FigSize: [14.8054083333333 11.10405625]
  2. Next, go to the File/Print-Preview menu option and modify the setup according to your specific needs, and retrieve the new (modified setup):
    >> newSetup = get(hFig,'PrintTemplate');
  3. Now compare the two structures and retrieve only the modified setup options. This can be done in several ways - I personally use the objdiff utility. In our case, we modified the DriverColor (B&W => color), FigSize (for “Fill page”), and PrintUI (for hiding UI controls) fields:
    >> objdiff(oldSetup,newSetup)
    ans = 
        DriverColor: {[0]  [1]}
            FigSize: {[14.8054083333333 11.10405625]  [2x1 double]}
            PrintUI: {[1]  [0]}
         StyleSheet: {'default'  'modified'}
           limState: {{}  ''}
          tickState: {{}  ''}
  4. Finally, use the undocumented printtemplate and setprinttemplate functions to prepare the default setup sub-structure, and override with the modified options that you have just discovered. Place this in the figure’s _OpeningFcn function (for GUIDE-generated figures) or in your figure’s initialization function (for non-GUIDE figures). For example, if we have a GUIDE-generated figure called “MyFig”, then place this code in the MyFig_OpeningFcn function in MyFig.m:
    function MyFig_OpeningFcn(hObject, eventdata, handles, varargin)
      ...
      % This was adapted from initprintexporttemplate.m
      pt = printtemplate;
      pt.StyleSheet = 'modified';
      pt.VersionNumber = 2;   % important (Note #1 below)
      pt.FigSize = [38.1, 21.0];
      pt.DriverColor = 1;
      pt.PrintUI = 0;
      % we must set the paper size *before* setprinttemplate
      set(hObject, 'PaperPositionMode','manual', ...
                   'PaperPosition',[0 0.5 29.5 20], ...
                   'PaperSize',[29.68 20.98], ...
                   'PaperType','A4');
      setprinttemplate(hObject, pt);
     
      % Choose default command line output for MyFig
      handles.output = hObject;
     
      % Update handles structure
      guidata(hObject, handles);

That’s all there is to it. So easy once we know how, isn’t it? The most annoying pain-in-the-so-and-so sometimes have simple solutions…

Note #1: it is very important to set pt.VersionNumber to 2, otherwise some modifications will not take effect.

Note #2: the internal implementation of printtemplate as well as the internal setup fields have changed between Matlab releases. These were often minor backward-compatible changes, but at least once this was a major change (VersionNumber 1=>2, I think around Matlab 7.2, but I’m not sure). Therefore, carefully test your code on the oldest release which is supposed to run it. Also, if you plan the code to run in future Matlab releases, you should note that the entire setup functionality might break without prior notice, since it is an internal unsupported implementation.

Bookmark and Share

Undocumented scatter plot behavior

Monday, October 12th, 2009

One of my blog readers, Henrik Toft, reported an undocumented behavior of the well-known scatter plot function: scatter returns a handle to an hggroup-type object that contains child handles of the data points (patch-type objects). This enables easy manipulation of specific points (for example, changing the color or marker of interesting data points).

The undocumented behavior is that when the scatter plot contains more than 100 points, the returned hggroup object only has a single child - a handle to a unified patch object that contains all the data points. This prevents customization of specific points: color can be customized via the cdata property, but this is not trivial; the marker cannot be customized at all.

Both Henrik and I initially thought this was a simple bug. But when Henrik reported this issue to MathWorks support, he got the response that this is indeed the expected behavior (i.e., not a bug but a feature), which is not documented. MathWorks did not say whether this was because they didn’t think users need more than 100 child handles, or perhaps an issue with memory/performance. Whatever the reason, I feel that this behavior/feature should at least be documented:

>> hggroup = scatter(rand(99,1),rand(99,1));
>> size(get(hggroup,'Children'))
ans =
    99     1
 
>> hggroup = scatter(rand(100,1),rand(100,1));
>> size(get(hggroup,'Children'))
ans =
   100     1
 
>> hggroup = scatter(rand(101,1),rand(101,1));
>> size(get(hggroup,'Children'))
ans =
     1     1

Workaround: use the ‘v6′ option. Unfortunately, this option is deprecated and marked as obsolete by Matlab, with the warning that it will be removed in a future Matlab version. I hope this will not be done until a better workaround for the above-reported issue is implemented in the scatter function:

>> hPatches = scatter('v6',rand(101,1),rand(101,1));
Warning: The 'v6' argument to SCATTER is deprecated, and will no longer be supported in a future release.
> In usev6plotapi>warnv6args at 84
  In usev6plotapi at 40
  In scatter at 39
 
>> size(hPatches)
ans =
   101     1

If you have detected any other undocumented behavior, function or feature in Matlab, please report them in the comments section below, or directly by email to me: altmany at gmail dot com.

Bookmark and Share

Detecting window focus events

Wednesday, September 9th, 2009

A CSSM reader recently asked whether it is possible to detect window focus events (specifically, the focus-gain event) asynchronously, so that such events can trigger a callback without necessitating a polling thread to constantly monitor the windows state.

The user correctly mentioned the fact that although mouse-clicks within the window frame can be detected using the documented figure callback WindowButtonDownFcn, there are other methods by which a window can gain focus: keyboard (<Alt>-<Tab> on Windows, for example), clicking the window frame edge etc. These methods are all undetected by WindowButtonDownFcn.

This problem is, to the best of my knowledge, insoluble using standard documented Matlab. However, there is indeed a simple solution using undocumented/unsupported Matlab features. The solution relies on the fact that all Matlab windows are basically Java Swing objects, and these objects have dozens of standard callback hooks that can be utilized (Matlab only exposes a few callbacks). The list of standard Swing callbacks was detailed in my earlier article about uicontrol callbacks, which is also relevant for Java window frames.

In this specific case, we are interested in FocusGainedCallback. This callback is invoked for the figure Frame’s AxisComponent (a part of the Frame that will be explained in another article). For each of our monitored figure windows, we set this callback to a predefined Matlab function. We may also wish to set its companion FocusLostCallback.

Here’s the resulting code snippet (hFig is our Matlab figure handle):

% Prepare the figure
hFig = figure;  % etc. - prepare the figure
 
% Get the underlying Java reference
warning off MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame
jFig = get(hFig, 'JavaFrame');
jAxis = jFig.getAxisComponent;
 
% Set the focus event callback
set(jAxis,'FocusGainedCallback',{@myMatlabFunc,hFig});
% perhaps also set the FocusLostCallback here

Whenever any of the monitored figures now gets focus, by whichever means, the user-defined Matlab function myMatlabFunc() will be invoked. This function should be defined as follows:

function myMatlabFunc(jAxis, jEventData, hFig)
   % do whatever you wish with the event/hFig information
end

Extra input parameters can be added during callback setup and definition, as follows:

set(jAxis,'FocusLostCallback',{@myMatlabFunc,hFig,data1,data2})
...
function myMatlabFunc(jAxis, jEventData, hFig, data1, data2)
   % do whatever you wish with the event/hFig/data information
end

A very similar technique can detect other windowing events (maximization/minimization/movement etc.). Depending on the case, you may need to use jFig.fFigureClient.getWindow instead of jFig.getAxisComponent. The list of available callbacks for each of these objects can be seen using a simple set(jFig.getAxisComponent) command, or via my UIInspect or FindJObj utilities on the Matlab File Exchange.

Note that all this relies on the undocumented hidden figure property JavaFrame, which issues a standing warning (since Matlab release R2008a) of becoming obsolete in some future Matlab release. Since it worked so far, I have turned off this warning in the code above, but note that this code may well fail in some future Matlab version. If and when JavaFrame does become obsolete, be sure to look in this blog for workarounds…

Bookmark and Share