Bridging the Gap in App Building: From uicontrol to UI Components
![]() |
Guest Writer: Jasmine PoppickJasmine works in documentation at MathWorks, both as a writer for the App Building area and as a Senior Team Lead for the Math area. Her first introduction to MATLAB was when she taught a differential equations course during her graduate studies in math. At MathWorks, she has written the documentation for many big app building features, such as custom UI components and Simulink apps in App Designer. When she isn’t writing new examples about how to build apps, she loves playing games, gardening, and spending time with her dog, Mochi. |
When I'm not documenting all of the cool new features that the app building team releases, you can usually find me playing a game. Lately, my new obsession is bridge (yes, that bridge), a wonderfully complex, 4-player partner game that I play weekly with some neighbors. I had built a small app to explore the data from our weekly sessions using an old friend, the uicontrol function. But there are newer, more powerful players on the MATLAB app building field that I wanted to use to upgrade my app
While uicontrol isn’t going anywhere, UI component functions such as uibutton are easier to use, give a wider range of component types and customizability, and have a more modern appearance. In addition, the components support more streamlined and robust options for laying out your app.
This blog article walks through the steps to move away from the uicontrol and figure functions and on to UI components and the uifigure function, using the bridge app as an example.
Original App
I originally created the bridge analysis app using some older app building functionality:
- The uicontrol function, to create controls
- The Position property, to manually specify the size and location of containers and controls
- The figure function, to create a figure window to house the app
Here's how the original app looks when I run it in R2025a.
Update UI Components
The first step to update a figure-based app is to replace all calls to uicontrol with calls to the appropriate UI component function. The table below illustrates some example replacements; for a complete list of UI Components, see the app building components gallery ). This step is what enables later upgrades to app functionality. The UI components support all sorts of additional properties and configurations, and using UI components unlocks easier layout options. And starting in R2025a, this replacement is easier than ever: because you can add UI components to figures created using the figure function (not just the uifigure function), you can make these updates incrementally, one component at a time, without having to revamp your whole app in one go.
Conversions available in 2025a (from Update UIControl Objects and Callbacks.)
Replacing UIControl objects with UI components may take some adjustment but there's a mapping between most styles and properties which simplifies much of the conversion . You can find that mapping here: Update UIControl Objects and Callbacks.
In my case, I had a handful of different control styles. For example, let's look at the Making check box, which is a "checkbox" style UIControl object. Here's the original code:
To update this component, you can use the uicheckbox function and make the following changes.
- Replace the uicontrol(__) function name with uicheckbox(__).
- Change some property names, since the set of properties on a CheckBox object is slightly different from those on a UIControl In this example, String becomes Text.
- Instead of setting the Callback, set the ValueChangedFcn
- Delete Style altogether
Everything else stays exactly the same:
It's a similar story for the drop-down lists. You can replace the "popupmenu" style UIControl object with a DropDown UI component. Here's the before and after code:
Before | After |
ddPartner = uicontrol(pnlFilter, ... Style="popupmenu", ... Position=[10 160 100 22], ... String=["All","Alice","Bob","Charlie"], ... Callback=@updateData); |
ddPartner = uidropdown(pnlFilter, ... Position=[10 160 100 22], ... Items=["All","Alice","Bob","Charlie"], ... ValueChangedFcn=@updateData);
|
Update Callback Functions
When you update components, the change might require some additional updates to your callback code. For example, to query the currently selected item for a UIControl drop-down list, you can use the Value property (which returns the index of the selected value in the list of items) together with the String property. To get the drop-down value to filter the table data, I originally had this line of code:
partnerval = ddPartner.String{ddPartner.Value};
The DropDown object doesn't have a String property (it stores the list of all items using the Items property instead), and the Value property instead returns the selected item. So I replaced the line of code with:
partnerval = ddPartner.Value;
I went through the process of updating all the check boxes, drop-down lists, labels, edit fields, and buttons in my app. While the updated app appears and functions almost identically to the original, the app now uses more modern coding patterns that enable new functionality.
Adopt UI Component Functionality
With all of my controls updated to use the modern UI components, I turned my sights to the fun part: improving the functionality of my app. I wanted to take advantage of two things:
- UI component types that better suit the app functionality
- Existing component configuration options to upgrade the component usability
Add Additional Component Types
Some UI components don't exist as UIControl styles (such as trees, spinners, hyperlinks, and so on). Using these components can help make app building tasks easier, since the components often have built-in functionality to easily accomplish common tasks.
For example, for my bridge app, there was a lingering bug that I was able to fix by using a different component type. In the Add New Hand panel, my app had edit fields where I could type the bid value and number of overtricks. But while the edit fields allow for typing any text value, in practice, the allowed values really should be constrained: a bid in bridge can only be an integer between 1 and 7.
I could have coded my own custom validation. But with the new UI components, I didn't have to do that because built-in functionality helped me achieve this validation with very little effort. I replaced the Bid edit field with a spinner instead.
Before | After |
editBid = uicontrol(pnlAddData, ... Style="edit", ... Position=[40 58 30 20], ... String="1"); |
editBid = uispinner(pnlAddData, ... Position=[40 58 50 20], ... Value=1, ... Limits=[1 7]);
|
The spinner automatically constrains the allowed values to integers, lets the app user navigate using arrow buttons, and provides built-in validation that prevents entering a number outside of [1, 7].
Update Component Configuration
Even for existing components, there were some places where I was able to adopt some new functionality to improve my app. In my app, the main focus was the table UI component, which has a ton of new features from the past several releases. First, I made the columns sortable, and I specified that the width of each table column fits its content. I also updated the underlying UI table data to use the table data type, which gave me automatic row and column names and let me display Bid as a multicolumn variable.
Before | After |
tbl = uitable(fig, ... Units="normalized", ... Position=[0.22 0.05 0.38 0.9], ... Data=data, ... RowName=[], ... ColumnName=["Bid","Overtricks","Partner","Declaring"]); |
tbl = uitable(fig, ... Units="normalized", ... Position=[0.22 0.05 0.38 0.9], ... Data=data, ... ColumnWidth={'fit'}, ... ColumnEditable=[false true true true], ... ColumnSortable=true); |
Finally, I spruced up the visuals by adding color to the table rows using uistyle and addStyle. In my app, I wanted to visually distinguish the rows that correspond to making a contract perfectly (green), going down (red), and going over (blue), so I created and applied three different styles.
undertrickRows = find(tbl.Data.Overtricks < 0); sUnder = uistyle(BackgroundColor="#FFBBAA"); addStyle(tbl,sUnder,"row",undertrickRows) makingRows = find(tbl.Data.Overtricks == 0); sMaking = uistyle(BackgroundColor="#D5FFAD"); addStyle(tbl,sMaking,"row",makingRows) overtrickRows = find(tbl.Data.Overtricks > 0); sOver = uistyle(BackgroundColor="#ADBDFF"); addStyle(tbl,sOver,"row",overtrickRows)
Use a Grid Layout
One big benefit of using UI components is that, unlike UIControl objects, you can lay out components in a grid layout manager.
In my bridge app, improving the layout was the main draw for making updates. I was tired of wrestling with Position values for every component, and the app already had a pretty natural grid-like structure. I decided to add two layers of grids: one in the figure to manage the position of the panels and table, and one in each of the panels to manage the content inside.
First, I created each of the grid layouts using the uigridlayout function and configured the row heights and column widths. For example, this code creates the top-level grid in the figure:
g = uigridlayout(fig); g.RowHeight = {'1x','fit'}; g.ColumnWidth = {'fit','fit','1x'};
The 'fit' option is great; it specifies that the grid calculate the right width or height to contain its content. For example, rather than manually calculating how wide the table should be to display all of its columns, I just specified the width of the second column as 'fit'. Now, even if I decide to add or remove a table column in the future, my layout will automatically adapt to the new table size. For the rest of the rows and columns, I used a weighted height and width (‘1x’) to fill the rest of the space. For example, I specified that the second row fills the remaining vertical space so that the table height spans the whole figure.
After I created the grid layout managers, I added the components to them. When moving from position-based layouts to a grid, you can follow a three-step process:
- Specify the grid (rather than the panel or figure) as the component parent.
- Remove any code that sets the Position or Units
- Position the component in the grid by specifying the component Layout
For example, here's the before and after for a few components in the Add New Hand panel.
Before:
lblDeclaring = uicontrol(pnlAddData,Position=[135 28 50 20], ... Style="text",String="Declaring:"); cbxIsDeclaring = uicontrol(pnlAddData,Position=[185 28 20 20], ... Style="checkbox",Value=true); btnAdd = uicontrol(pnlAddData,Position=[220 5 30 25], ... Style="pushbutton",String="Add",Callback=@addData);
After: lblDeclaring = uilabel(gAddData,Text="Declaring:"); lblDeclaring.Layout.Row = 2; lblDeclaring.Layout.Column = 4; cbxIsDeclaring = uicheckbox(gAddData,Value=true,Text=""); cbxIsDeclaring.Layout.Row = 2; cbxIsDeclaring.Layout.Column = 5; btnAdd = uibutton(gAddData,Text="Add",ButtonPushedFcn=@addData); btnAdd.Layout.Row = 3; btnAdd.Layout.Column = [4 5];
Update to uifigure
The last change I made in my bridge app was to update the original call to the figure function to uifigure.
Highlight:
- figure(__) is configured for data exploration and visualization.
- uifigure(__) is configured for app building workflows.
Both of these functions create a Figure object, but the objects that they create have slightly different property values and behaviors that have evolved over time. Figures created using the figure function are configured for data exploration and visualization, while figures created using the uifigure function are configured for app building. For a list of differences between the two, see Differences Between Figures Created Using figure and uifigure.
In my app, updating the function call didn't have any visual impact. However, there are a few benefits it granted. For example, figures created using figure can become the current figure by default, whereas figures created using uifigure cannot. This difference is due to the different default values of the HandleVisibility property. Many graphics functions implicitly use gcf or gca to determine the target for operations, which means that if I ran the figure-based app and then called plot(1:10), a new axes would get added to the app. Updating my code to use uifigure ensured that any plotting functions I call in the Command Window will not make unwanted changes to my app.
As a second example, while figures created using the figure function have figure tools by default (that is, a figure toolstrip in R2025a or a menu bar and toolbar in previous releases), these tools generally don't make much sense in the context of an app. I had originally added this line of code in my app to remove any figure tools:
fig.MenuBar = "none";
The uifigure function creates a figure without any figure tools by default, so when I updated my app I could remove that line of code.
Final App
After all of the changes, here's how the upgraded app looks when I run it in R2025a with cleaner, more readable code, and improved usability.
If you're interested in seeing the full before and after code, you can download it here:
- bridge data: BridgeScores.mat
- old app: BridgeAppOld.m
- new app: BridgeAppNew.m
For more information about updating figure-based apps, see Update figure-Based Apps to Use uifigure.
We'd love to hear about your experiences with UI components and any tips you have.
- Category:
- App Building,
- Demos,
- New Features
Comments
To leave a comment, please click here to sign in to your MathWorks Account or create a new one.