Detecting window focus events

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…

Addendum Aug 21, 2015: In in HG2 (R2014b onward), setting the Focus events (FocusGained, FocusLost etc.) on the AxisComponent does not work. Instead, we can simply set the callbacks on the AxisComponent’s direct child, as follows:

set(jAxis.getComponent(0),'FocusGainedCallback',{@myMatlabFunc,hFig});
Categories: Figure window, GUI, Hidden property, Java, Medium risk of breaking in future versions

Tags: , , , , ,

Bookmark and SharePrint Print

41 Responses to Detecting window focus events

  1. Donn Shull says:

    I think I found this in some of your code on the MathWorks file exchange, anyway if you don’t like having to turn warnings off you can use the undocumented use of handle as a function to get the underlying figure object and access the JavaFrame property from there without generating a warning.

    hFig = figure;
    obj = handle(hFig);
    jFig = obj.JavaFrame;

    Thanks for the great work Yair.

  2. David says:

    Hi,

    I am working with a java component in matlab. I have wrapped a JTextArea and added a scroll and some features to it. I have two problems that I would like to solve:

    1) I need a callback to use when something changes in the text area. I would like to use this to implement a UNDO, crl+z, feature. Do you know a suitable callback for this?

    2) When I work with the text area it is very slow to render and repaint. If i delete some text, paste large blocks etc it might not render. I have tried calling repaint without success. Do you have a solution for this?

    -David

  3. Kesh says:

    Hey Yair,

    It’s me again. I got a funny behavior when trying to access figure focus callbacks of saved figure. Here’s an example:

    Open a plain figure with nothing in it and get the AxisComponent properties:

    figure
    jFig = get(handle(gcf), 'JavaFrame');
    jAxis = jFig.getAxisComponent;
    get(jAxis)

    returns what we expect:

    	UIJGraphics = []
    	AccessibleContext = [ (1 by 1) java.awt.Canvas$AccessibleAWTCanvas array]
    	Background = [0.92549 0.913725 0.847059]
    	BufferStrategy = []
    	Component = [ (1 by 1) com.mathworks.hg.peer.FigureAxisComponentProxy$_AxisCanvas array]
    	ComponentLightweight = off
    	Enabled = on
    	Focusable = on
    	Font = [ (1 by 1) javax.swing.plaf.FontUIResource array]
    	Foreground = [0 0 0]
    	Graphics = [ (1 by 1) com.mathworks.hg.peer.AxisCanvas$HGDebugGraphics array]
    	Name = fAxisComponentProxy
    	NativeWindowHandle = [0]
    	NativeWindowHandleValid = off
    	MouseWheelMovedCallback = 
    	MouseWheelMovedCallbackData = []
     
    	KeyTypedCallback = 
    	KeyTypedCallbackData = []
     
    	BeingDeleted = off
    	ButtonDownFcn = 
    	Children = []
    	Clipping = on
    	CreateFcn = 
    	DeleteFcn = 
    	BusyAction = queue
    	HandleVisibility = on
    	HitTest = on
    	Interruptible = on
    	Parent = []
    	Selected = off
    	SelectionHighlight = on
    	Tag = 
    	Type = com.mathworks.hg.peer.FigureAxisComponentProxy$_AxisCanvas
    	UIContextMenu = []
    	UserData = []
    	Visible = on

    But, when this figure is saved and loaded again:

    hgsave test
    hgload test
     
    jFig1 = get(handle(gcf), 'JavaFrame');
    jAxis1 = jFig1.getAxisComponent;
    get(jAxis1)

    Now, AxisComponent properties are crippled:

                    UIJGraphics: []
              AccessibleContext: [1x1 java.awt.Canvas$AccessibleAWTCanvas]
                     Background: [1x1 javax.swing.plaf.ColorUIResource]
                 BufferStrategy: []
                      Component: [1x1 com.mathworks.hg.peer.FigureAxisComponentProxy$_AxisCanvas]
           ComponentLightweight: 0
                        Enabled: 1
                      Focusable: 1
                           Font: [1x1 javax.swing.plaf.FontUIResource]
                     Foreground: [1x1 javax.swing.plaf.ColorUIResource]
                       Graphics: [1x1 com.mathworks.hg.peer.AxisCanvas$HGDebugGraphics]
                           Name: 'fAxisComponentProxy'
             NativeWindowHandle: 0
        NativeWindowHandleValid: 0

    The callback fields are no longer accessible among many others. Do you know why this happens and/or how to work around this problem to get to the focus callbacks?

    TIA

    • Kesh says:

      Oops, I mistakenly placed “” text in MATLAB code block. For the first get(jAxis) returned values, there are many other Callback Fields between “MouseWheelMovedCallbackData = []” and ” KeyTypedCallback = “

    • @Kesh – this is an undocumented bug in the way Matlab loads FIG files – it loads Java objects without the ‘CallbackProperties’ argument (or some equivalent action), causing the object’s callbacks not to be visible in Matlab.

      Apparently, only GUI figures and components created in run-time have accessible callbacks. This sounds odd, and I would love to learn how to bypass this limitation, but at the moment I don’t know if this is possible.

    • Kesh says:

      That’s a bummer. I wonder if there is any way for us to “attach” CallbackProperties after FIG is loaded… If you learn a way around for it, please let me know!

    • Donn says:

      @Kesh – Even though the callback properties are not available the corresponding events are still generated. You can create listeners to them with:

         l = handle.listener(handle(jAxis), 'FocusGained', @callback)

      It is not as convienent as the callback properties, but it works.

    • Kesh says:

      @Donn, thanks for the tip! That’ll do for me.

  4. per isakson says:

    Hello Yair,

    This function to detect when a figure gain focus is tempting to use and I tried it. However, I run into one problem with clearing objects. causes a burst of
    “Warning: Objects of ‘xyz’ class exist. Cannot clear this class or any of its super-classes.”

    I use your code in a method of a class. The function invoked by the event is a nested function defines in the same method.

    This might not be a big problem once the my code is correct. However, during debugging it forces me to restart Matlab often. Changes in my classes are not used unless the old object are cleared.

    Is there a work around – or did I miss something?

    Best regards
    per

    • @per – This happens because the Java object (jAxis) keeps a reference to your class and so the class cannot be cleared. Try closing all relevant figure windows and/or using clear java before you attempt to clear the Matlab class instances.

      Alternately, you can use the handle.listener approach suggested by Donn Shull above. You may possibly need to delete all the listener handles before clearing the Matlab class instances, but it may even work without these deletions.

      Please let us all know if any of these tricks actually solved the problem.
      -Yair

    • per isakson says:

      @Yair – No, clear classes still triggers the burst of warnings.

      Now, I have tried the construct that you propose in the post, the variant proposed by Donn Shull and the handle.listener approach (see code snippet below) also proposed by Donn Shull. In all three cases my nested function, FocusGained, is called when I click on the figure. Deleting the figure and applying clear java, clear all, clear classes (with and without lear all) after each of the three puts Matlab into an “unexpected” state: inmem reports an “empty” memory and clear classes produces the burst of warnings. See below.

      >> inmem
      ans = 
          'workspacefunc'
      >> clear classes
      Warning: Objects of 'SpringHarp' class exist.  Cannot clear this class or any of its super-classes. 
      Warning: Objects of 'Editbox' class exist.  Cannot clear this class or any of its super-classes. 
      etc.

      Below is an excerpt from a method of the class. I tried with and without the line “this”, which is the internal name of th object. That does not make any difference. I still have to create the jAxis object or did I miss something?

      jFig = get( handle( this.hgh ), 'JavaFrame');
      jAxis = jFig.getAxisComponent;           
      hListener = handle.listener( handle( jAxis ) , 'FocusGained', { @FocusGained, 'kk' } );
      %
      function    FocusGained( varargin )    
         this;
         y=1;%#ok    
         z=1;%#ok
      end

      Shot in the dark

      % jFig = get( handle( this.hgh ), 'JavaFrame');
      % jAxis = jFig.getAxisComponent;           
      hListener = handle.listener( handle( this.hgh ), 'FocusGained', { @FocusGained, 'kk' } );

      this.hgh is the figure handle graphic handle. This did not cause any errors or warnings, but clicking the figure did not trigger the callback. This also left the Matlab with inmem reporting empty and clear classes causing the burst of warnings.

      / per

      PS. I run R2010a on Windows 7 both with 64bit.

    • per isakson says:

      @Yair – I should have known better than testing with a nested function!

      Now, I have tested a with a subfunctions as callbackfunction, i.e. moved the function, FocusGained, to after the end of classdef. The object, named this, is passed as an argument.

       set( jax, 'FocusGainedCallback', { @FocusGained, this } );

      The problem remains with the construct that you propose in the post, including with the variant proposed by Donn Shull. However, the handle.listener approach seems to work, i.e. clear classes clears the objects. There is no burst of warnings. clear java is not needed.

      / per

  5. Interesting difference. Thanks for sharing, Per
    -Yair

  6. Jamil Onaran says:

    Hi Donn
    The below code did not work for the loaded figure, unlike the FocusGained callback. Is there another work around for this problem.

    hgsave test
    hgload test
     
    jFig1 = get(handle(gcf), 'JavaFrame');
    jAxis1 = jFig1.getAxisComponent;
    l = handle.listener(handle(jAxis1),'MouseMovedCallback',@(a,b) disp('ab'))

    Thanks

    • @Jamil – this may be the same issue as Kesh mentioned in comments above – please see my answer there. In short, Java callbacks don’t work as advertised for hgloaded figures – only for figures created in runtime. This appears to be due to an internal Matlab bug, but I would not hold my breath for it to be fixed…

      Try using Donn’s suggested workaround of using UDD’s handle.listener function, which are explained in detail in this article.

    • Donn Shull says:

      @Jamil – Note that when using handle.listener you need to use the event name which in your case is ‘MouseMoved’ rather than the callback property name which is ‘MouseMovedCallback’.

      hgsave test
      hgload test
       
      jFig1 = get(handle(gcf), 'JavaFrame');
      jAxis1 = jFig1.getAxisComponent;
      l = handle.listener(handle(jAxis1),'MouseMoved',@(a,b) disp('ab'))
    • Jamil Onaran says:

      Thanks,
      I figured it out.
      toolbox\matlab\uitools\@uitools\@uimode\createuimode.m file cuases this problem, here is the corresponding code. The callbacks are usable until this line.

          hAxisComponent = handle(hFrame.getAxisComponent);

      If you use get function for the axis component before this line or set the figure visible, the callbacks would be working. I don’t exactly know what handle is doing on invisible figures.

    • Jamil Onaran says:

      Hi again,
      Actually this is not a visibility issue.
      I have a test code below, using handles on AxisComponent will cause this error if we try to install a a callback.

      %% Create a new figure
      close all
      hFig = figure;
      %% Run this to generate an error
      Frame  = get(hFig,'JavaFrame');
      hFrame = handle(Frame);
      hAxisComponent = handle(hFrame.getAxisComponent);
      %% Get java frame and install a callback
      Frame  = get(hFig,'JavaFrame');
      AxisComponent = Frame.getAxisComponent;
      set(AxisComponent,'MouseMovedCallback',@(a,b) disp('MMC'))
    • Donn Shull says:

      @Jamil – To summarize: To have callback properties the UDD peer must be created in the javahandle_withcallbacks package. In your code above you should use:

      Frame  = get(hFig,'JavaFrame', 'CallbackProperties');

      Then you will not generate errors. As Yair has explained hgload will create the UDD peer in the javahandle package which does not have callback properties. If you wish to use hgsave and hgload then you should design your code using handle.listener to process events. You need to take care to use the proper event names and provide persistent storage for your listener objects.

      Donn

    • Jamil Onaran says:

      Hi Donn,
      I got it, but I just demonstrated what createuimode.m does to disable the callback of axis component. After calling handle function for axisComponenent, we can not assign any callback to AxisCompnent. But your method works perfect and solved my problem. I’m using the following code to get the axisComponent.

      mde = com.mathworks.mde.desk.MLDesktop.getInstance;
      figName = get(hObject,'name');
      if strcmpi(get(hObject,'number'),'on')
          figName = regexprep(['Figure ' num2str(hObject) ': ' figName],': $','');
      end
      jFig = []; iTry = 10;
      while isempty(jFig) &amp;&amp; iTry
          jFig = mde.getClient(figName);
          iTry = iTry-1;
          pause(0.1);
      end
      axisComponent = jFig.getComponent(0).getComponent(0).getComponent(1);
      axisComponent = handle(axisComponent, 'CallbackProperties');

      This is an alternative method to get the axis component, but we can not obtain JavaFrame object from this code. I wish Matlab won’t stop supporting the javaframe.
      Do you have other work arounds if they do so?

  7. Jamil Onaran says:

    There should be a way to delete modify these comment, is there a way that I don’t know?

  8. julien says:

    Hi !
    I would like to launch a callback when mouse enters or exits a figure. This can be achieved by setting MouseEnteredCallback and MouseExitedCallback on axisComponent. However, when figure’s contentPane is completely filled with controls (uicontrols or JComponents), this does not work any more. If I well understand, this is because components’ panel is above axisComponent.
    Is there another way to detect when mouse enters or exits a figure ? I played around with other JavaFrame hierarchy containers, but I did not find a solution.
    I could set all components MouseEnteredCallback and triggers my callback when figure has not the focus but this is rather heavy…
    Would you have another idea?

    • You may find the MouseMotionHandler class in Project Waterloo does what you need.

      It uses the standard TMW WindowButtonMotion callback as a hook but discriminates between mouse exits, enters and moves using internal logic. You can still use MouseEnteredCallbacks, ButtonDownCallbacks etc in tandem.

    • julien says:

      Thanks for your help, Malcolm. This is a good solution indeed, except when the figure is populated with JComponents (with javacomponent function) for which WindowButtonMotionFcn is not executed when mouse moves over them…
      However, your MouseMotionHandler class is a very nice utility!

  9. Bluesmaster says:

    Is it possible to listen to minimized/ maximized events
    I tried a lot but why this task is so much more difficult than mousemove or focus etc?

    regards

    Bluesmaster

    • Yair Altman says:

      @Bluemaster – yes, this is possible. I discuss it in Section 7.3.7 of my Matlab-Java programming book. Specifically, look at the WindowIconifiedCallback and WindowDeiconifiedCallback properties of the FigureFrame proxy object.

    • Bluesmaster says:

      Thank you so much.

      For anyone whos interested (if it is ok?): the ‘javaframe’ is not enough:

      f = figure;
      j = get( f , 'javaframe' );
      drawnow;
      w = j.fHG1Client.getWindow();
       
      set( w , 'WindowIconifiedCallback'   , @(~,~) disp('WindowIconifiedCallback')   );
      set( w , 'WindowDeiconifiedCallback' , @(~,~) disp('WindowDeiconifiedCallback') );
  10. SA says:

    Any idea if it is possible to intercept existing callbacks? I’d be interested in disabling matlabs’ ability to change the currentfigure setting when it receives mouse focus. I would like to return to the old behaviour where figure() sets the currentfigure and the mouse doesn’t.

    • SA says:

      Can I modify the question – I’d like to capture all focus events for all figures (then I can reset “currentfigure”). Is there a way to do this?

    • SA says:

      Solution found – thanks for undoc’d documentation – brilliant.

      (1) overload figure.m (place this function in your path)

      %% fix the annoying focus follows mouse bug
      %% (c) GPL SA 2013
      function h=figure(n);
          global hasfocus;
          if(~exist('n'))
              n=1;
              end;
          h = builtin('figure',n);
          jFig = get(h,'JavaFrame');
          jAxis = jFig.getAxisComponent;
          set(jAxis,'FocusGainedCallback',{@setfocus,h});
          hasfocus = h;

      (2) catch the mouse focus events (place this function in your path)

      %% fix the annoying focus follows mouse bug
      %% (c) GPL SA 2013
      function setfocus(jAxis, jEventData, hFig)
          global hasfocus;
          set(0,'currentfigure',hasfocus)
      end

      (3) If you are bothered by warning messages spamming up your console put this line in your matlab start up files:

      warning off MATLAB:dispatcher:nameConflict

      Works for me on matlab 2007something

  11. Paul Andrews says:

    Dear all,

    The mechanism described above for detecting windows focus event does not work in R2014b. I believe the issue is related in changes in the figure’s AxisComponent. In R2013b, jFrame.getAxisComponent returns:

    com.mathworks.hg.peer.FigureAxisComponentProxy$_AxisCanvas[fAxisComponentProxy,0,0,560x420]

    In R2014b, it returns:

    com.mathworks.hg.peer.HeavyweightLightweightContainerFactory$FigurePanelContainerLight[fFigurePanel,0,0,560x420,layout=...]

    If you have a solution for this problem, please post it.

    Paul

    % Snippet used to test focus callbacks
    hFig = figure('name', 'FocusGainTest');  % etc. - prepare the figure
     
    % Get the underlying Java reference
    warning off MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame
    jFrame = get(hFig, 'JavaFrame');
    hFrame = handle(jFrame,'CallbackProperties');
     
    jAxis = jFrame.getAxisComponent 
    hAxis = handle(jAxis,'CallbackProperties');
     
    % Set the focus event callback
    set(hAxis,'FocusGainedCallback', @(a,b) disp('FocusGainedCallback'));
    set(hAxis,'FocusLostCallback', @(a,b) disp('FocusLostCallback'));
    • Paul, I’m facing the same problem as you have in R2014b. By any chance, did you find any solution to this?

      Yvon

    • @Paul & @Yvon – in HG2 (R2014b onward), you can simply set the Focus events (FocusGained, FocusLost etc.) on the direct child of the AxisComponent, as follows:

      set(jAxis.getComponent(0),'FocusGainedCallback',{@myMatlabFunc,hFig});
  12. Fabio says:

    It seem as the callbacks don’t work when an axes is inside the figure (as said also here: https://it.mathworks.com/matlabcentral/answers/284809-focusgainedcallback-doesn-t-work-once-figure-has-axes-2015b ), any solution? Thanks in advance

    • Daniel Castano says:

      Same problem here :-( … so frustrating, as other callbacks as for instance:

      set(jAxis.getComponent(0),'MouseMovedCallback',@(x,y)disp('ab'));

      work fine after adding axes to the figure. I really hope that somebody comes up with a solution.

    • Well, I found a solution (R2016b) by trial and error for very simple situations, i.e., just one axes inside the figure.
      I just check the number of components in jAxis and grab the last one.

      hFig = figure; 
      h = axes(hFig);
      plot(rand(100,1),'Parent',h);
      jFig = get(hFig, 'JavaFrame');
      jAxis = jFig.getAxisComponent;
       
      n = jAxis.getComponentCount;
      disp(n);
      jAxis.grabFocus();
      drawnow; % will not work without this
      n = jAxis.getComponentCount;
      disp(n);
       
      set(jAxis.getComponent(n-1),'FocusGainedCallback',@(x,y)disp('in focus'));
      set(jAxis.getComponent(n-1),'FocusLostCallback',  @(x,y)disp('... out'));

      Note that this is mainly cargo cult: I have no idea why I need to grab the focus and force a redraw for this to work. Still would be thankful if somebody comes up with a better, explainable solution, because the one I suggest here does not work for more complicated situations, such as GUIs with multiple panels and changing number of elements…

    • Alexander K. says:
      % R2018a
      hFig = figure; 
      hAxes = axes(hFig);
      plot(rand(100,1),'Parent',hAxes);
       
      JavaSceneServerGLJPanel=get(hAxes.NodeParent.JavaComponent,'Component')
      hJavaSceneServerGLJPanel=handle(JavaSceneServerGLJPanel,'CallbackProperties');
       
      set(hJavaSceneServerGLJPanel,'FocusGainedCallback',@(x,y)disp('in focus'));
      set(hJavaSceneServerGLJPanel,'FocusLostCallback',  @(x,y)disp('... out'));
    • Alexander K. says:
      % R2018a
      hFig = figure; 
      hAxes = axes(hFig);
      plot(rand(100,1),'Parent',hAxes);
       
      % JavaSceneServerGLJPanel=get(hAxes.NodeParent.JavaComponent,'Component')
      % hJavaSceneServerGLJPanel=handle(JavaSceneServerGLJPanel,'CallbackProperties');
      %  
      % set(hJavaSceneServerGLJPanel,'FocusGainedCallback',@(x,y)disp('in focus'));
      % set(hJavaSceneServerGLJPanel,'FocusLostCallback',  @(x,y)disp('... out'));
       
      drawnow
       
      jf=get(hFig,'JavaFrame');
      jw=handle(jf.getFigurePanelContainer.getTopLevelAncestor,'CallbackProperties');
       
      set(jw,'WindowGainedFocusCallback',@(x,y)disp('in focus'));
      set(jw,'WindowLostFocusCallback',  @(x,y)disp('... out'));

Leave a Reply

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