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}); |
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.
Thanks for the great work Yair.
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
@David – have a look here: http://undocumentedmatlab.com/blog/uicontrol-callbacks/
I don’t have an immediate solution to the performance issue. JTextArea is usually performant, since it doesn’t have different fonts/styles like JEditorPane or JTextPane. Perhaps the bottleneck is in one of your callbacks or listeners?
JText**** text edits are thread-safe: they are always run on the EDT by the Swing component’s own code. A drawnow() from MATLAB might help if the screen is not updating.
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:
returns what we expect:
But, when this figure is saved and loaded again:
Now, AxisComponent properties are crippled:
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
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.
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!
@Kesh – Even though the callback properties are not available the corresponding events are still generated. You can create listeners to them with:
It is not as convienent as the callback properties, but it works.
@Donn, thanks for the tip! That’ll do for me.
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
@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.
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?
Shot in the dark
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.
@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.
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
Interesting difference. Thanks for sharing, Per
-Yair
Hi Donn
The below code did not work for the loaded figure, unlike the FocusGained callback. Is there another work around for this problem.
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.
@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’.
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.
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.
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.
@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:
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
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 toAxisCompnent
. But your method works perfect and solved my problem. I’m using the following code to get theaxisComponent
.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?
There should be a way to delete modify these comment, is there a way that I don’t know?
@Jamil – unfortunately, the current website design does not enable comment modification/deletion…
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.
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!
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
@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.Thank you so much.
For anyone whos interested (if it is ok?): the ‘javaframe’ is not enough:
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.
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?
Solution found – thanks for undoc’d documentation – brilliant.
(1) overload figure.m (place this function in your path)
(2) catch the mouse focus events (place this function in your path)
(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
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:
In R2014b, it returns:
If you have a solution for this problem, please post it.
Paul
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:
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
Same problem here 🙁 … so frustrating, as other callbacks as for instance:
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.
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…