06 · App Designer Basics¶
Verification note
MATLAB App Designer was not available in the environment used to
write this page. The classdef-based app structure, callback
wiring, and component properties described below are documented,
version-stable App Designer conventions, hand-traced against the
MATLAB documentation rather than built or run in App Designer
itself.
App Designer builds interactive GUI applications on top of the same
classdef mechanics from Module 01, generating a .mlapp file — a
zipped bundle containing a classdef, the UI layout, and resources.
Rather than write layout code by hand (though that's possible via
uifigure + programmatic component creation), App Designer's canvas
generates that code for you; this module focuses on the code structure
that either path produces, since that's what you can read, review, and
extend.
Anatomy of an app¶
An App Designer app is a handle class (Module 01 — because UI state
must be shared and mutated across callbacks, not copied):
classdef TemperatureConverterApp < matlab.apps.AppBase
properties (Access = public)
UIFigure matlab.ui.Figure
CelsiusField matlab.ui.control.NumericEditField
FahrenheitField matlab.ui.control.NumericEditField
ConvertButton matlab.ui.control.Button
CelsiusLabel matlab.ui.control.Label
FahrenheitLabel matlab.ui.control.Label
end
methods (Access = private)
function ConvertButtonPushed(app, event)
c = app.CelsiusField.Value;
f = c * 9/5 + 32;
app.FahrenheitField.Value = f;
end
end
methods (Access = private)
function createComponents(app)
app.UIFigure = uifigure('Name', 'Temperature Converter', 'Position', [100 100 300 200]);
app.CelsiusLabel = uilabel(app.UIFigure, 'Text', 'Celsius:', 'Position', [30 130 60 22]);
app.CelsiusField = uieditfield(app.UIFigure, 'numeric', 'Position', [100 130 100 22]);
app.FahrenheitLabel = uilabel(app.UIFigure, 'Text', 'Fahrenheit:', 'Position', [30 90 60 22]);
app.FahrenheitField = uieditfield(app.UIFigure, 'numeric', 'Position', [100 90 100 22]);
app.FahrenheitField.Editable = 'off';
app.ConvertButton = uibutton(app.UIFigure, 'push', ...
'Text', 'Convert', ...
'Position', [100 50 100 30], ...
'ButtonPushedFcn', createCallbackFcn(app, @ConvertButtonPushed, true));
end
end
methods (Access = public)
function app = TemperatureConverterApp
createComponents(app);
registerApp(app, app.UIFigure);
end
function delete(app)
delete(app.UIFigure);
end
end
end
Three parts matter:
- Properties declare every UI component as a typed property so
callbacks can reach
app.CelsiusField.Valuefrom anywhere in the class. createComponentsbuilds the figure and every widget once, at construction, wiring each control's callback to a method viacreateCallbackFcn.- Callback methods (
ConvertButtonPushed) run whenever their event fires, receivingapp(the handle object — so mutations persist) andevent(details about what triggered the callback).
uifigure vs. classic figure¶
App Designer apps run on uifigure, a separate figure type from the
plotting figure used in earlier modules. uifigure supports the
modern app-building components (uieditfield, uidropdown,
uiswitch, uigauge, uitable, uitree, uiaxes for plots inside
apps) but does not support some legacy plotting commands directly on
it — plotting inside an app goes through a uiaxes component:
app.UIAxes = uiaxes(app.UIFigure, 'Position', [20 20 260 150]);
plot(app.UIAxes, x, y); % target the uiaxes explicitly, not gca
Common components¶
uilabel(fig, 'Text', 'Status:');
uieditfield(fig, 'text'); % text input
uieditfield(fig, 'numeric'); % numeric input, auto-validates
uibutton(fig, 'push', 'Text', 'Run');
uidropdown(fig, 'Items', {'Low','Medium','High'});
uicheckbox(fig, 'Text', 'Enable logging');
uislider(fig, 'Limits', [0 100]);
uiswitch(fig, 'rocker', 'Items', {'Off','On'});
uitable(fig, 'Data', magic(4));
uigauge(fig, 'Limits', [0 100]);
uilistbox(fig, 'Items', {'A','B','C'}, 'Multiselect', 'on');
Every component's callback follows the same naming convention:
ValueChangedFcn for controls that carry a value (dropdown, slider,
checkbox), ButtonPushedFcn for buttons, SelectionChangedFcn for
lists/tables.
Wiring callbacks and sharing state¶
Callbacks read and write app properties directly, which is why apps
must be handle classes — a value class would give each callback its own
disconnected copy of the app, breaking state persistence between calls.
properties (Access = private)
HistoryLog = {} % private state not exposed as a UI component
end
methods (Access = private)
function ConvertButtonPushed(app, event)
c = app.CelsiusField.Value;
f = c * 9/5 + 32;
app.FahrenheitField.Value = f;
app.HistoryLog{end+1} = sprintf('%.1fC -> %.1fF', c, f);
app.HistoryListBox.Items = app.HistoryLog;
end
end
Startup and cleanup¶
methods (Access = private)
function startupFcn(app, varargin)
app.HistoryLog = {};
app.StatusLabel.Text = 'Ready';
end
end
startupFcn, if present, is called automatically right after
createComponents during construction — the natural place to
initialize data, load a config file, or set default control values
before the user interacts with anything.
delete(app) is the destructor: closing the figure calls it, and it
should tear down anything that outlives the figure itself — open files,
timers, or connections.
Timers for periodic UI updates¶
properties (Access = private)
UpdateTimer
end
methods (Access = private)
function startupFcn(app)
app.UpdateTimer = timer('ExecutionMode', 'fixedRate', ...
'Period', 1, ...
'TimerFcn', @(~,~) app.refreshClock());
start(app.UpdateTimer);
end
function refreshClock(app)
app.ClockLabel.Text = datestr(now, 'HH:MM:SS');
end
function delete(app)
if isvalid(app.UpdateTimer)
stop(app.UpdateTimer);
delete(app.UpdateTimer);
end
delete(app.UIFigure);
end
end
Always stop and delete timers in delete(app) — an app closed with a
running timer leaves an orphaned timer object firing callbacks against a
deleted figure, which errors repeatedly in the background.
Input validation¶
function CelsiusFieldValueChanged(app, event)
c = app.CelsiusField.Value;
if c < -273.15
uialert(app.UIFigure, 'Temperature below absolute zero is not physical.', 'Invalid Input');
app.CelsiusField.Value = -273.15;
return;
end
app.ConvertButtonPushed(event);
end
uialert shows a modal dialog without halting the whole app the way
error() would — appropriate for user-facing validation feedback inside
an interactive tool, where you want the user to fix input and continue,
not crash the session.
How It Actually Works¶
An App Designer app is, underneath its drag-and-drop editor, a
classdef object (Module 01's handle-class semantics) with UI
component handles stored as its properties and callback functions
implemented as its methods — the visual designer is generating real
MATLAB classdef source code that you can inspect (via "Edit Code");
there is no separate declarative UI-description format being interpreted
at runtime, it's ordinary object-oriented MATLAB code with generated
scaffolding.
Callbacks (a button's ButtonPushedFcn, say) are wired through MATLAB's
event/listener mechanism, the same underlying system that lets graphics
handle-object properties trigger reactions elsewhere (linked axes in
Module 05): each UI component is a handle object that fires an event when
interacted with, and the callback function you write is registered as a
listener on that event — this is why callbacks execute asynchronously
relative to your app's "main" flow, triggered by the event loop whenever
the corresponding UI interaction occurs, not by sequential top-to-bottom
script execution.
Because UI components are handle objects, passing app (the object
representing your whole app instance) into a helper function doesn't copy
the app's state — the helper function is operating on the same live
object, which is exactly what lets one callback update a plot or label
that was created by an entirely different callback: both are mutating
properties on the same shared handle-class instance.
Note: based on the documented classdef/handle-object architecture
underlying App Designer; App Designer itself is unavailable in this
environment to inspect generated code directly.
Practice¶
- Extend the temperature converter with a
uidropdownto choose the conversion direction (C→F or F→C) and update the single active edit field'sEditableproperty accordingly. - Add a
uitablethat logs every conversion performed (input, output, timestamp) and a "Clear History" button that resets it. - Add a
startupFcnthat disables the Convert button until the Celsius field has a non-empty value, using itsValueChangedFcnto re-enable it. - Explain, in your own words, why an App Designer app's main class must
inherit handle semantics rather than being a plain value
classdef, referencing what breaks (concretely, in terms of what a callback would and wouldn't see) if it weren't.