MATLAB 2015 and later problem with figure management

9 views (last 30 days)
I have a program written in MATLAB. The program is GUIDE generated and in the opening function I set the default figure create function for MATLAB, "set(0, 'DefaultFigureCreateFcn', {@figure_createfcn, hObject})". I also do the same thing for the default figure delete function. I do this so that I can capture the handle for all figures created and add them to the main program's appdata list of open figures. Likewise, I want to be able to remove them from the list when they are deleted. There is also some formatting that is being done on any figure created to provide consistency across the board for all figures being created.
Prior to MATLAB 2015, there hasn't been any problems at all with this feature. With 2015 and later, this is causing the main program figure to be saved with the new figure during saving. When someone then opens the saved figure, you get the original intended saved figure PLUS the program figure rendered. This is a big problem. If I comment out both of these lines it works fine. Does anyone have any solution?
  1 Comment
Thomas Sawyer
Thomas Sawyer on 17 Apr 2018
Here is a simple example:
Step 1: Create a myCreateFcn.m and a myDeleteFcn.m file.
% myCreateFcn.m
function myCreateFcn(src, eventdata, hObject)
% No need to define anything here.
end
% myDeleteFcn.m
function myDeleteFcn(src, eventdata, hObject)
% No need to define anything here.
end
Step 2: Create a simple and empty GUIDE created GUI. Lets call it "createGUIDEGeneratedFigure". In the createGUIDEGeneratedFigure_OpeningFcn we need to set the root's default figure create/delete functions.
function createGUIDEGeneratedFigure_OpeningFcn(hObject, eventedata, handles, varargin)
handles.output = hObject;
set(0, 'DefaultFigureCreateFcn', {@myCreateFcn, hObject});
set(0, 'DefaultFigureDeleteFcn', {@myDeleteFcn, hObject});
set(hObject, 'Name', 'GUIDE Generated Figure');
guidata(hObject, handles);
end
Step 3: Create a function called createNonGUIDEGeneratedFigure.m file.
% createNonGUIDEGeneratedFigure.m
function fig = createNonGUIDEGeneratedFigure()
set(0, 'DefaultFigureCreateFcn', {@myCreateFcn, []});
set(0, 'DefaultFigureDeleteFcn', {@myDeleteFcn, []});
fig = figure('NumberTitle', 'off', 'Name', 'Non-GUIDE Generated Figure');
end
Step 4: Create a createAndSaveFigure.m file.
% createAndSaveFigure.m
function fig = createAndSaveFigure(fileName)
fig = figure('NumberTitle', 'off', 'Name', 'Saved Figure');
hold on;
x = -10:10;
plot(x, x.^2, '.b');
plot(x, x.^3, '.r');
saveas(fig, filename, 'fig');
end
Step 5: Create a myReset.m file for resetting the root's default functions.
% myReset.m
function myReset()
set(0, 'DefaultFigureCreateFcn', []);
set(0, 'DefaultFigureDeleteFcn', []);
end
Step 6: Lets test a happy path... (via the command window)
createNonGUIDEGeneratedFigure();
createAndSaveFigure('Problem_Free_Figure.fig');
Step 7: Reset (via the command window)
myReset();
Step 8: Here comes the good stuff! (via the command window)
createGUIDEGeneratedFigure();
createAndSaveFigure('Problematic_Figure.fig');
Step 9: Reset (via the command window)
myReset();
Now examine your new .fig files. Notice the difference in size. Now open them and you will see what my problem is.

Sign in to comment.

Accepted Answer

Jan
Jan on 18 Apr 2018

2nd answer after your (very good! +1 for this.) comment containing code to reproduce the problem:

function createGUIDEGeneratedFigure_OpeningFcn(hObject, eventedata, handles, varargin)
handles.output = hObject;
set(0, 'DefaultFigureCreateFcn', {@myCreateFcn, hObject});
...

Now you create a figure and its CreateFcn contains hObject, which is the handle to the main figure. Saving this figure seems to save the "contents" of this handle also. You see an equivalent effect, if you use load() to import the contents of a FIG file: The format is the same as a MAT file and with HG1 (until R2014b) load replied a struct containing the figure's contents. But with HG2 a Data = load('File.fig') opens the figure, because this seems to start a constructor automatically.

This means that having the handle of the "program GUI" stored in the CreateFcn of a saved figure triggers including its data to the saved file. This is not a clear description of whats going on internally, but just a guess, which matches the observation.

My suggestion for a solution remains the same: Do not inject the code to control the behavior into the figure itself (especially not for all figures by setting a root property), but let a dedicated function control the figures. This treats the figures as "data" and does not confuse it with "code".

What happens if you omit "hObject" in the CreateFcn and DeleteFcn? Then these function can use findobj() and search for a specific tag instead of accessing the "program GUI". Then I guess, that saving a figure cannot have any connection to other figures.

 DefaultFigureCreateFcn ...
 What's the intended purpose of that feature if not what I just described?

A chainsaw is a valuable and useful tool also, but its usage can have unwanted side-effects which can be considered to be "brute". Obviously setting a DefaultFigureCreateFcn in the way you do it has unwated side-effects also, which are hard to control.

  2 Comments
Thomas Sawyer
Thomas Sawyer on 18 Apr 2018
This was a very meaningful response. Thanks. I never even considered that because I'm passing in the figure handle for the main program into the create/delete functions the saved figure has access to it, even though it is never used. I can certainly replace passing it in with a call to findobj searching for the main figure's tag. I will test that now.
And about how I'm setting the root's default figure create function... I don't think a chainsaw is an accurate analogous to using utilizing the default figure create function property. The idea was to inject figure formatting that occurs behind the scene, from the context of the user without them having to do anything extra. Plus there is the figure handle management that is critical to a feature of the main program, which is done through the create/delete functions. I would really like to leverage the functionality of setting the default figure create function without applying it globally. I would prefer to only have it applied to figures generated strictly by the main program and any functions that are called by it. I have another solution that I'm going to investigate for feasibility.
I just find it odd how the behavior has changed and doesn't provide backwards compatibility. I guess by now I should expect this as it has happened time and time again with other functions such as unique, ismember, and several others.
Thomas Sawyer
Thomas Sawyer on 18 Apr 2018
That fixes it! Thanks Jan. You have been a big help.

Sign in to comment.

More Answers (2)

Jan
Jan on 16 Apr 2018

Setting the DefaultFigureCreateFcn globally is brute. Note that it is applied for all dialog boxes also. Such global settings have the drawback to have global effects. I do not understand, why this influences the saving of a figure.

I suggest to avoid setting the DefaultFigureCreateFcn, but to use a special function to open figures, e.g.:

function H = myFigure(varargin)
H = figure(varargin{:}, 'DeleteFcn', @yourDeleteFcn);
figure_createfcn(H, [], H);
end

Now replace all calls of figure() by myFigure(). Now the wanted functions run at creation and deleting without global effects.

  3 Comments
Jan
Jan on 17 Apr 2018
Overloading the figure function and setting a CreateFcn globally is both a brute method, as you can see by the effects already. You influence each msgbox call as well as errordlg, warndlg, listdlg also. You cannot start other tools also, when the use an own layout mechanism. Even the demos of the documentation might fail.
That you struggle now with backward compatibility and unexpected side-effects are the typical effects of using global variables or settings. The actual problem happened, when you decided to choose this method, although it worked for a while.
I suggest to remove these global dependencies. But beside this, there seems to be another problem:
This problem also occurs when you set the create/delete
function on any particular figure.
And the actual problem is:
With 2015 and later, this is causing the main program figure
to be saved with the new figure during saving.
What does "save" mean here exactly? How do you save what?
When someone then opens the saved figure, you get the original
intended saved figure PLUS the program figure rendered.
This means, that opening the saved figure calls the CreateFcn, which opens the "program figure" (what ever this is) in addition, what is not wanted. I guess, this is triggered inside figure_createfcn(). Note that you defined it as:
'CreateFcn', {@figure_createfcn, hObject}
where hObject is a certain handle. After saving, closing and re-opening the figures, this handle might be invalid. Without seeing the code of figure_createfcn it is not clear, why this function opens the "program figure", but using the debugger you should find the reason for this.
If you save a figure, whose layout is controlled by the CreateFcn, this function is called also, when the saved figure is opened on any other computer. Therefore it is a bad idea, to use this method instead of an explicit method. Injecting a kind of autonomous intelligence to the CreateFcn and DeleteFcn let these figure apply this power even if you do not want them to do so.
I had a problem in a roughly similar field: I have to open a set of figures, e.g. 4. Each figure has a navigator to get to the next or previous figure, a button to close or print the complete set and the maximization concerns all figures also. Therefore I create the figures with a specific function and store the handles of all figures in the ApplicationData of each one. Calling the control for any action provides the current figure handle and the set of all figure handles to a specific function. This applies the action to all figures, which are currently existing (so deleting a single figure does not cause errors). In addition it is checked, if the current figure handle is part of the set of handles. Then saving a figure as fig file and re-opening it does not cause troubles with assuming, that the figure is still part of a figure set.
I'm not sure, how I could help you beside these general ideas. I guess the problem is hidden inside your figure_createfcn(), but without seeing this code, I cannot guess the cause of the observed behavior.
Thomas Sawyer
Thomas Sawyer on 17 Apr 2018
I still don't know if it is accurate to say that utilizing the DefaultFigureCreateFcn feature is handling something in a brute fashion. It is an effective way of doing some pre-figure creation handling. What's the intended purpose of that feature if not what I just described?
About the dialogs affected by the create function, I do have some handling for any kind of dialog and target specifically figures for plotting. This relies on some loose assumptions but as long as those assumptions are enforced by predetermined practices, it's not a problem. And it hasn't ever been a problem, which is why it's probably still done this way.
In reference to "What does "save" mean here exactly? How do you save what?"... The heart of this problem occurs when you save a figure, either via saveas, savefig, or the figure's save menuitem. If the figure has it's create or delete function defined, it saves the figure and the main program's figure in to a single .fig file. In my case, a .fig file that should only be about 3 KB ends up being 1,424 KB because of all the data that is saved from the main program's figure GUI.
I have thoroughly debugged it. During saving of the figure, once the program counter passes the savefig function and enters a write function, debugging becomes useless as MATLAB has hidden what happens, whether it's because that code has been implemented as a mex function or some other reason. I cannot see what is happening beyond that point. However, if you open GUIDE in 2015 or later and create a blank gui (an analogous for the "main program GUI"), then set the root's DefaultFigureCreateFcn via the opening function, any figure you create after launching that GUI will execute the create function as expected with no unintended consequences. As soon as you try to save that figure you start running into this problem. No matter how you save it, savefig ends up getting called which calls the write function which is where the calamity is happening.
And yes, I've already experienced the createFcn being called when you open the fig, and it's resulting errors. I've resolved that by removing the createFcn from the figure as the final step in the createFcn.
As my example in a previous paragraph illustrates, it's not a problem with the contents of my createFcn. It has everything to do with how MATLAB is handling the saving of figures when they have these functions defined. How or why it's happening is beyond me which is why I'm here. I started with what appeared to be a complex problem. I reduced it down to a very simple example that demonstrated the problem. You can have an empty createFcn/deleteFcn and the problem still occurs.

Sign in to comment.


Walter Roberson
Walter Roberson on 17 Apr 2018
You can store double() of the handle and you can get back to the handle with handle()
  6 Comments
Walter Roberson
Walter Roberson on 18 Apr 2018
You have
function createGUIDEGeneratedFigure_OpeningFcn(hObject, eventedata, handles, varargin)
handles.output = hObject;
set(0, 'DefaultFigureCreateFcn', {@myCreateFcn, hObject});
set(0, 'DefaultFigureDeleteFcn', {@myDeleteFcn, hObject});
set(hObject, 'Name', 'GUIDE Generated Figure');
guidata(hObject, handles);
end
Here hObject is a graphics object, probably the figure handle. You are storing a copy of the figure handle in handles.output . You then update the figure guidata to include that handles.output . In your code hObject is not a figure number: it is an object oriented object.
Then later you use
saveas(fig, filename, 'fig');
this saves the entire figure property, including its guidata, and that guidata refers to the saved figure handle so it needs to save the entire figure as well, so that when the figure named by the file is reloaded that it is recreated to the point where the guidata is a valid figure so that retrieving handles.output would once again be valid.
Now if you were to change that code to
handles.output = double(hObject);
then only the figure number would be saved in handles.output, as a numeric value. Saving the numeric value in the fig would just be another number with no special treatment. When the .fig was reloaded, it would just be reloading a number, with no special treatment. Afterwards, handles.output of the recreated figure would refer to the old figure number, which might be the number of a non-existent figure or might point to a random existing figure that might or might not have anything to do with the figure just loaded. If you wanted to refer to the random figure you could
if ishghandle(handles.output)
fig = handle(handles.output);
else
fprintf('so sorry, saved figure number refers to something that does not exist\n');
fig = [];
end
You should consider whether you need to store the figure handle in handles.output at all.
Another thing you can consider is to change
saveas(fig, filename, 'fig');
to
saveas(fig, filename, 'fig', 'compact');
That will save the figure in a smaller form by eliminating the backwards compatibility portions of the .fig
Thomas Sawyer
Thomas Sawyer on 19 Apr 2018
Your answer was very close to the actual problem. The hObject variable (in the openingFcn) is a handle to the main program's figure. I'm not trying to save this at all. Inside the create/delete functions, the first input, "src", is the handle to the figure for saving and storing. I have a button in my main program that closes all open figures. Ideally I only want to close figures created via (or through) the main program. This is why I accumulate a list of them in the figure create function. It is also important to note that there are users that write functions to plot data. These functions are executed through this program. I need to be able to capture their figure handles, save them, and format the figures as well.
However, when I set the 'DefaultFigureCreateFcn' and the delete one too, I pass in hObject (main program figure handle). Because the create function handle gets set as a property of all figures created afterwards, they now have a reference back to the main program's figure through it's handle (hObject is the last input to the create/delete functions). Because of that, the main program's figure gets included in the .fig file during saving.

Sign in to comment.

Categories

Find more on Specifying Target for Graphics Output in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!