Undocumented Matlab
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT

Inactive Control Tooltips & Event Chaining

February 24, 2010 8 Comments

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

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'

>> 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.

Related posts:

  1. Matlab and the Event Dispatch Thread (EDT) – The Java Swing Event Dispatch Thread (EDT) is very important for Matlab GUI timings. This article explains the potential pitfalls and their avoidance using undocumented Matlab functionality....
  2. Additional uicontrol tooltip hacks – Matlab's uicontrol tooltips have several limitations that can be overcome using the control's underlying Java object....
  3. Spicing up Matlab uicontrol tooltips – Matlab uicontrol tooltips can be spiced-up using HTML and CSS, including fonts, colors, tables and images...
  4. Multi-line tooltips – Multi-line tooltips are very easy to set up, once you know your way around a few undocumented hiccups....
  5. Setting listbox mouse actions – Matlab listbox uicontrol can be modified to detect mouse events for right-click context menus, dynamic tooltips etc....
  6. Continuous slider callback – Matlab slider uicontrols do not enable a continuous-motion callback by default. This article explains how this can be achieved using undocumented features....
Callbacks GUI Handle graphics Hidden property Java Listener Matt Whitaker uicontrol Undocumented function
Print Print
« Previous
Next »
8 Responses
  1. Thomas Goodwin May 6, 2010 at 13:33 Reply

    I realize this isn’t an undocumented feature, but I note you’ve had a couple event/listener features explored here. I’ve implemented this construct as a means to have handle class objects interact with one another such that I can have one object contain many others. I can then pass the container into a GUI which establishes listeners to the various contained object’s events so that it will update the GUI as necessary. The problem I’ve run into is that the calls are computationally expensive (on the order of tenths of seconds or more per call), and according to the profile viewer, the time is entirely “overhead” in calling the listener – not the function the listener actually performs when the event is triggered as I was expecting. I’m not sure what is causing the latency, but it makes the whole interface crawl if the objects are highly active.

    It’s a real shame too since the event/listener construct is a really elegant way to make one’s objects interface-agnostic in a model-viewer relationship. To give you an idea of scale, I have 50 objects with 3 events each being listened to by a single viewer.

    Does anyone know of a way to speed up the event/listener overhead or make the profile cough up what ‘overhead’ is actually taking so long?

    • Yair Altman May 6, 2010 at 13:47 Reply

      @Thomas – I suspect a case of endless recursion due to the contained object’s listener triggering its parent which triggers back the child and so on until you reach your recursion limit (or something similar to that effect).

      To test this hypothesis, try triggering some entirely external Matlab function (i.e., a function outside any class) and see whether the overhead remains. If my hunch is correct, then you’ll see the overhead disappear. If not, then you’ll have a very simple example to submit with your bug report to isupport@mathworks.com.

      There are several ways to fix such recursive triggering. Here is a simple bare-bones implementation that you can use:

      function myCallbackFunc(hObject,hEventData,varargin)
        persistent inCallback
        try
          if ~isempty(inCallback)
            return;   % we're already in this callback: bail out
          else
            inCallback=1;
          end
          % The actual callback processing is done here 
        catch
           % Do some cleanup here
        end
        inCallback = [];

      function myCallbackFunc(hObject,hEventData,varargin) persistent inCallback try if ~isempty(inCallback) return; % we're already in this callback: bail out else inCallback=1; end % The actual callback processing is done here catch % Do some cleanup here end inCallback = [];

      • Thomas Goodwin May 7, 2010 at 05:30

        Weird – you might be on to something, but for the life of me I am not seeing where the recursion is happening. Actually, I’m getting the feeling “overhead” is taking into consideration the time the GUI is usingto draw/refresh objects.

        If I replace all callbacks the GUI does with a function that draws/updates nothing to the screen (writes to the command window), the overhead of the events drops to nothing. Yet if I tie it to the GUI updating callbacks, the only thing listed in the event and callback function is overhead.

        Maybe I need to look at a different way to draw the objects to the screen? I’m already re-using object handles, so I’m not sure how much more optimized it can be – haha.

        I guess I’ll keep digging. Thank you for the insight.

  2. Malcolm Lidierth January 5, 2011 at 07:37 Reply

    Of some relevance to this, I have just posted a mouse motion handle class to the FEX. MouseMotionHandler is a MATLAB class that uses a MATLAB figure’s WindowButtonMotionFcn callback as a hook to extend button motion functionality to all handle graphics child objects of a figure including axes, lines, surfaces uipanels, uicontrols etc.

    http://www.mathworks.com/matlabcentral/fileexchange/29913-mouse-motion-handler

    MouseMotionHandler provides an alternative to chaining callbacks or having a lengthy switch block in a WindowButtonMotionFcn callback to manage mouse motion effects. MouseMotionHandler puts its own callback into a figure’s WindowButtonMotionFcn property. This callback manages the servicing of callbacks for other objects in the figure. It actively determines what is beneath the mouse then invokes a user-specified callback for that object, if one is set. Specify these objects and their mouse motion callbacks using the MouseMotionHandler add and put methods. MouseMotionHandler also extends the functionality of the standard WindowButtonMotionFcn callback: its internal logic discriminates between mouse entered, moved and exited calls.

    • Yair Altman January 5, 2011 at 11:35 Reply

      @Malcolm – Thanks for the pointer. It appears to be a very useful function for heavily-laden GUIs.

      You might wish to modify the sigtools link at the bottom of your demo to a blue-underlined hyperlink. I posted an article that discussed this in 2009, as well as some code and discussion on CSSM.

  3. Multi-line tooltips | Undocumented Matlab November 17, 2011 at 17:55 Reply

    […] I have already posted several articles about how to tweak tooltip contents (here, here, and here). Today I would like to expand on one of the aspects that were already covered […]

  4. Steven Ricks February 6, 2013 at 14:43 Reply

    Can you use this method for a multi-line tooltip on an inactive control? The solution below, which works for Matlab’s tooltipstring, does not work here.

    tipString = sprintf('This is line 1nThis is line 2');
    javaMethodEDT('setToolTipText',jBtn,tipString);

    tipString = sprintf('This is line 1nThis is line 2'); javaMethodEDT('setToolTipText',jBtn,tipString);

    • Yair Altman February 6, 2013 at 14:46 Reply

      @Steven – you can use HTML formatting to achieve multi-line tooltips, as explained here.

Leave a Reply
HTML tags such as <b> or <i> are accepted.
Wrap code fragments inside <pre lang="matlab"> tags, like this:
<pre lang="matlab">
a = magic(3);
disp(sum(a))
</pre>
I reserve the right to edit/delete comments (read the site policies).
Not all comments will be answered. You can always email me (altmany at gmail) for private consulting.

Click here to cancel reply.

Useful links
  •  Email Yair Altman
  •  Subscribe to new posts (feed)
  •  Subscribe to new posts (reader)
  •  Subscribe to comments (feed)
 
Accelerating MATLAB Performance book
Recent Posts

Speeding-up builtin Matlab functions – part 3

Improving graphics interactivity

Interesting Matlab puzzle – analysis

Interesting Matlab puzzle

Undocumented plot marker types

Matlab toolstrip – part 9 (popup figures)

Matlab toolstrip – part 8 (galleries)

Matlab toolstrip – part 7 (selection controls)

Matlab toolstrip – part 6 (complex controls)

Matlab toolstrip – part 5 (icons)

Matlab toolstrip – part 4 (control customization)

Reverting axes controls in figure toolbar

Matlab toolstrip – part 3 (basic customization)

Matlab toolstrip – part 2 (ToolGroup App)

Matlab toolstrip – part 1

Categories
  • Desktop (45)
  • Figure window (59)
  • Guest bloggers (65)
  • GUI (165)
  • Handle graphics (84)
  • Hidden property (42)
  • Icons (15)
  • Java (174)
  • Listeners (22)
  • Memory (16)
  • Mex (13)
  • Presumed future risk (394)
    • High risk of breaking in future versions (100)
    • Low risk of breaking in future versions (160)
    • Medium risk of breaking in future versions (136)
  • Public presentation (6)
  • Semi-documented feature (10)
  • Semi-documented function (35)
  • Stock Matlab function (140)
  • Toolbox (10)
  • UI controls (52)
  • Uncategorized (13)
  • Undocumented feature (217)
  • Undocumented function (37)
Tags
AppDesigner (9) Callbacks (31) Compiler (10) Desktop (38) Donn Shull (10) Editor (8) Figure (19) FindJObj (27) GUI (141) GUIDE (8) Handle graphics (78) HG2 (34) Hidden property (51) HTML (26) Icons (9) Internal component (39) Java (178) JavaFrame (20) JIDE (19) JMI (8) Listener (17) Malcolm Lidierth (8) MCOS (11) Memory (13) Menubar (9) Mex (14) Optical illusion (11) Performance (78) Profiler (9) Pure Matlab (187) schema (7) schema.class (8) schema.prop (18) Semi-documented feature (6) Semi-documented function (33) Toolbar (14) Toolstrip (13) uicontrol (37) uifigure (8) UIInspect (12) uitable (6) uitools (20) Undocumented feature (187) Undocumented function (37) Undocumented property (20)
Recent Comments
Contact us
Captcha image for Custom Contact Forms plugin. You must type the numbers shown in the image
Undocumented Matlab © 2009 - Yair Altman
This website and Octahedron Ltd. are not affiliated with The MathWorks Inc.; MATLAB® is a registered trademark of The MathWorks Inc.
Scroll to top