AppDesigner’s mlapp file format

Six years ago, I exposed the fact that *.fig files are simply MAT files in disguise. This information, in addition to the data format that I explained in that article, can help us to introspect and modify FIG files without having to actually display the figure onscreen.

Matlab has changed significantly since 2010, and one of the exciting new additions is the AppDesigner, Matlab’s new GUI layout designer/editor. Unfortunately, AppDesigner still has quite a few limitations in functionality and behavior. I expect that this will improve in upcoming releases since AppDesigner is undergoing active development. But in the meantime, it makes sense to see whether we could directly introspect and potentially manipulate AppDesigner’s output (*.mlapp files), as we could with GUIDE’s output (*.fig files).

A situation for checking this was recently raised by a reader on the Answers forum: apparently AppDesigner becomes increasingly sluggish when the figure’s code has more than a few hundred lines of code (i.e., a very simplistic GUI). In today’s post I intend to show how we can explore the resulting *.mlapp file, and possibly manipulate it in a text editor outside AppDesigner.

Matlab's new AppDesigner (a somewhat outdated screenshot)

Matlab's new AppDesigner (a somewhat outdated screenshot)


The MLAPP file format

Apparently, *.mlapp files are simply ZIP files in disguise (note: not MAT files as for *.fig files). A typical MLAPP’s zipped contents contains the following files (note that this might be a bit different on different Matlab releases):

  • [Content_Types].xml – this seems to be application-independent:
    <?xml version="1.0" encoding="UTF-8" standalone="true"?>
    <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
       <Default Extension="mat" ContentType="application/vnd.mathworks.matlab.appDesigner.appModel+mat"/>
       <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
       <Default Extension="xml" ContentType="application/vnd.mathworks.matlab.code.document+xml;plaincode=true"/>
       <Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/metadata/coreProperties.xml"/>
       <Override ContentType="application/vnd.mathworks.package.coreProperties+xml" PartName="/metadata/mwcoreProperties.xml"/>
       <Override ContentType="application/vnd.mathworks.package.corePropertiesExtension+xml" PartName="/metadata/mwcorePropertiesExtension.xml"/>
    </Types>
  • _rels/.rels – also application-independent:
    <?xml version="1.0" encoding="UTF-8" standalone="true"?>
    <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
       <Relationship Type="http://schemas.mathworks.com/matlab/code/2013/relationships/document" Target="matlab/document.xml" Id="rId1"/>
       <Relationship Type="http://schemas.mathworks.com/package/2012/relationships/coreProperties" Target="metadata/mwcoreProperties.xml" Id="rId2"/>
       <Relationship Type="http://schemas.mathworks.com/package/2014/relationships/corePropertiesExtension" Target="metadata/mwcorePropertiesExtension.xml" Id="rId3"/>
       <Relationship Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="metadata/coreProperties.xml" Id="rId4"/>
       <Relationship Type="http://schemas.mathworks.com/appDesigner/app/2014/relationships/appModel" Target="appdesigner/appModel.mat" Id="rId5"/>
    </Relationships>
  • metadata/coreProperties.xml – contains the timestamp of figure creation and last update:
    <?xml version="1.0" encoding="UTF-8" standalone="true"?>
    <cp:coreProperties xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties">
       <dcterms:created xsi:type="dcterms:W3CDTF">2016-08-01T18:20:26Z</dcterms:created>
       <dcterms:modified xsi:type="dcterms:W3CDTF">2016-08-01T18:20:27Z</dcterms:modified>
    </cp:coreProperties>
  • metadata/mwcoreProperties.xml – contains information on the generating Matlab release:
    <?xml version="1.0" encoding="UTF-8" standalone="true"?>
    <mwcoreProperties xmlns="http://schemas.mathworks.com/package/2012/coreProperties">
       <contentType>application/vnd.mathworks.matlab.app</contentType>
       <contentTypeFriendlyName>MATLAB App</contentTypeFriendlyName>
       <matlabRelease>R2016a</matlabRelease>
    </mwcoreProperties>
  • metadata/mwcorePropertiesExtension.xml – more information about the generating Matlab release. Note that the version number is not exactly the same as the main Matlab version number: here we have 9.0.0.328027 whereas the main Matlab version number is 9.0.0.341360. I do not know whether this is checked anywhere.
    <?xml version="1.0" encoding="UTF-8" standalone="true"?>
    <mwcoreProperties xmlns="http://schemas.mathworks.com/package/2014/corePropertiesExtension">
       <matlabVersion>9.0.0.328027</matlabVersion>
    </mwcoreProperties>
  • appdesigner/appModel.mat – This is a simple MAT file that holds a single Matlab object called “appData” (of type appdesigner.internal.serialization.app.AppData) the information about the uifigure, similar in concept to the *.fig files generated by the old GUIDE:
    >> d = load('C:\Yair\App3\appdesigner\appModel.mat')
    Warning: Functionality not supported with figures created with the uifigure function. For more information,
    see Graphics Support in App Designer.
    (Type "warning off MATLAB:ui:uifigure:UnsupportedAppDesignerFunctionality" to suppress this warning.)
     
    d = 
        appData: [1x1 appdesigner.internal.serialization.app.AppData]
     
    >> d.appData
    ans = 
      AppData with properties:
     
          UIFigure: [1x1 Figure]
          CodeData: [1x1 appdesigner.internal.codegeneration.model.CodeData]
          Metadata: [1x1 appdesigner.internal.serialization.app.AppMetadata]
        ToolboxVer: '2016a'
     
    >> d.appData.CodeData
    ans = 
      CodeData with properties:
     
        GeneratedClassName: 'App3'
                 Callbacks: [0x0 appdesigner.internal.codegeneration.model.AppCallback]
                StartupFcn: [1x1 appdesigner.internal.codegeneration.model.AppCallback]
           EditableSection: [1x1 appdesigner.internal.codegeneration.model.CodeSection]
                ToolboxVer: '2016a'
     
    >> d.appData.Metadata
    ans = 
      AppMetadata with properties:
     
        GroupHierarchy: {}
            ToolboxVer: '2016a'
  • matlab/document.xml – this file contains a copy of the figure’s classdef code in plain-text XML:
    <?xml version="1.0" encoding="UTF-8"?>
    <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
       <w:body>
          <w:p>
             <w:pPr>
                <w:pStyle w:val="code"/>
             </w:pPr>
             <w:r>
                <w:t>
                   <![CDATA[classdef App2 < matlab.apps.AppBase % Properties that correspond to app components properties (Access = public) UIFigure matlab.ui.Figure UIAxes matlab.ui.control.UIAxes Button matlab.ui.control.Button CheckBox matlab.ui.control.CheckBox ListBoxLabel matlab.ui.control.Label ListBox matlab.ui.control.ListBox end methods (Access = public) function results = func(app) % Yair 1/8/2016 end end % App initialization and construction methods (Access = private) % Create UIFigure and components function createComponents(app) % Create UIFigure app.UIFigure = uifigure; app.UIFigure.Position = [100 100 640 480]; app.UIFigure.Name = 'UI Figure'; setAutoResize(app, app.UIFigure, true) % Create UIAxes app.UIAxes = uiaxes(app.UIFigure); title(app.UIAxes, 'Axes'); xlabel(app.UIAxes, 'X'); ylabel(app.UIAxes, 'Y'); app.UIAxes.Position = [23 273 300 185]; % Create Button app.Button = uibutton(app.UIFigure, 'push'); app.Button.Position = [491 378 100 22]; % Create CheckBox app.CheckBox = uicheckbox(app.UIFigure); app.CheckBox.Position = [491 304 76 15]; % Create ListBoxLabel app.ListBoxLabel = uilabel(app.UIFigure); app.ListBoxLabel.HorizontalAlignment = 'right'; app.ListBoxLabel.Position = [359 260 43 15]; app.ListBoxLabel.Text = 'List Box'; % Create ListBox app.ListBox = uilistbox(app.UIFigure); app.ListBox.Position = [417 203 100 74]; end end methods (Access = public) % Construct app function app = App2() % Create and configure components createComponents(app) % Register the app with App Designer registerApp(app, app.UIFigure) if nargout == 0 clear app end end % Code that executes before app deletion function delete(app) % Delete UIFigure when app is deleted delete(app.UIFigure) end end end]]>
                </w:t>
             </w:r>
          </w:p>
       </w:body>
    </w:document>

I do not know why the code is duplicated, both in document.xml and (twice!) in appModel.mat. On the face of it, this does not seem to be a wise design decision.

Editing MLAPP files outside AppDesigner

We can presumably edit the app in an external editor as follow:

  1. Open the *.mlapp file in your favorite zip viewer (e.g., winzip or winrar). You may need to rename/copy the file as *.zip.
  2. Edit the contents of the contained matlab/document.xml file in your favorite text editor (Matlab’s editor for example)
  3. Load appdesigner/appModel.mat into Matlab workspace.
  4. Go to appData.CodeData.EditableSection.Code and update the cell array with the lines of your updated code (one cell element per user-code line).
  5. Do the same with appData.CodeData.GeneratedCode (if existing), which holds the same data as appData.CodeData.EditableSection.Code but also including the AppDesigner-generated [non-editable] code.
  6. Save the modified appData struct back into appdesigner/appModel.mat
  7. Update the zip file (*.mlapp) with the updated appModel.mat and document.xml

In theory, it is enough to extract the classdef code and same it in a simple *.m file, but then you would not be able to continue using AppDesigner to make layout modifications, and you would need to make all the changes manually in the m-file. If you wish to continue using AppDesigner after you modified the code, then you need to save it back into the *.mlapp file as explained above.

If you think this is not worth all the effort, then you’re probably right. But you must admit that it’s a bit fun to poke around…

One day maybe I’ll create wrapper utilities (mlapp2m and m2mlapp) that do all this automatically, in both directions. Or maybe one of my readers here will pick up the glove and do it sooner – are you up for the challenge?

Caveat Emptor

Note that the MLAPP file format is deeply undocumented and subject to change without prior notice in upcoming Matlab releases. In fact, MathWorker Chris Portal warns us that:

A word of caution for anyone that tries this undocumented/unsupported poking into their MLAPP file. Taking this approach will almost certainly guarantee your app to not load in one of the subsequent releases. Just something to consider in your off-roading expedition!

Then again, the same could have been said about the FIG and other binary file formats used by Matlab, which remained essentially the same for the past decade: Some internal field values may have changed but not the general format, and in any case the newer releases still accept files created with previous releases. For this reason, I speculate that future AppDesigners will accept MLAPP files created by older releases, possibly even hand-modified MLAPP files. Perhaps a CRC hash code of some sort will be expected, but I believe that any MLAPP that we modify today will still work in future releases. However, I could well be mistaken, so please be very careful with this knowledge. I trust that you can make up your own mind about whether it is worth the risk (and fun) or not.

AppDesigner is destined to gradually replace the aging GUIDE over the upcoming years. They currently coexist since AppDesigner (and its web-based uifigures) still does not contain all the functionality that GUIDE (and JFrame-based figures) provides (a few examples). I already posted a few short posts about AppDesigner (use the AppDesigner tag to list them), and today’s article is another in that series. Over the next few years I intend to publish more on AppDesigner and its associated new GUI framework (uifigures).

Zurich visit, 21-31 Aug 2016

I will be traveling to Zürich for a business trip between August 21-31. If you are in the Zürich area and wish to meet me to discuss how I could bring value to your work, then please email me (altmany at gmail).

Categories: GUI, High risk of breaking in future versions, Undocumented feature

Tags: , , , , ,

Bookmark and SharePrint Print

10 Responses to AppDesigner’s mlapp file format

  1. Sam says:

    Though not quite the same as what you’re envisioning, there’s a proof of concept implementation of *.mlapp -> *.m classdef on FEX (http://www.mathworks.com/matlabcentral/fileexchange/56237-mlapp2classdef).

    It’s a pretty basic thing thrown together so I could use AppDesigner’s layout tools and get a programmatic GUI out of it, which is my preferred GUI medium. It also attempts to strip the new UI elements out in favor of the “traditional” ones so I could work with older versions of MATLAB.

  2. Terry Brennan says:

    Why not use class inheritance to achieve your goals? As for working programmatically, one of the things I like about the app designer is that the resulting mlapp file defines a class so I can derive other classes from the gui-created class and work programmatically without the visual interface on the derived class. One can add components, modify properties etc. One can even declare methods and/or properties in the mlapp to be Abstract within the appdesigner. This allows the creation of a base class from which customizations can be derived.

    For example if I have a working appGUI.mlapp I can create

    classdef myAppGUI < appGUI
       properties
          NewButton matlab.ui.control.Button
       end
       methods
          function A = myAppGUI
             % A = A@appGUI; <-- usually not necessary
             A.NewButton = uibutton(A.UIFigure);
             A.NewButton.Position = [20 20 100 22];
          end
       end
    end

    To assist you in recalling component names, etc, while programming the derived class you can have an instance of the base class (if it is not Abstract) in the workspace

    >> Base = appGUI;

    to interrogate its methods and properties. This is almost as good as having the appGUI code.

    Of course, this approach should not become obsolete, unless the MathWorks does something dumb like making mlapp classes Sealed.

  3. Oleg says:

    The following methods load the data:

    lf      = appdesigner.internal.serialization.AppLoadingFactory.instance();
     
    appData = lf.getAppData('C:\Users\ok1011\Documents\github\carv-realtime-matlab\src\utilities\+carv\+plot\turn.mlapp')
     
      AppData with properties:
     
                       UIFigure: [1×1 Figure]
                       CodeData: [1×1 appdesigner.internal.codegeneration.model.CodeData]
                       Metadata: [1×1 appdesigner.internal.serialization.app.AppMetadata]
                     ToolboxVer: '2016b'
                    FullVersion: '9.1.0.441655 (R2016b)'
        MinimumSupportedVersion: 'R2016b'

    Or using the file reader, which allows to read Matlab code directly:

    fid     = appdesigner.internal.serialization.FileReader('C:\Users\ok1011\Documents\github\carv-realtime-matlab\src\utilities\+carv\+plot\turn.mlapp');
    appData = fid.readAppDesignerData();
    mcode   = fid.readMATLABCodeText();

    The appdesigner.internal.serialization.FileWriter() class allows to write code to the .mlapp file.

  4. Tom says:

    Thanks for this tutorial. You saved me a ton of work. After Matlab crashed and corrupted my .mlapp file, I was able to unzip the .mlapp file and get to my GUI code and recreate my GUI. Without this, I would be months behind recreating the GUI and rewriting all the code. This website was a lifesaver. Thank you very much.

  5. Antonio Martinez says:

    So, I don’t know if anyone is still reading this comment section but I tried digging a bit more into how MLAPP files are generated, following up Oleg’s contribution.

    My conclusion is that, as far as I have been able to reach without basically fully reverse engineering AppDesigner’s inner workings, there is no way to use the methods available in appdesigner.internal such as FileWriter() to generate MLAPP files from the class definition in text format in an M file.

    Here’s what I found.

    • In newer versions of MATLAB the MLAPP file is organized in pretty much the same way but the appdesigner/appModel.mat file has a different organization. Now the AppData field is deprecated but kept for compatibility, and the data it contained is replace by two new fields ‘code’ and ‘components’. The ‘code’ struct stores the code from the app in separated sections (EditableSectionCode which is the part that contains the methods and properties, Callbacks, StartupCallback, etc), and components stores the UIFigure and the group hierarchy (I have no idea what this is, is generated by the AppModel method getGroupHierarchy() and is way to deep for me too understand already). The groups hierarchy was previusly stored inside appData.metadata.

    AppModel: probably the parent for all apps created in AppDesigner is the class appdesigner.internal.model.AppModel . The AppModel contains the save() method, and has the properties Metadata and CodeModel among others. CodeModel is generated from the class appdesigner.internal.codegeneration.model.CodeModel, and contains the different sections of the code.

    FileWriter(): as Oleg’s pointed correctly, appdesigner.internal.serialization.FileWriter() is a class that can generate and write to MLAPP files. It has 4 methods (writeMATLABCodeText, writeAppDesignerData, writeAppMetadata, writeAppScreenshot) that have to be called to succesfully contruct an MLAPP file, and can be called from the command line, which would allow using them to generate MLAPP files from its components. The problem is, this components can only be accessed by AppDesigner itself, which renders this methods useless.

    Hypothesis for MLAPP file saving: my hypothesis is that when an app is saved, the save() method in the AppModel class is called, which in turn calls the AppModel method writeAppToFile(), which creates an appdesigner.internal.serialization.MLAPPSerializer instance. The AppModel’s method setDataOnSerializer() copies all the required information to MLAPPSerializer, including the code sections, the metadata, group hierarchy, etc. Then, MLAPPSerializer creates a fileWriter instance, divides the different objects received from AppModel and passes them to the relevant fileWriter methods to generate the MLAPP file.

    I’m afraid there is literally no way to correctly generate the data scructures that are necessary to reconstruct an MLAPP file from the plain code without putting a ton of hours into untangling all the logic behind AppDesigner. Maybe someone can use this information to take on that task in the future.

    • Antonio Martinez says:

      By the way, these are the relevant MATLAB files if someone wants so keep reverse engineering this thing

      • C:\Program Files\MATLAB\R2018b\toolbox\matlab\appdesigner\appdesigner\+appdesigner\+internal\+model\AppModel.m
      • C:\Program Files\MATLAB\R2018b\toolbox\matlab\appdesigner\appdesigner\+appdesigner\+internal\+codegeneration\+model\CodeModel.m
      • C:\Program Files\MATLAB\R2018b\toolbox\matlab\appdesigner\appdesigner\+appdesigner\+internal\+serialization\MLAPPSerializer.m
      • C:\Program Files\MATLAB\R2018b\toolbox\matlab\appdesigner\appdesigner\+appdesigner\+internal\+serialization\FileWriter.m

  6. Bradley Stiritz says:

    It’s great to see that Yair and others are digging into the internals of AppDesigner. It seems to have a lot of promise. At the same time, AppDesigner is still often unstable and unusable, as of R2019b. I just posted on the running Mathworks Answers thread about this–

    https://www.mathworks.com/matlabcentral/answers/279042-app-designer-s-editor-is-slow-and-gets-stuck-alot

    Question for anyone who has studied the internals– have you also experienced problems in AppDesigner, and then gained any insights whatsoever via your sleuthing? Do you have any clues or hypotheses about what could be going wrong for so many users?

    Surely, after all these years of hard work, the AppDesigner team should have worked out most of the minor bugs..?

Leave a Reply

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