Matlab App Designer After Press Button Focus Uigetfile
Simple GUI Tabs for Advanced Matlab Trading App
February 17, 2016, 10:00 am
I'd like to introduce guest blogger Alex Boykov, one of the developers of the Walk-Forward Analysis Toolbox for Matlab (WFAToolbox), which enables accelerated trading strategies development using Matlab. Today, Alex will explain how they used tabs in a way that can be replicated by any other Matlab GUI, not necessarily having the latest Matlab release.
In this post, we want to tell you about how we solved the problem of tab creation for WFAToolbox. We required the following criteria:
- The tabs need to be attractive and look like tabs, not like buttons with panels
- The tabs need to have been drawn using the editor GUIDE so that the contents of the tab panel can be easily edited
- The tabs can be easily added and removed without significant code additions. They must be simple to use in different projects and tasks
The sophisticated user of Matlab might think that this is a trivial objective, seeing as there are numerous solutions for this problem in the Matlab Exchange and since Matlab R2014b, it supports creating native tabs with the help of uitab and uitabgroup functions. Also, with the addition of App Designer, it might appear that this issue will be solved with the new interface for GUI creation; tabs can be created right in the editor. However, in this post, we will attempt to explain why none of the methods above fit the three criteria stated and we will present our own solution for the tabs.
Regardless of the fact that we only took on the problem in 2013, when we first started creating our WFAToolbox, at the moment of writing this article (January 2016), this problem is still a relevant issue for many Matlab users. After the release of R2016a, it is doubtful the problem will be entirely solved. This is why we created our own example of a code which we have released on the Matlab File Exchange (see below).
Tab-enabled WFAToolbox (Matlab app for algorithmic trading)
1. The tabs have to look like tabs
When we created WFAToolbox, our goal was to create an application which would allow everyone interested to create a strategy for trading on the financial markets to be able to do so, along with having the opportunity to use the full potential of Matlab and its progressive tools, including genetic algorithms, parallel computing, econometrics, neural networks, and much, much more (basically, any data analysis that can be done in Matlab). At the same time, we do not want our users to spend time on developing an advanced software environment for testing, analysis, and strategy execution, but rather to do it from an easy-to use GUI. Thus, in WFAToolbox, you can create, test, and, finally, launch your own trading strategy or test a hypothesis within minutes, even with little prior knowledge of Matlab programming.
Of course, in order to fit these features into a single application, guarantee that it would be easy to understand even by beginners, and that it would be simple to operate, it was necessary to pay special attention to the graphic interface. In our opinion, perhaps the most intelligent solution for placing the many controls and functions necessary for sophisticated applications is by creating tabs. Because we knew that we were not the only ones who thought this way, we started looking for examples of codes that were previously created in the Matlab Exchange. We were very surprised when we found only a few solutions, most of which did not even match our first criteria of tab attractiveness! Unfortunately, a majority of them were old and quite unattractive (they looked more like buttons with panels). Even the new App Designer has tabs that in our eyes look more like buttons than tabs.
Having tried a lot of these utilities in our test versions, we came to the conclusion that Tab Panel Constructor v.2.8 would be the best option for us. It fits all three criteria above. In 2013, we used it quite successfully in our first versions of WFAToolbox. Everything looked great, but, unfortunately, it later turned out that the problem was far from being solved.
Tab-enabled WFAToolbox (Matlab app for algorithmic trading)
2. The tabs need to be created through GUIDE
Unfortunately, with time, it turned out that with the newer version of Matlab it didn't work smoothly and the code we wanted to use as our solution practically fell apart in front of us. After adding a couple of elements in GUI, partial formatting was lost and we had to redo everything. The process of adding the tags created a lot of bugs which needed to be solved immediately.
In 2014, we already had more than 500 clients using our application. We started hearing, more and more often, that it would be great if the colors and locations of the tabs could be changed. It turned out that, depending on the operating system and Matlab version, the tab format changes. So, we made the decision to change our tabs.
By that time, a new version of Matlab was released, R2014b. It allowed us to build tabs with the help of the uitabgroup and uitab functions. The results looked exactly how we wanted: attractive, pleasant, and appeared like real tabs: UI With Tab Panel in Matlab R2014b. However, we were discouraged that they could not be created in GUIDE!
During that time, we were developing a module for WFAToolbox which would allow users to download data from Google Finance: 10,000+ free daily and intraday quotes from 20+ exchanges. Tabs were the easiest to use when switching between downloading free data from Google Finance and downloading custom user data from the Matlab Workspace. But entering so many elements through code and not through an editor? What will happen when we add 100,000+ free historical data from Yahoo Finance for futures, bonds, currency, stocks and others? We didn't want to create all of this without the GUIDE editor! This is why we came to the conclusion that it is necessary for us to create a code of tabs, starting from scratch, so that they would correspond with all three of our criteria.
Tab-enabled WFAToolbox (Matlab app for algorithmic trading)
3. The tabs should be easy to add and edit
We chose the Simple Tab Panel, which has existed in the Matlab File Exchange since 2007, as a base for our new code because we considered it to be the most elegant and attractive example of GUIDE tabs. This solution fit our first two criteria, but we really wanted it to be universal and easy to use. We also wanted to have a simplified process of tab addition and deletion so that instead of having to copy and rewrite a large amount of code and other details, we could just add a single line of code. We wanted to save on labor costs, because we often add new features to WFAToolbox and this includes having to constantly add new elements to existing tabs, as well as adding new tabs.
So, we rewrote the code and created our own universal example so that everyone could use it to their advantage. We uploaded the code to the Matlab File Exchange, where it can be freely downloaded: Simple Optimized GUI Tab.
Next, we will describe how to use this code for tab addition and how to use the process for the implementation of tasks.
So, in order to add a new tab, you need to:
- Open GUIDE and apply uipanel and uitext in a way that will make uipanel easier to work with in the future, and place uitext in a place where the tab switch will be located.
- Rename the Tag of the uitext to ['tab',N,'text'], where N is the tab index. In our example, we are creating the 3rd tab, so our tag would be 'tab3text'. Using this same principle, ['tab',N,'Panel'] needs to be renamed to tag of uipanel in the 'tab3Panel'.
- Add the name of the new tab to the
TabNames
variable. In our example, we use 'Tab3' (but you can use any name).
TabNames = { 'Tab 1','Tab 2','Tab3' };
How the code was created
The primary principle of how our code works is that we create the uipanel and uitext objects in GUIDE, then we take the uitext coordinates and replace the objects to the axes and text objects. We assign a callback function to them which works when the object is clicked on. The function makes the uipanel s visible/invisible and changes the colors of tab.
Let's look at the function code SimpleOptimizedTabs2.m , which is part of the Simple Optimized GUI Tab submission.
1. Tab settings
% Settings TabFontSize = 10; TabNames = { 'Tab 1','Tab 2' }; FigWidth = 0.265;
If we change the parameters under Settings, we can control the appearance of our GUI and tabs. So, the parameter of TabFontSize
changes the font size on the tab switch, and, with the help of TabNames
we can rename or add tab names, and with FigWidth
, we can determine the normalized width of the GUI.
2. Changing the figure width
% Figure resize set (handles.SimpleOptimizedTab,'Units','normalized' ) pos = get (handles. SimpleOptimizedTab, 'Position' ); set (handles. SimpleOptimizedTab, 'Position', [pos( 1 ) pos( 2 ) FigWidth pos( 4 ) ] )
The GUI width changes in the code because it isn't comfortable to manually stretch and narrow the figure. It is more beneficial to see the contents of all tabs and work with them without having to change the width every time you make a small change. If you want to make your uipanel s bigger than in the example, then do this with the GUIDE editor. However, don't forget to change the FigWidth
parameter.
Please note that, due to the peculiarities of the editor, you cannot narrow a figure by height without shifting tab locations. You can only do this if you are changing the width, so we only recommend adding tabs by increasing the width of the figure and not the length.
3. Creating tabs
Do the following for each tab: obtain the uitext coordinates, which we entered into the GUI panel, and position the axes and text using these coordinates (using the necessary settings of external apparel). Using the ButtonDownFcn parameter, we can link the callback function, called ClickOnTab, in order to switch tabs when clicking on the text or axes .
% Tabs Execution handles = TabsFun(handles,TabFontSize,TabNames); % --- TabsFun creates axes and text objects for tabs function handles = TabsFun(handles,TabFontSize,TabNames) % Set the colors indicating a selected/unselected tab handles.selectedTabColor=get (handles.tab1Panel,'BackgroundColor' ); handles.unselectedTabColor=handles.selectedTabColor-0.1; % Create Tabs TabsNumber = length (TabNames); handles.TabsNumber = TabsNumber; TabColor = handles.selectedTabColor; for i = 1:TabsNumber n = num2str ( i ); % Get text objects position set (handles.( [ 'tab',n,'text' ] ),'Units','normalized' ) pos=get (handles.( [ 'tab',n,'text' ] ),'Position' ); % Create axes with callback function handles.( [ 'a',n] ) = axes ( 'Units','normalized',... 'Box','on',... 'XTick',[ ],... 'YTick',[ ],... 'Color',TabColor,... 'Position',[pos( 1 ) pos( 2 ) pos( 3 ) pos( 4 )+0.01 ],... 'Tag',n,... 'ButtonDownFcn',[mfilename,'(' 'ClickOnTab' ',gcbo,[],guidata(gcbo))' ] ); % Create text with callback function handles.( [ 't',n] ) = text( 'String',TabNames{ i },... 'Units','normalized',... 'Position',[pos( 3 ),pos( 2 )/2+pos( 4 ) ],... 'HorizontalAlignment','left',... 'VerticalAlignment','middle',... 'Margin',0.001,... 'FontSize',TabFontSize,... 'Backgroundcolor',TabColor,... 'Tag',n,... 'ButtonDownFcn',[mfilename,'(' 'ClickOnTab' ',gcbo,[],guidata(gcbo))' ] ); TabColor = handles.unselectedTabColor; end % Manage panels (place them in the correct position and manage visibilities) set (handles.tab1Panel,'Units','normalized' ) pan1pos=get (handles.tab1Panel,'Position' ); set (handles.tab1text,'Visible','off' ) for i = 2:TabsNumber n = num2str ( i ); set (handles.( [ 'tab',n,'Panel' ] ),'Units','normalized' ) set (handles.( [ 'tab',n,'Panel' ] ),'Position',pan1pos) set (handles.( [ 'tab',n,'Panel' ] ),'Visible','off' ) set (handles.( [ 'tab',n,'text' ] ),'Visible','off' ) end
Actually, if you have long tab names and you want to change the switch size, then it you may possibly need to correct the Position parameter for the text object by adding the correcting coefficients to it. Unfortunately, this is also a feature of GUIDE. If someone can solve this problem so that the text would always be shown in the middle of the switch tab regardless of the width, we would be happy to read any suggestions in the comments to this post.
4. The callback function ClickOnTab
The callback function ClickOnTab is used every time when clicking on the tab switch and the result of the switches are visible/invisible in the uipanel s and in changes to the colors of the switches.
% --- Callback function for clicking on tab function ClickOnTab(hObject,~,handles) m = str2double ( get (hObject,'Tag' ) ); for i = 1:handles.TabsNumber; n = num2str ( i ); if i == m set (handles.( [ 'a',n] ),'Color',handles.selectedTabColor ) set (handles.( [ 't',n] ),'BackgroundColor',handles.selectedTabColor ) set (handles.( [ 'tab',n,'Panel' ] ),'Visible','on' ) else set (handles.( [ 'a',n] ),'Color',handles.unselectedTabColor ) set (handles.( [ 't',n] ),'BackgroundColor',handles.unselectedTabColor ) set (handles.( [ 'tab',n,'Panel' ] ),'Visible','off' ) end end
More information about our Walk-Forward Analysis Toolbox for Algorithmic Trading (WFAToolbox) can be found at wfatoolbox.com.
Adding a search box to figure toolbar
March 30, 2016, 6:50 am
Last week I wrote about my upcoming presentations in Tel Aviv and Munich, where I will discuss a Matlab-based financial application that uses some advanced GUI concepts. In today's post I will review one of these concepts that could be useful in a wide range of Matlab applications – adding an interactive search box to the toolbar of Matlab figures.
The basic idea is simple: whenever the user types in the search box, a Matlab callback function checks the data for the search term. If one or more matches are found then the searchbox's background remains white, otherwise it is colored yellow to highlight the term. When the user presses <Enter>, the search action is triggered to highlight the term in the data, and any subsequent press of <Enter> will highlight the next match (cycling back at the top as needed). Very simple and intuitive:
Interactive search-box in Matlab figure toolbar
In my specific case, the search action (highlighting the search term in the data) involved doing a lot of work: updating multiple charts and synchronizing row selection in several connected uitables. For this reason, I chose not to do this action interactively (upon each keypress in the search box) but rather only upon clicking <Enter>. In your implementation, if the search action is simpler and faster, you could do it interactively for an even more intuitive effect.
Technical components
The pieces of today's post were already discussed separately on this website, but never shown together as I will do today:
- The search box component (
com.mathworks.widgets.SearchTextField
) was discussed in last year's article on auto-completion widgets - I showed how to add custom controls to the figure toolbar in a 2009 post (time flies!)
- I discussed controlling toolbar components' size in another old post
- I discussed my findjobj utility, used for accessing the underlying Java components of Matlab uicontrols in another article
- I discussed Matlab's use of EDT in a dedicated article on the subject back in 2010 (and I'll have more to say about this subject next week)
- Finally, I discussed how to trap Java control events in Matlab in separate articles here and here
Adding a search-box to the figure toolbar
As a first step, let's create the search-box component and add it to our figure's toolbar:
% First, create the search-box component on the EDT, complete with invokable Matlab callbacks: jSearch = com.mathworks.widgets.SearchTextField ( 'Symbol' ); % 'Symbol' is my default search prompt jSearchPanel = javaObjectEDT(jSearch.getComponent ); % this is a com.mathworks.mwswing.MJPanel object jSearchPanel = handle(jSearchPanel, 'CallbackProperties' ); % enable Matlab callbacks % Now, set a fixed size for this component so that it does not resize when the figure resizes: jSize = java.awt.Dimension ( 100,25 ); % 100px wide, 25px tall jSearchPanel.setMaximumSize (jSize) jSearchPanel.setMinimumSize (jSize) jSearchPanel.setPreferredSize (jSize) jSearchPanel.setSize (jSize) % Now, attach the Matlab callback function to search box events (key-clicks, Enter, and icon clicks): jSearchBox = handle(javaObjectEDT(jSearchPanel.getComponent ( 0 ) ), 'CallbackProperties' ); set (jSearchBox, 'ActionPerformedCallback', { @searchSymbol,hFig,jSearchBox} ) set (jSearchBox, 'KeyPressedCallback', { @searchSymbol,hFig,jSearchBox} ) jClearButton = handle(javaObjectEDT(jSearchPanel.getComponent ( 1 ) ), 'CallbackProperties' ); set (jClearButton, 'ActionPerformedCallback', { @searchSymbol,hFig,jSearchBox} ) % Now, get the handle for the figure's toolbar: hToolbar = findall(hFig,'tag','FigureToolBar' ); jToolbar = get ( get (hToolbar,'JavaContainer' ),'ComponentPeer' ); % or: hToolbar.JavaContainer.getComponentPeer % Now, justify the search-box to the right of the toolbar using an invisible filler control % (first add the filler control to the toolbar, then the search-box control): jFiller = javax.swing.Box.createHorizontalGlue; % this is a javax.swing.Box$Filler object jToolbar.add (jFiller, jToolbar.getComponentCount ); jToolbar.add (jSearchPanel, jToolbar.getComponentCount ); % Finally, refresh the toolbar so that the new control is displayed: jToolbar.revalidate jToolbar.repaint
Search action callback functionality
Now that the control is displayed in the toolbar, let's define what our Matlab callback function searchSymbol() does. Remember that this callback function is invoked whenever any of the possible events occur: keypress, <Enter>, or clicking the search-box's icon (typically the "x" icon, to clear the search term).
We first reset the search-box appearance (foreground/background colors), then we check the search term (if non-empty). Based on the selected tab, we search the corresponding data table's symbol column(s) for the search term. If no match is found, we highlight the search term by setting the search-box's text to be red over yellow. Otherwise, we change the table's selected row to the next match's row index (i.e., the row following the table's currently-selected row, cycling back at the top of the table if no match is found lower in the table).
Reading and updating the table's selected row requires using my findjobj utility – for performance considerations the jTable handle should be cached (perhaps in the hTable's UserData or ApplicationData):
% Callback function to search for a symbol function searchSymbol(hObject, eventData, hFig, jSearchBox) try % Clear search-box formatting jSearchBox.setBackground (java.awt.Color.white ) jSearchBox.setForeground (java.awt.Color.black ) jSearchBox.setSelectedTextColor (java.awt.Color.black ) jSearchBox.repaint % Search for the specified symbol in the data table symbol = char (jSearchBox.getText ); if ~isempty (symbol) handles = guidata(hFig); hTab = handles.hTabGroup.SelectedTab; colOffset = 0; forceCol0 = false; switch hTab.Title case 'Scanning' hTable = handles.tbScanResults; symbols = cell (hTable.Data (:,1 ) ); case 'Correlation' hTable = handles.tbCorrResults; symbols = cell (hTable.Data (:,1:2 ) ); case 'Backtesting' hTab = handles.hBacktestTabGroup.SelectedTab; hTable = findobj (hTab, 'Type','uitable', 'Tag','results' ); pairs = cell (hTable.Data (:,1 ) ); symbols = cellfun ( @ (c)strsplit(c,'/' ), pairs, 'uniform',false ); symbols = reshape ( [symbols{:} ],2,[ ] )'; forceCol0 = true; case 'Trading' hTable = handles.tbTrading; symbols = cell (hTable.Data (:,2:3 ) ); colOffset = 1; otherwise % ignore return end if isempty (symbols) return end [rows,cols] = ind2sub ( size (symbols), find ( strcmpi (symbol,symbols) ) ); if isempty (rows) % Not found - highlight the search term jSearchBox.setBackground (java.awt.Color.yellow ) jSearchBox.setForeground (java.awt.Color.red ) jSearchBox.setSelectedTextColor (java.awt.Color.red ) jSearchBox.repaint elseif isa (eventData, 'java.awt.event.KeyEvent' ) && isequal(eventData.getKeyCode,10 ) % Found with <Enter> event - highlight the relevant data row jTable = findjobj(hTable); try jTable = jTable.getViewport.getView; catch, end % in case findjobj returns the containing scrollpane rather than the jTable [rows, sortedIdx] = sort (rows); cols = cols(sortedIdx); currentRow = jTable.getSelectedRow + 1; idx = find (rows>currentRow,1 ); if isempty (idx), idx = 1; end if forceCol0 jTable.changeSelection (rows(idx)-1, 0, false, false ) else jTable.changeSelection (rows(idx)-1, cols(idx)-1+colOffset, false, false ) end jTable.repaint jTable.getTableHeader.repaint jTable.getParent.getParent.repaint drawnow end end catch % never mind - ignore end end
That's all there is to it. In my specific case, changing the table's selected row cased an immediate trigger that updated the associated charts, synchronized the other data tables and did several other background tasks.
What about the new web-based uifigure?
The discussion above refers only to traditional Matlab figure s (both HG1 and HG2), not to the new web-based (AppDesigner) uifigure s that were officially introduced in R2016a (I wrote about it last year).
AppDesigner uifigures are basically webpages rather than desktop windows (JFrames). They use an entirely different UI mechanism, based on HTML webpages served from a localhost webserver, using the DOJO Javascript toolkit for visualization and interaction, rather than Java Swing as in the existing JFrame figures. The existing figures still work without change, and are expected to continue working alongside the new uifigures for the foreseeable future. I'll discuss the new uifigures in separate future posts (in the meantime you can read a bit about them in my post from last year).
I suspect that the new uifigures will replace the old figures at some point in the future, to enable a fully web-based (online) Matlab. Will this happen in 2017 or 2027 ? – your guess is as good as mine, but my personal guesstimate is around 2018-2020.
Faster findjobj
April 11, 2016, 2:18 am
My findjobj utility, created in 2007 and updated over the years, has received wide recognition and is employed by numerous Matlab programs, including a few dozen utilities in the Matlab File Exchange. I am quite proud of this utility and find it extremely useful for customizing Matlab controls in many ways that are impossible using standard Matlab properties. I have shown many examples of this in this blog over the past years.
I am happy to announce that I have just uploaded a new version of findjobj to the Matlab File Exchange, which significantly improves the utility's performance for the most common use-case of a single input and a single output, namely finding the handle of the underlying Java component (peer) of a certain Matlab control:
>> hButton = uicontrol ( 'String','click me!' ); >> tic, jButton = findjobj(hButton); toc % old findjobj Elapsed time is 1.513217 seconds. >> tic, jButton = findjobj(hButton); toc % new findjobj Elapsed time is 0.029348 seconds.
The new findjobj is backward-compatible with the old findjobj and with all prior Matlab releases. It is a drop-in replacement that will significantly improve your program's speed.
The new version relies on several techniques:
First, as I showed last year, in HG2 (R2014 onward), Matlab uipanel s have finally become full-featured Java JPanel
s, that can be accessed and customized in many interesting manners. More to the point here, we can now directly access the underlying JPanel
component handle using the uipanel 's hidden JavaFrame property (thanks to MathWorks for supplying this useful hook!). The new findjobj version detects this and immediately returns this handle if the user specified a uipanel input.
I still do not know of any direct way to retrieve the underlying Java component's handle for Matlab uicontrol s, this has been a major frustration of mine for quite a few years. So, we need to find the containing Java container in which we will recursively search for the control's underlying Java handle. In the old version of finjobj , we retrieve the containing figure's JFrame
reference and from it the ContentPane
handle, and use this handle as the Java container that is recursively searched. This is quite slow when the figure window is heavily-laden with multiple controls. In the new version, we try to use the specified Matlab uicontrol 's direct parent, which is very often a uipanel . In this case, we can directly retrieve the panel's JPanel
reference as explained above. This results in a must smaller and faster search since we need to recursively search far fewer controls within the container, compared to the figure's ContentPane
.
In addition, I used a suggestion by blog reader Hannes for a faster recursive search that uses the control's tooltip rather than its size, position and class. Finally, the search order is reversed to search backward from the last child component, since this is the component that will most often contain the requested control peer.
Feel free to download and use the new findjobj version. The code for the fast variant can be found in lines #190-205 and #3375-3415.
Enjoy!
p.s. – as I explained last week, today's discussion, and in general anything that has to do with Java peers of GUI controls, only relates to the existing JFrame
-based figure windows, not to the new web-based uifigure .
Smart listbox & editbox scrollbars
April 20, 2016, 10:47 am
A good friend recently asked me for examples where using Java in Matlab programs provides a significant benefit that would offset the risk of using undocumented/unsupported functionality, which may possibly stop working in some future Matlab release. Today I will discuss a very easy Java-based hack that in my opinion improves the appearance of Matlab GUIs with minimal risk of a catastrophic failure in a future release.
The problem with Matlab listbox and multi-line editbox controls in the current (non web-based) GUI, is that they use a scrollbar whose behavior policy is set to VERTICAL_SCROLLBAR_ALWAYS
. This causes the vertical scrollbar to appear even when the listbox does not really require it. In many cases, when the listbox is too narrow, this also causes the automatic appearance of a horizontal scrollbar. The end result is a listbox that displays 2 useless scrollbars, that possibly hide some listbox contents, and are a sore to the eyes:
Standard (left) and smart (right) listbox scrollbars
default scrollbars (VERTICAL_SCROLLBAR_ALWAYS)
non-default scrollbars (VERTICAL_SCROLLBAR_AS_NEEDED)
By default, Matlab implements a vertical scrollbar policy of VERTICAL_SCROLLBAR_ALWAYS
for sufficiently tall uicontrols (>20-25 pixels, which practically means always) and VERTICAL_SCROLLBAR_NEVER
for shorter uicontrols (this may possibly be platform-dependent).
A similar problem happens with the horizontal scrollbar: Matlab implements a horizontal scrollbar policy of HORIZONTAL_SCROLLBAR_NEVER
for all editboxes and also for narrow listboxes (HORIZONTAL_SCROLLBAR_AS_NEEDED for wide listboxes.
In many cases we may wish to modify the settings, as in the example shown above. The solution to this is very easy, as I explained back in 2010.
All we need to do is to retrieve the control's underlying Java reference (a Java JScrollPane
object) and change the policy value to VERTICAL_SCROLLBAR_AS_NEEDED
:
% Create a multi-line (Max>1) editbox uicontrol hEditbox = uicontrol ( 'style','edit', 'max',5, ... ); try % graceful-degradation for future compatibility % Get the Java scroll-pane container reference jScrollPane = findjobj(hEditbox); % Modify the scroll-pane's scrollbar policies % (note the equivalent alternative methods used below) set (jScrollPane,'VerticalScrollBarPolicy',javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED ); %VERTICAL_SCROLLBAR_AS_NEEDED=20 jScrollPane.setHorizontalScrollBarPolicy (javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED ); %HORIZONTAL_SCROLLBAR_AS_NEEDED=30 catch % Never mind... end
Note that updating the uicontrol handle Position property has the side-effect of automatically reverting the scrollbar policies to their default values (HORIZONTAL_SCROLLBAR_NEVER
and VERTICAL_SCROLLBAR_ALWAYS/NEVER
). This also happens whenever the uicontrol is resized interactively (by resizing its container figure window, for example). It is therefore advisable to set jScrollPane's ComponentResizedCallback property to "unrevert" the policies:
cbFunc = @ (h,e) set (h,'VerticalScrollBarPolicy',20, 'HorizontalScrollBarPolicy',30 ); hjScrollPane = handle(jScrollPane,'CallbackProperties' ); set (hjScrollPane,'ComponentResizedCallback',cbFunc);
smart_scrollbars utility
I created a new utility called smart_scrollbars that implements all of this, which you can download from the Matlab File Exchange. The usage in Matlab code is very simple:
% Fix scrollbars for a specific listbox hListbox = uicontrol ( 'style','list', ... ); smart_scrollbars(hListbox) % Fix scrollbars for a specific editbox hEditbox = uicontrol ( 'style','edit', 'max',5, ... ); smart_scrollbars(hEditbox) % Fix all listbox/editbox scrollbars in a panel or figure smart_scrollbars % fixes all scrollbars in current figure (gcf) smart_scrollbars(hFig) % fixes all scrollbars in a specific figure smart_scrollbars(hContainer) % fixes all scrollbars in a container (panel/tab/...)
Performance considerations
Finding the underlying JScrollPane
reference of Matlab listboxes/editboxes can take some time. While the latest version of findjobj significantly improved the performance of this, it can still take quite a while in complex GUIs. For this reason, it is highly advisable to limit the search to a Java container of the control that includes as few internal components as possible.
In R2014b or newer, this is easily achieved by wrapping the listbox/editbox control in a tightly-fitting invisible uipanel . The reason is that in R2014b, uipanel s have finally become full-fledged Java components (which they weren't until then), but more to the point they now contain a property with a direct reference to the underlying JPanel
. By using this panel reference we limit findjobj 's search only to the contained scrollpane, and this is much faster:
% Slower code: hListbox = uicontrol ( 'style','list', 'parent',hParent, 'pos',... ); smart_scrollbars(hListbox) % Much faster (using a tightly-fitting transparent uipanel wrapper): hPanel = uipanel( 'BorderType','none', 'parent',hParent, 'pos',... ); % same position/units/parent as above hListbox = uicontrol ( 'style','list', 'parent',hPanel, 'units','norm', 'pos',[ 0,0,1,1 ], ... ); smart_scrollbars(hListbox)
The smart_scrollbars utility detects cases where there is a potential for such speedups and reports it in a console warning message:
>> smart_scrollbars(hListbox) Warning: smart_scrollbars can be much faster if the list/edit control is wrapped in a tightly-fitting uipanel (details)
If you wish, you can suppress this warning using code such as the following:
oldWarn = warning ( 'off', 'YMA:smart_scrollbars:uipanel' ); smart_scrollbars(hListbox) warning (oldWarn); % restore warnings
Musings on future compatibility
Going back to my friend's question at the top of today's post, the risk of future compatibility was highlighted in the recent release of Matlab R2016a, which introduced web-based uifigures and controls, for which the vast majority of Java hacks that I presented in this blog since 2009 (including today's hack) will not work. While the full transition from Java-based to web-based GUIs is not expected anytime soon, this recent addition highlighted the risk inherent in using unsupported functionality.
Users can take a case-by-case decision whether any improved functionality or appearance using Java hacks is worth the extra risk: On one hand, such hacks have been quite stable and worked remarkably well for the past decade, and will probably continue working into 2020 or so (or longer if you keep using a not up-to-the-moment Matlab release, or if you create compiled applications). On the other hand, once they stop working sometime in R2020a (or whenever), major code rewrites may possibly be required, depending on the amount of dependency of your code on these hacks.
There is an obvious tradeoff between improved GUIs now and for the coming years, versus increased maintainability cost a few years in the future. Each specific GUI will have its own sweet spot on the wide spectrum between using no such hacks at all, through non-critical hacks that provide graceful functionality degradation if they ever fail, to major Java-based functionality that would require complete rework. It is certainly NOT an all-or-nothing decision. Users who take the conservative approach of using no unsupported feature at all, lose the opportunity to have professional grade Matlab GUIs today and in the upcoming years. Decisions, decisions, …
In any case, we can reduce the risk of using such hacks today by carefully wrapping all their code in try-catch blocks. This way, even if the code fails in some future Matlab release, we'd still be left with a working implementation based on fully-supported functionality. This is the reason why I've used such a block in the code snippet above, as well as in my smart_scrollbars utility. What this means is that you can safely use smart_scrollbars in your code today and if the worst happens and it stops working in a few years, then it will simply do nothing without causing any error. In other word, future compatibility in the form of graceful degradation. I strongly advise using such defensive coding techniques whenever you use unsupported features.
Transparent labels
May 4, 2016, 9:26 am
For the application that I will be presenting at next week's MATLAB Expo in Munich (presentation slides), I wanted to add a text label at a specific location within the figure. The problem was, as you can clearly see from the screenshot below, that there is precious little available space for a new label. I could drive the entire content down to make space for it, but that would reduce the usable space for the actual contents, which is already at a premium:
Adding a transparent label to Matlab GUI (click for full-size image)
A natural place for the new label, as indicated, would be on top of the empty space next to the content's sub-tabs (Correlation and Backtesting). This empty space is taken up by Matlab's uitabgroup control, and we can simply place our label on top of it.
Well, easier said than done…
The obvious first attempt is to set the label's position to [0,0,1,1]
(in normalized units of its parent container). The label text will appear at the expected location, since Matlab labels are always top-aligned. However, the label's opaque background will hide anything underneath (which is basically the entire content).
If we set the label's position to something smaller (say, [.2,.9,.6,.1]
), the label will now hide a much smaller portion of the content, but will still mask part of it (depending of the exact size of the figure) and for very small figure might actually make the label too small to display. Making the label background transparent will solve this dilemma.
Unfortunately, all Matlab controls are made opaque by default. Until recently there was not much that could be done about this, since all Matlab controls used heavyweight java.awt.Panel
-derived containers that cannot be made transparent (details). Fortunately, in HG2 (R2014b onward) containers are now lightweight javax.swing.JPanel
-derived and we can transform them and their contained control from opaque to non-opaque (i.e., having a transparent background).
There are 3 simple steps for this:
- Find the text label control's underlying Java peer (control) reference handle. This can be done using my findjobj utility, or by direct access via the containing uipanel hierarchy (if the label is inside such a uipanel ), as explained here.
- Set the Java label reference to be non-opaque (via its setOpaque() method)
- Repaint the label via its repaint() method
% Create the Matlab text label uicontrol hLabel = uicontrol ( 'Style','text', 'Parent',hPanel, 'Units','norm', 'Pos',[ 0,0,1,1 ], 'String','Results for BERY / PKG (1 hour)' ); % Get the underlying Java peer (control) reference jLabel = findjobj(hLabel); %jLabel = hPanel.JavaFrame.getGUIDEView.getComponent(0).getComponent(0).getComponent(0).getComponent(0); % a direct alternative % Set the control to be non-opaque and repaint it jLabel.setOpaque ( false ); jLabel.repaint ( );
This now looks nice, but not quite: Matlab displays the label text at the very top of its container, and this is not really in-line with the uitab labels. We need to add a small vertical padding at the top. One way to do this would be to set the label's position to [0,0,1,.99]
rather than [0,0,1,1]
. Unfortunately, this results in varying amounts of padding depending on the container/figure height. A better alternative here would be to set the label to have a fixed-size padding amount. This can be done by attaching an empty Border
to our JLabel
:
% Attach a 6-pixel top padding jBorder = javax.swing.BorderFactory.createEmptyBorder ( 6,0,0,0 ); % top, left, bottom, right jLabel.setBorder (jBorder);
Another limitation is that while the transparent background presents the illusion of emptiness, trying to interact with any of the contents beneath it using mouse clicks fails because the mouse clicks are trapped by the Label background, transparent though it may be. We could reduce the label's size so that it occludes a smaller portion of the content. Alternatively, we can remove the label's mouse listeners so that any mouse events are passed-through to the controls underneath (i.e., not consumed by the label control, or actually it's internal Java container):
jLabelParent = jLabel.getParent; % Remove the mouse listeners from the control's internal container jListener = jLabelParent.getMouseListeners; jLabelParent.removeMouseListener (jListener( 1 ) ); jListener = jLabelParent.getMouseMotionListeners; jLabelParent.removeMouseMotionListener (jListener( 1 ) );
Using the label's Java peer reference, we could do a lot of other neat stuff. A simple example for this is the VerticalAlignment or LineWrap properties – for some reason that eludes me, Matlab's uicontrol only allows specifying the horizontal alignment and forces a line-wrap, despite the fact that these features are readily available in the underlying Java peer.
Finally, while it is not generally a good design practice to change fonts throughout the GUI, it sometimes makes sense to use different font colors, sizes, faces
and/or attributes for parts of the label text, in various situations. For example, to emphasize certain things, as I've done in my title label. Such customizations can easily be done using HTML strings with most Matlab uicontrols, but unfortunately not for labels, even today in R2016a. MathWorks created custom code that removes the HTML support in Matlab labels, for reasons that elude me yet again, especially since Matlab upcoming future GUI will probably be web-based so it will also natively support HTML, so maybe there's still hope that HTML will be supported in Matlab labels in a future release.
Anyway, the bottom line is that if we need our label to have HTML support today, we can use a standard Java JLabel
and add it to the GUI using the javacomponent function. Here's a simple usage example:
% Create the label and add it to the GUI jLabel = javaObjectEDT(javax.swing.JLabel ( '<html>Results for <b>BERY / PKG (1 Hour)</b></html>' ) ); [hjLabel, hContainer] = javacomponent(jLabel, [ 10,10,10,10 ], hPanel); set (hContainer, 'Units','norm', 'Pos',[ 0,0,1,1 ] ) % Make the label (and its internal container) transparent jLabel.getParent.getParent.setOpaque ( false ) % label's internal container jLabel.setOpaque ( false ) % the label control itself % Align the label jLabel.setVerticalAlignment (jLabel.TOP ); jLabel.setHorizontalAlignment (jLabel.CENTER ); % Add 6-pixel top border padding and repaint the label jLabel.setBorder (javax.swing.BorderFactory.createEmptyBorder ( 6,0,0,0 ) ); jLabel.repaint; % Now do the rest - mouse-listeners removal etc. ...
If you happen to attend the Matlab Expo next week in Munich Germany, please do come by and say hello!
Figure window customizations
June 1, 2016, 1:00 am
A friend recently asked me, in light of my guesstimate that Java-based Matlab figures will be replaced by web-based figures sometime around 2018-2020, whether there are any "killer features" that make it worthwhile to use undocumented Java-based tricks today, despite the fact that they will probably break in 2-5 years. In my opinion, there are many such features; today I will focus on just a subset of them – those features that relate to the entire figure window.
Over the years I wrote many articles here about figure-level customizations, as well as an entire chapter in my Matlab-Java programming book. So today's post will be a high-level overview, and users who are interested in any specific topic can visit the referenced links for the implementation details.
An undecorated Matlab figure window – one of many possible figure-level customizations
JavaFrame
JavaFrame is an undocumented hidden property of the figure handle that provides access to the underlying Java window (JFrame
) peer object's reference. Since R2008a, a warning is issued whenever we retrieve this property:
>> jFrame = get(gcf,'JavaFrame'); Warning: figure JavaFrame property will be obsoleted in a future release. For more information see the JavaFrame resource on the MathWorks web site. (Type "warning off MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame" to suppress this warning.)
Until HG2 (R2014b+) we could suppress the warning by simply wrapping the figure handle within a handle() call, as explained here. Since R2014b we need to use the warning function to do this:
warning ( 'off', 'MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame' );
We can do several things directly with the JavaFrame's properties and methods, including:
- Maximize/minimize/restore the window, via the properties Maximized/Minimized (which accept and return a boolean (logical) value), or the corresponding methods jFrame.isMaximized(), isMinimized(), setMaximized(flag), setMinimized(flag). details
- Modify the container to which the figure will be docked. By default this is the "Figures" container, but this can be changed to any user-specified container, or even to the "Editor", using the GroupName property or its associated methods. See the related setFigDockGroup utility that I posted on the Matlab File exchange.
- Remove the top separator line between the toolbar and the content-pane, to blend them together, via the jFrame.showTopSeparator(flag) method.
- Retrieve a direct Java reference to the Matlab Desktop and the figure's internal containers via the Desktop and FigurePanelContainer properties, respectively (we can also get those references by other means).
- Retrieve a direct Java reference to the containing
JFrame
(Java window), as discussed below - A few other features that I will not discuss here
MathWorks have set up a dedicated webpage where you can specify how you are using JavaFrame and why it is important for you: http://www.mathworks.com/javaframe. I encourage you to use this webpage to tell MathWorks which features are important for you. This will help them to decide which functionality should be added to the new web-based figures.
JFrame window
The JavaFrame handle enables direct retrieval of the containing Java JFrame
(window) reference, using several alternatives. Here are two of these alternatives (there are others):
% Alternative #1 >> jWindow = jFrame.getFigurePanelContainer.getTopLevelAncestor jWindow = com.mathworks.hg.peer.FigureFrameProxy$FigureFrame[fClientProxyFrame,72,62,576x507,... ] % Alternative #2 try jClient = jFrame.fFigureClient; % This works up to R2011a catch try jClient = jFrame.fHG1Client; % This works from R2008b-R2014a catch jClient = jFrame.fHG2Client; % This works from R2014b and up end end jWindow = jClient.getWindow;
Customized menu items (top) and figure status bar (bottom)
With the retrieved jWindow
reference, we can do several additional interesting things:
- Enable/disable the entire figure in a single go (details)
- Remove/restore the window frame (borders and title bar), otherwise known as an "undecorated window" (details)
- Set the figure window to be "Always-On-Top", i.e. not occluded by any other window, via the AlwaysOnTop property, or the corresponding jWindow.isAlwaysOnTop(), setAlwaysOnTop(flag) methods.
- Make the figure window fully or partially transparent (details). Note: this fails on R2013b/Java7 and higher due to a change in the way that transparency works in Java 7 compared to earlier releases; in other words blame Oracle's Java, not MathWorks' Matlab….
- Blur/restore the figure window (details). This too works only up to R2013a.
- Detect and handle window-level focus gain/loss events (details), as well as window-level mouse events (enter/exit/hover etc. – details).
- Customize the figure's menu bar – dynamic behavior, tooltips, highlights, keyboard shortcuts/accelerators, font colors/styles, callbacks, icons etc. (details1, details2)
- Control figure docking in compiled (deployed) applications (details1, details2)
- Display an integral figure status-bar with text and GUI controls (details1, details2).
- A few other features that I will not discuss here
As you can see, there are numerous very interesting customizations that can be done to Matlab figures which rely on the undocumented implementation. Here are a couple of usage examples that you can easily adapt (follow the links above for additional details and usage examples):
jWindow.setEnabled ( false ); % disable entire figure [true/false] jWindow.setMinimized ( true ); % minimize window [true/false] jWindow.setMaximized ( true ); % maximize window [true/false] jWindow.setAlwaysOnTop ( true ); % set to be always on top [true/false] % Set a Matlab callback function to a window focus-gain event hjWindow = handle(jWindow, 'CallbackProperties' ); hjWindow.FocusGainedCallback = @myCallbackFunc;
In addition to the Java-based features above, some functionalities can also be achieved via direct OS manipulations, for example using Jan Simon's great WindowAPI utility (Windows-only), although I typically prefer using the Java approach since it is cross-platform compatible.
Using all these features is super-easy, so there is not really a question of code complexity or technical risk – the main question is whether to accept the risk that the associated code will stop working when Matlab figures will eventually become web-based.
So is it worth the risk?
This is an excellent question. I contend that the answer depends on the specific use-case. In one project you may decide that it is indeed worth-while to use these undocumented features today, whereas in another GUI you may decide that it is not.
It might make sense to use the features above in any of the following circumstances:
- If you need any of the features in your Matlab GUI today. In this case, you really have no alternative other than to use these features, since there is no documented way to achieve the required functionality.
- If you do not plan to upgrade your Matlab release soon, or at least after the Java-based figures are discontinued in a few years. The commercial Matlab license is perpetual, enabling users to enjoy these features for as long as they continue using this Matlab release.
- If you are compiling your Matlab program using the Matlab Compiler or Coder toolboxes. In such cases, the executable will remain static, until such time (if ever) that you decide to recompile it using a newer Matlab release. Users of the compiled code could continue to use the compiled undocumented features well into the future, for as long as their computers keep running. In such cases, we are not concerned with release compatibility issues.
- If you accept the risk that some recoding may be necessary in the future, or that some functionality will degrade, for the added benefit that they provide your GUIs today.
- If you are willing to code without MathWorks' official support and endorsement, and accept the fact that they will not fix any internal bugs that you may discover which is related to these features.
- If you wish to present a professional-grade GUI today, and worry about potential incompatibilities only if and when they eventually arrive, sometime in the future.
Here's another twist to consider: do not take it for granted that when web-based uifigures replace Java-based figures all the documented functionality will work as-is on the new uifigures just as they have on the old figures. In fact, I personally believe that we will need to extensively modify our GUI code to make it compatible with the new uifigures. In other words, avoiding the undocumented hacks above will probably not save us from the need to recode (or at least adapt) our GUI, it will just reduce the necessary work somewhat. We encountered a similar situation with the graphics hacks that I exposed over the years: many people avoided them in the fear that they might someday break; then when R2014b came and HG2 graphics replaced HG1, it turned out that many of these supposedly risky hacks continued working in HG2 (examples: LooseInset, YLimInclude) whereas quite a bit of standard fully-documented Matlab functionality was broken and required some recoding. I believe that the lessons from the HG2 migration were well studied and assimilated by MathWorks, but realistically speaking we should not expect a 100% full-proof transition to uifigures.
Still, accepting the risk does not mean that we should bury our head in the sand. Whenever using any undocumented feature in your code, I strongly suggest to use defensive coding practices, such as wrapping your code within try-catch blocks. This way, even if the feature is removed in R2020a (or whenever), the program will still run, albeit with somewhat diminished functionality, or in other words, graceful degradation. For example:
try jFrame = get (hFig, 'JavaFrame' ); jFrame.setMaximized ( true ); catch oldUnits = get (hFig, 'Units' ); set (hFig, 'Units','norm', 'Pos',[ 0,0,1,1 ] ); set (hFig, 'Units',oldUnits); end
Once again, I urge you to visit http://www.mathworks.com/javaframe and tell MathWorks which of the above features are important for you. The more users tell MathWorks that they depend on a specific feature, the more would MathWorks be likely to invest R&D efforts in enabling it in the future web-based figures.
Listbox selection hacks
July 13, 2016, 8:36 am
Last week a reader on the CSSM newsgroup asked whether it is possible to programmatically deselect all listbox items. By default, Matlab listboxes enable a single item selection: trying to deselect it interactively has no effect, while trying to set the listbox's Value property to empty ([]) results in the listbox disappearing and a warning issued to the Matlab console:
>> hListbox = uicontrol ( 'Style','list', 'String',{ 'item #1','item #2','item #3','item #4','item #5','item #6' } ); >> set (hListbox,'Value',[ ] ); Warning: Single-selection 'listbox' control requires a scalar Value. Control will not be rendered until all of its parameter values are valid (Type "warning off MATLAB:hg:uicontrol:ValueMustBeScalar" to suppress this warning.)
The reader's question was whether there is a way to bypass this limitation so that no listbox item will be selected. The answer to this question was provided by MathWorker Steve(n) Lord. Steve is a very long-time benefactor of the Matlab community with endless, tireless, and patient advise to queries small and large (way beyond the point that would have frustrated mere mortals). Steve pointed out that by default, Matlab listboxes only enable a single selection – not more and not less. However, when the listbox's Max value is set to be >1, the listbox enables multiple-items selection, meaning that Value accepts and reports an array of item indices, and there is nothing that prevents this array from being empty (meaning no items selected):
>> hListbox = uicontrol ( 'Style','list', 'Max',2, 'String',{ 'item #1','item #2','item #3','item #4','item #5','item #6' } ); >> set (hListbox,'Value',[ ] ); % this is ok - listbox appears with no items selected
Note: actually, the listbox checks the value of Max–Min, but by default Min=0 and there is really no reason to modify this default value, just Max.
While this makes sense if you think about it, the existing documentation makes no mention of this fact:
The Max property value helps determine whether the user can select multiple items in the list box simultaneously. If Max – Min > 1, then the user can select multiple items simultaneously. Otherwise, the user cannot select multiple items simultaneously. If you set the Max and Min properties to allow multiple selections, then the Value property value can be a vector of indices.
Some readers might think that this feature is not really undocumented, since it does not directly conflict with the documentation text, but then so are many other undocumented aspects and features on this blog, which are not mentioned anywhere in the official documentation. I contend that if this feature is officially supported, then it deserves an explicit sentence in the official documentation.
However, the original CSSM reader wanted to preserve Matlab's single-selection model while enabling deselection of an item. Basically, the reader wanted a selection model that enables 0 or 1 selections, but not 2 or more. This requires some tweaking using the listbox's selection callback:
set (hListbox,'Callback',@myCallbackFunc); ... function test(hListbox, eventData) value = get (hListbox, 'Value' ); if numel(value) > 1 set (hListbox, 'Value', value( 1 ) ); end end
…or a callback-function version that is a bit better because it takes the previous selection into account and tries to set the new selection to the latest-selected item (this works in most cases, but not with shift-clicks as explained below):
function myCallbackFunc(hListbox, eventData) lastValue = getappdata (hListbox, 'lastValue' ); value = get (hListbox, 'Value' ); if ~isequal(value, lastValue) value2 = setdiff (value, lastValue); if isempty (value2) setappdata (hListbox, 'lastValue', value); else value = value2( 1 ); % see quirk below setappdata (hListbox, 'lastValue', value); set (hListbox, 'Value', value); end end end
This does the job of enabling only a single selection at the same time as allowing the user to interactively deselect that item (by ctrl-clicking it).
There's just a few quirks: If the user selects a block of items (using shift-click), then only the second-from-top item in the block is selected, rather than the expected last-selected item. This is due to line #9 in the callback code which selects the first value. Matlab does not provide us with information about which item was clicked, so this cannot be helped using pure Matlab. Another quirk that cannot easily be solved using pure Matlab is the flicker that occurs when the selection changes and is then corrected by the callback.
We can solve both of these problems using the listbox's underlying Java component, which we can retrieve using my findjobj utility:
% No need for the standard Matlab callback now set (hListbox,'Callback',[ ] ); % Get the underlying Java component peer jScrollPane = findjobj(h); jListbox = jScrollPane.getViewport.getView; jListbox = handle(jListbox,'CallbackProperties' ); % enable callbacks % Attach our callback to the listbox's Java peer jListbox.ValueChangedCallback = { @myCallbackFunc, hListbox}; ... function myCallbackFunc(jListbox, eventData, hListbox) if numel(jListbox.getSelectedIndices ) > 1 set (hListbox, 'Value', jListbox.getLeadSelectionIndex+1 ); % +1 because Java indices start at 0 end end
We can use a similar mechanism to control other aspects of selection, for example to enable only up to 3 selections but no more etc.
We can use this underlying Java component peer for a few other useful selection-related hacks: First, we can use the peer's RightSelectionEnabled property or setRightSelectionEnabled() method to enable the user to select by right-clicking listbox items (this is disabled by default):
jListbox.setRightSelectionEnabled ( true ); % false by default set (jListbox,'RightSelectionEnabled',true ); % equivalent alternative
A similarly useful property is DragSelectionEnabled (or the corresponding setDragSelectionEnabled() method), which is true by default, and controls whether the selection is extended to other items when the mouse drags an item up or down the listbox.
Finally, we can control whether in multi-selection mode we enable the user to only select a single contiguous block of items, or not (which is Matlab's default behavior). This is set via the SelectionMode property (or associated setSelectionMode() method), as follows:
jListbox.setSelectionMode (javax.swing.ListSelectionModel.SINGLE_INTERVAL_SELECTION ); jListbox.setSelectionMode ( 1 ); % equivalent alternative (less maintainable/readable, but simpler)
Additional listbox customizations can be found in related posts on this blog (see links below), or in section 6.6 of my Matlab-Java Programming Secrets book (which is still selling nicely almost five years after its publication, to the pleasant surprise of my publisher…).
AppDesigner's mlapp file format
August 17, 2016, 10:00 am
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)
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:
- Open the *.mlapp file in your favorite zip viewer (e.g., winzip or winrar). You may need to rename/copy the file as *.zip.
- Edit the contents of the contained matlab/document.xml file in your favorite text editor (Matlab's editor for example)
- Load appdesigner/appModel.mat into Matlab workspace.
- 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). - Do the same with
appData.CodeData.GeneratedCode
(if existing), which holds the same data asappData.CodeData.EditableSection.Code
but also including the AppDesigner-generated [non-editable] code. - Save the modified appData struct back into appdesigner/appModel.mat
- 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).
Customizing uifigures part 2
September 7, 2016, 10:00 am
I would like to introduce guest blogger Iliya Romm of Israel's Technion Turbomachinery and Heat Transfer Laboratory. Today Iliya will discuss how Matlab's new web-based figures can be customized with user-controlled CSS and JavaScript code.
When we compare the documented properties of a "classic" uicontrol
with an App Designer control such as uicheckbox
, we see lists of 42 and 15 properties, respectively. At first glance, this implies that our ability to customize App Designer elements is relatively very limited. This is surely a disquieting conclusion, especially for those used to being able to change most aspect of their Matlab figures via Java. Fortunately, such a conclusion is quite far from reality, as we will shortly see.
To understand this claim, we need to consider a previous post on this blog, where Yair discussed how uifigures are actually HTML webpages rendered by Matlab. As such, they have a DOM that can be accessed and manipulated through JavaScript commands to achieve various visual customizations. Today we'll explore the structure of the uifigure webpage; take a look at some possibilities provided by the Dojo Toolkit; and see how to use Dojo to customize uifigure controls visually using CSS styles and/or HTML attributes.
User customizations of Matlab uifigures (click to zoom-in)
A brief introduction to CSS
CSS stands for Cascading Style Sheets. As described on the official webpage of W3C (which governs web standards):
CSS is the language for describing the presentation of Web pages, including colors, layout, and fonts. CSS is independent of HTML. This is referred to as the separation of structure (or: content) from presentation.
CSS rules (or "styles") can be defined in one of three places:
- A separate file, such as the main.css that Matlab uses for uifigures (this file is found minified in %matlabroot%\toolbox\matlab\uitools\uifigureappjs\release\gbtclient\css)
- An inline block inside the HTML's
<head>
section - Directly within a DOM node
Deciding which of the above to use, is largely a choice of the right tool for the job. Usually, the first two choices should be preferred, as they adhere to the "separation of structure and presentation" idea better. However, in the scope of this demonstration, we'll be using mostly the 3rd option, because it allows us not to worry about possible CSS precedence issues (suggested read).
The syntax of CSS is generally: selector { property: value }
, but it can have other forms as well.
Getting down to business
Let us consider a very basic uifigure that only contains a uitextarea
and its label:
Simple demo uifigure with a TextArea and label
The auto-generated code for it is:
classdef DOMdemo < matlab.apps.AppBase % Properties that correspond to app components properties (Access = public) UIFigure matlab.ui.Figure % UI Figure LabelTextArea matlab.ui.control.Label % Text Area TextArea matlab.ui.control.TextArea % This is some text. end methods (Access = private) % Code that executes after component creation function startupFcn(app) 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 280 102 ]; app.UIFigure.Name = 'UI Figure'; setAutoResize(app, app.UIFigure, true ) % Create LabelTextArea app.LabelTextArea = uilabel(app.UIFigure ); app.LabelTextArea.HorizontalAlignment = 'right'; app.LabelTextArea.Position = [ 16 73 62 15 ]; app.LabelTextArea.Text = 'Text Area'; % Create TextArea app.TextArea = uitextarea(app.UIFigure ); app.TextArea.Position = [ 116 14 151 60 ]; app.TextArea.Value = { 'This is some text.' }; end end methods (Access = public) % Construct app function app = DOMdemo( ) % Create and configure components createComponents(app) % Register the app with App Designer registerApp(app, app.UIFigure ) % Execute the startup function runStartupFcn(app, @startupFcn) 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
Let's say we want to modify certain aspects of the TextArea
widget, such as the text color, background, and/or horizontal alignment. The workflow for styling elements involves:
- Find the handle to the webfigure
- Find the DOM node we want to modify
- Find the property name that corresponds to the change we want
- Find a way to manipulate the desired node from Matlab
Step 1: Find the handle to the webfigure
The first thing we need to do is to strategically place a bit of code that would allow us to get the URL of the figure so we can inspect it in our browser:
function startupFcn(app) % Customizations (aka "MAGIC GOES HERE"): warning off Matlab:HandleGraphics:ObsoletedProperty:JavaFrame warning off Matlab:structOnObject while true try win = struct ( struct ( struct (app).UIFigure ).Controller ).Container.CEF; disp (win.URL ); break catch disp ( 'Not ready yet!' ); pause ( 0.5 ); % Give the figure (webpage) some more time to load end end end
This code waits until the page is sufficiently loaded, and then retrieve its local address (URL). The result will be something like this, which can be directly opened in any browser (outside Matlab):
http://localhost:31415/toolbox/matlab/uitools/uifigureappjs/componentContainer.html?channel=/uicontainer/861ef484-534e-4a50-993e-6d00bdba73a5&snc=88E96E
Step 2: Find the DOM node that corresponds to the component that we want to modify
Loading this URL in an external browser (e.g., Chrome, Firefox or IE/Edge) enables us to use web-development addins (e.g., FireBug) to inspect the page contents (source-code). Opening the URL inside a browser and inspecting the page contents, we can see its DOM:
Inspecting the DOM in Firefox (click to zoom-in)
Notice the three data-tag
entries marked by red frames. Any idea why there are exactly three nonempty tags like that? This is because our App Designer object, app
, contains 3 declared children, as defined in:
createComponents(app): app.UIFigure = uifigure; app.LabelTextArea = uilabel(app.UIFigure ); app.TextArea = uitextarea(app.UIFigure );
… and each of them is assigned a random hexadecimal id whenever the app is opened.
Finding the relevant node involved some trial-and-error, but after doing it several times I seem to have found a consistent pattern that can be used to our advantage. Apparently, the nodes with data-tag
are always above the element we want to style, sometimes as a direct parent and sometimes farther away. So why do we even need to bother with choosing more accurate nodes than these "tagged" ones? Shouldn't styles applied to the tagged nodes cascade down to the element we care about? Sure, sometimes it works like that, but we want to do better than "sometimes". To that end, we would like to select as relevant a node as possible.
Anyway, the next step in the program is to find the data-tag that corresponds to the selected component. Luckily, there is a direct (undocumented) way to get it:
% Determine the data-tag of the DOM component that we want to modify: hComponent = app.TextArea; % handle to the component that we want to modify data_tag = char ( struct (hComponent).Controller.ProxyView.PeerNode.getId ); % this part is generic: can be used with any web-based GUI component
Let's take a look at the elements marked with blue and green borders (in that order) in the DOM screenshot. We see that the data-tag
property is exactly one level above these elements, in other words, the first child of the tagged node is an element that contains a widgetid
property. This property is very important, as it contains the id
of the node that we actually want to change. Think pointers. To summarize this part:
We shall use this transformation in Step 4 below.
I wanted to start with the blue-outlined element as it demonstrates this structure using distinct elements. The green-outlined element is slightly strange, as it contains a widgetid
that points back to itself. Since this obeys the same algorithm, it's not a problem.
Step 3: Find the CSS property name that corresponds to the change we want
There is no trick here: it's just a matter of going through a list of CSS properties and choosing one that "sounds about right" (there are often several ways to achieve the same visual result with CSS). After we choose the relevant properties, we need to convert them to camelCase as per documentation of dojo.style():
If the CSS style property is hyphenated, the JavaScript property is camelCased. For example: "font-size" becomes "fontSize", and so on.
Note that Matlab R2016a comes bundled with Dojo v1.10.4, rev. f4fef70 (January 11 2015). Other Matlab releases will probably come with other Dojo versions. They will never be the latest version of Dojo, but rather a version that is 1-2 years old. We should keep this in mind when searching the Dojo documentation. We can get the current Dojo version as follows:
>> f=uifigure; drawnow; dojoVersion = matlab.internal.webwindowmanager.instance.windowList ( 1 ).executeJS ( 'dojo.version' ), delete (f) dojoVersion = {"major":1,"minor":10,"patch":4,"flag":"","revision":"f4fef70"}
This tells us that Dojo 1.10.4.f4fef70 is the currently-used version. We can use this information to browse the relevant documentation branch, as well as possibly use different Dojo functions/features.
Step 4: Manipulate the desired element from Matlab
In this demo, we'll use a combination of several commands:
- {matlab.internal.webwindow.}executeJS() – For sending JS commands to the uifigure.
- dojo.query() – for finding nodes inside the DOM.
- dojo.style() (deprecated since v1.8) – for applying styles to the required nodes of the DOM.
Syntax: dojo.style(node, style, value); - dojo.setAttr (deprecated since v1.8) – for setting some non-style attributes.
Syntax: dojo.setAttr(node, name, value);
Consider the following JS commands:
- search the DOM for nodes having a data-tag attribute having the specified value, take their first child of type
<div>
, and return the value of this child'swidgetid
attribute:[ 'dojo.getAttr(dojo.query("[data-tag^=' '' data_tag '' '] > div")[0],"widgetid")' ]
- search the DOM for nodes with id of
widgetid
, then take the first element of the result and set its text alignment:[ 'dojo.style(dojo.query("#' widgetId( 2:end-1 ) '")[0],"textAlign","center")' ]
- append the CSS style defined by
{SOME CSS STYLE}
to the page (this style can later be used by nodes):[ 'document.head.innerHTML += ' '<style>{SOME CSS STYLE}</style>' '' ] );
Putting it all together
It should finally be possible to understand the code that appears in the animated screenshot at the top of this post:
%% 1. Get a handle to the webwindow: win = struct ( struct ( struct (app).UIFigure ).Controller ).Container.CEF; %% 2. Find which element of the DOM we want to edit (as before): data_tag = char ( struct (app.TextArea ).Controller.ProxyView.PeerNode.getId ); %% 3. Manipulate the DOM via a JS command % ^ always references a class="vc-widget" element. widgetId = win.executeJS ( [ 'dojo.getAttr(dojo.query("[data-tag^=' '' data_tag '' '] > div")[0],"widgetid")' ] ); % Change font weight: dojo_style_prefix = [ 'dojo.style(dojo.query("#' widgetId( 2:end-1 ) '")[0],' ]; win.executeJS ( [dojo_style_prefix '"fontWeight","900")' ] ); % Change font color: win.executeJS ( [dojo_style_prefix '"color","yellow")' ] ); % Add an inline css to the HTML <head>: win.executeJS ( [ 'document.head.innerHTML += ' '<style>' ... '@-webkit-keyframes mymove {50% {background-color: blue;}}' ... '@keyframes mymove {50% {background-color: blue;}}</style>' '' ] ); % Add animation to control: win.executeJS ( [dojo_style_prefix '"-webkit-animation","mymove 5s infinite")' ] ); % Change Dojo theme: win.executeJS ( 'dojo.setAttr(document.body,' 'class' ',' 'nihilo' ')[0]' ); % Center text: win.executeJS ( [dojo_style_prefix '"textAlign","center")' ] );
A similar method for center-aligning the items in a uilistbox is described here (using a CSS text-align
directive).
The only thing we need to ensure before running code that manipulates the DOM, is that the page is fully loaded. The easiest way is to include a pause() of several seconds right after the createComponents(app)
function (this will not interfere with the creation of the uifigure, as it happens on a different thread). I have been experimenting with another method involving webwindow
's PageLoadFinishedCallback
callback, but haven't found anything elegant yet.
A few words of caution
In this demonstration, we invoked Dojo functions via the webwindow's JS interface. For something like this to be possible, there has to exist some form of "bridge" that translates Matlab commands to JS commands issued to the browser and control the DOM. We also know that this bridge has to be bi-directional, because binding Matlab callbacks to uifigure actions (e.g. ButtonPushFcn
for uibuttons) is a documented feature.
The extent to which the bridge might allow malicious code to control the Matlab process needs to be investigated. Until then, the ability of webwindows to execute arbitrary JS code should be considered a known vulnerability. For more information, see XSS and related vulnerabilities.
Final remarks
It should be clear now that there are actually lots of possibilities afforded by the new uifigures for user customizations. One would hope that future Matlab releases will expose easier and more robust hooks for CSS/JS customizations of uifigure contents. But until that time arrives (if ever), we can make do with the mechanism shown above.
Readers are welcome to visit the GitHub project dedicated to manipulating uifigures using the methods discussed in this post. Feel free to comment, suggest improvements and ideas, and of course submit some pull requests
p.s. – it turns out that uifigures can also display MathML. But this is a topic for another post…
Aligning uicontrol contents
September 22, 2016, 6:10 am
Matlab automatically aligns the text contents of uicontrols: button labels are centered, listbox contents are left-aligned, and table cells align depending on their contents (left-aligned for strings, centered for logical values, and right-aligned for numbers). Unfortunately, the control's HorizontalAlignment property is generally ignored by uicontrols. So how can we force Matlab buttons (for example) to have right-aligned labels, or for listbox/table cells to be centered? Undocumented Matlab has the answer, yet again…
It turns out that there are at least two distinct ways to set uicontrol alignment, using HTML and using Java. Today I will only discuss the HTML variant.
The HTML method relies on the fact that Matlab uicontrols accept and process HTML strings. This was true ever since Matlab GUI started relying on Java Swing components (which inherently accept HTML labels) over a decade ago. This is expected to remain true even in Matlab's upcoming web-based GUI system, since Matlab would need to consciously disable HTML in its web components, and I see no reason for MathWorks to do so. In short, HTML parsing of GUI control strings is here to stay for the foreseeable future.
% note: no need to close HTML tags, e.g. </font></html> uicontrol ( 'Style','list', 'Position',[ 10,10,70,70 ], 'String', ... { '<HTML><FONT color="red">Hello</Font></html>', 'world', ... '<html><font style="font-family:impact;color:green"><i>What a', ... '<Html><FONT color="blue" face="Comic Sans MS">nice day!' } );
Listbox with HTML items
While HTML formatting is generally frowned-upon compared to the alternatives, it provides a very quick and easy way to format text labels in various different manners, including using a combination of font faces, sizes, colors and other aspects (bold, italic, super/sub-script, underline etc.) within a single text label. This is naturally impossible to do with Matlab's standard properties, but is super-easy with HTML placed in the label's String property.
Unfortunately, while Java Swing (and therefore Matlab) honors only a [large] sub-set of HTML and CSS. The most important directives are parsed but some others are not, and this is often difficult to debug. Luckily, using HTML and CSS there are often multiple ways to achieve the same visual effect, so if one method fails we can usually find an alternative. Such was the case when a reader asked me why the following seemingly-simple HTML snippet failed to right-align his button label:
hButton.String = '<html><div style="text-align:right">text';
As I explained in my answer, it's not Matlab that ignores the CSS align
directive but rather the underlying Swing behavior, which snugly fits the text in the center of the button, and of course aligning text within a tight-fitting box has no effect. The workaround that I suggested simply forces Swing to use a non-tightly-fitting boundary box, within which we can indeed align the text:
pxPos = getpixelposition(hButton); hButton.String = [ '<html><div width="' num2str (pxPos( 3 )-20 ) 'px" align="right">text' ]; % button margins use 20px
Centered (default) and right-aligned button labels
This solution is very easy to set up and maintain, and requires no special knowledge other than a bit of HTML/CSS, which most programmers know in this day and age.
Of course, the solution relies on the actual button size. So, if the button is created with normalized units and changes its size when its parent container is resized, we'd need to set a callback function on the parent (e.g., SizeChangedFcn of a uipanel) to automatically adjust the button's string based on its updated size. A better solution that would be independent of the button's pixel-size and would work even when the button is resized needs to use Java.
A related solution for table cells uses a different HTML-based trick: this time, we embed an HTML table cell within the Matlab control's cell, employing the fact that HTML table cells can easily be aligned. We just need to ensure that the HTML cell is defined to be larger than the actual cell width, so that the alignment fits well. We do this by setting the HTML cell width to 9999 pixels (note that the tr
and td
HTML tags are necessary, but the table
tag is optional):
uitable( 'Units','norm','Pos',[ 0,0,0.3,0.3 ], 'Data', ... { 'Left', ... '<html><tr><td align=center width=9999>Center', ... '<html><tr><td align=right width=9999>Right' } );
Non-default alignment of uitable cells
As noted above, a better solution might be to set the underlying Java component's alignment properties (or in the case of the uitable, its underlying JTable
component's cellrenderer's alignment). But in the general case, simple HTML such as above could well be sufficient.
Icon images & text in Matlab uicontrols
September 28, 2016, 3:28 am
One of my consulting clients recently asked me if I knew any builtin Matlab GUI control that could display a list of colormap names alongside their respective image icons, in a listbox or popup menu (drop-down/combo-box):
Matlab listbox (left) & popup menu (right) with icon images
My initial thought was that this should surely be possible, since Colormap is a documented figure property, that should therefore be listed inside the inspector window, and should therefore have an associated builtin Java control for the dropdown (just like other inspector controls, which are part of the com.mathworks.mlwidgets
package, or possibly as a standalone control in the com.mathworks.mwswing
package). To my surprise it turns out that for some unknown reason MathWorks neglected to add the Colormap property (and associated Java controls) to the inspector. This property is fully documented and all, just like Color and other standard figure properties, but unlike them Colormap can only be modified programmatically, not via the inspector window. Matlab does provide the related colormapeditor function and associated dialog window, but I would have expected a simple drop-down of the standard builtin colormaps to be available in the inspector. Anyway, this turned out to be a dead-end.
It turns out that we can relatively easily implement the requested listbox/combo-box using a bit of HTML magic, as I explained last week. The basic idea is for each of the listbox/combobox items to be an HTML string that contains both an <img> tag for the icon and the item label text. For example, such a string might contain something like this (parula is Matlab's default colormap in HG2, starting in R2014b):
<html> <img src="http://www.mathworks.com/help/matlab/ref/colormap_parula.png" > parula
parula colormap image
Of course, it would be a bit inefficient for each of the icons to be fetched from the internet. Luckily, the full set of Matlab documentation is typically installed on the local computer as part of the standard Matlab installation, beneath the docroot folder (e.g., C:\Program Files\Matlab\R2016b\help). In our specific case, the parula colormap image is located in:
imageFilename = [docroot, '/matlab/ref/colormap_parula.png' ]
Note that for a local image to be accepted by HTML, it needs to follow certain conventions. In our case, the HTML string for displaying the above image is:
<html> <img src="file:///C:/Program%20Files/Matlab/R2016b/help/matlab/ref/colormap_parula.png" > parula
Warning: it's easy when dealing with HTML images in Matlab to get the format confused, resulting in a red-x icon. I discussed this issue some 4 years ago, which is still relevant.
How can we get the list of available builtin colormaps? The standard Matlab way of doing this would be something like this:
>> possibleColormaps = set ( gcf,'Colormap' ) possibleColormaps = { }
but as we can see, for some unknown reason (probably another MathWorks omission), Matlab does not list the names of its available builtin colormaps.
Fortunately, all the builtin colormaps have image filenames that follow the same convention, which make it easy to get this list by simply listing the names of the relevant files, from which we can easily create the necessary HTML strings:
>> iconFiles = dir ( [docroot, '/matlab/ref/colormap_*.png' ] ); >> colormapNames = regexprep( {iconFiles.name }, '.*_(.*).png', '$1' ) colormapNames = Columns 1 through 9 'autumn' 'bone' 'colorcube' 'cool' 'copper' 'flag' 'gray' 'hot' 'hsv' Columns 10 through 18 'jet' 'lines' 'parula' 'pink' 'prism' 'spring' 'summer' 'white' 'winter' >> htmlStrings = strcat ( '<html><img width=200 height=10 src="file:///C:/Program%20Files/Matlab/R2016a/help/matlab/ref/colormap_', colormapNames', '.png">', colormapNames') str = '<html><img width=200 height=10 src="file:///C:/Program%20Files/Matlab/R2016a/help/matlab/ref/colormap_autumn.png">autumn' '<html><img width=200 height=10 src="file:///C:/Program%20Files/Matlab/R2016a/help/matlab/ref/colormap_bone.png">bone' '<html><img width=200 height=10 src="file:///C:/Program%20Files/Matlab/R2016a/help/matlab/ref/colormap_colorcube.png">colorcube' ... >> hListbox = uicontrol ( gcf, 'Style','listbox', 'Units','pixel', 'Pos',[ 10,10,270,200 ], 'String',htmlStrings); >> hPopup = uicontrol ( gcf, 'Style','popup', 'Units','pixel', 'Pos',[ 10,500,270,20 ], 'String',htmlStrings);
…which results in the screenshots at the top of this post.
Note how I scaled the images to 10px high (so that the labels would be shown and not cropped vertically) and 200px wide (so that it becomes narrower than the default 434px). There's really no need in this case for the full 434×27 image size – such flat images scale very nicely, even when their aspect ratio is not preserved. You can adjust the height
and width
values for a best fit with you GUI.
Unfortunately, it seems that HTML strings are not supported in the new web-based uifigure controls. This is not really Matlab's fault because the way to customize labels in HTML controls is via CSS: directly embedding HTML code in labels does not work (it's a Java-Swing feature, not a browser feature). I really hope that either HTML or CSS processing will be enabled for web-based uicontrol in a future Matlab release, because until that time uifigure uicontrols will remain seriously deficient compared to standard figure uicontrols. Until then, if we must use uifigures and wish to customize our labels or listbox items, we can directly access the underlying web controls, as Iliya explained here.
A blog reader recently complained that I'm abusing Swing and basically making Matlab work in unnatural ways, "something it was never meant to be". I feel that using HTML as I've shown last week and in this post would fall under the same category in his eyes. To him and to others who complain I say that I have absolutely no remorse about doing this. When I purchase anything I have the full rights (within the scope of the license) to adapt it in whatever way fits my needs. As a software developer and manager for over 25 years, I've developed in dozens of programming languages and environments, and I still enjoy [ab]using Matlab. Matlab is a great environment to get things done quickly and if this sometimes requires a bit of HTML or Java hacks that make some people cringe, then that's their problem, not mine – I'm content with being able to do in Matlab [nearly] everything I want, quickly, and move on to the next project. As long as it gets the job done, that's fine by me. If this makes me more of an engineer than a computer scientist, then so be it.
On the flip side, I say to those who claim that Matlab is lacking in this or that aspect, that in most likelihood the limitation is only in their minds, not in Matlab – we can do amazing stuff with Matlab if we just open our minds, and possibly use some undocumented hacks. I'm not saying that Matlab has no limitations, I'm just saying that in most cases they can be overcome if we took the time and trouble to look for a solution. Matlab is a great tool and yet many people are not aware of its potential. Blaming Matlab for its failings is just an easy excuse in many cases. Of course, MathWorks could help my crusade on this subject by enabling useful features such as easy GUI component customizations…
On this sad day, I wish you all Shanah Tova!
uigetfile/uiputfile customizations
November 2, 2016, 4:38 pm
Matlab includes a few built-in file and folder selection dialog windows, namely uigetfile , uiputfile and uigetdir . Unfortunately, these functions are not easily extendable for user-defined functionalities. Over the years, several of my consulting clients have asked me to provide them with versions of these dialog functions that are customized in certain ways. In today's post I discuss a few of these customizations: a file selector dialog with a preview panel, and automatic folder update as-you-type in the file-name edit box.
It is often useful to have an integrated preview panel to display the contents of a file in a file-selection dialog. Clicking the various files in the tree-view would display a user-defined preview in the panel below, based on the file's contents. An integrated panel avoids the need to manage multiple figure windows, one for the selector dialog and another for the preview. It also reduces the screen real-estate used by the dialog (also see the related resizing customization below).
I call the end-result uigetfile_with_preview ; you can download it from the Matlab File Exchange:
filename = uigetfile_with_preview(filterSpec, prompt, folder, callbackFunction, multiSelectFlag)
As you can see from the function signature, the user can specify the file-type filter, prompt and initial folder (quite similar to uigetfile , uiputfile ), as well as a custom callback function for updating the preview of a selected file, and a flag to enable selecting multiple files (not just one).
uigetfile_with_preview.m only has ~120 lines of code and plenty of comments, so feel free to download and review the code. It uses the following undocumented aspects:
- I used a
com.mathworks.hg.util.dFileChooser
component for the main file selector. This is a builtin Matlab control that extends the standardjavax.swing.JFileChooser
with a few properties and methods. I don't really need the extra features, so you can safely replace the component with aJFileChooser
if you wish (lines 54-55). Various properties of the file selector are then set, such as the folder that is initially displayed, the multi-selection flag, the component background color, and the data-type filter options. - I used the javacomponent function to place the file-selector component within the dialog window.
- I set a callback on the component's PropertyChangeCallback that is invoked whenever the user interactively selects a new file. This callback clears the preview panel and then calls the user-defined callback function (if available).
- I set a callback on the component's ActionPerformedCallback that is invoked whenever the user closes the figure or clicks the "Open" button. The selected filename(s) is/are then returned to the caller and the dialog window is closed.
- I set a callback on the component's file-name editbox's KeyTypedCallback that is invoked whenever the user types in the file-name editbox. The callback checks whether the entered text looks like a valid folder path and if so then it automatically updates the displayed folder as-you-type.
If you want to convert the code to a uiputfile variant, add the following code lines before the uiwait in line 111:
hjFileChooser.setShowOverwriteDialog ( true ); % default: false (true will display a popup alert if you select an existing file) hjFileChooser.setDialogType (hjFileChooser.java.SAVE_DIALOG ); % default: OPEN_DIALOG hjFileChooser.setApproveButtonText ( 'Save' ); % or any other string. Default for SAVE_DIALOG: 'Save' hjFileChooser.setApproveButtonToolTipText ( 'Save file' ); % or any other string. Default for SAVE_DIALOG: 'Save selected file'
In memory of my dear father.
Working with non-standard DPI displays
November 9, 2016, 1:47 pm
With high-density displays becoming increasingly popular, some users set their display's DPI to a higher-than-standard (i.e., >100%) value, in order to compensate for the increased pixel density to achieve readable interfaces. This OS setting tells the running applications that there are fewer visible screen pixels, and these are spread over a larger number of physical pixels. This works well for most cases (at least on recent OSes, it was a bit buggy in non-recet ones). Unfortunately, in some cases we might actually want to know the screen size in physical, rather than logical, pixels. Apparently, Matlab root's ScreenSize property only reports the logical (scaled) pixel size, not the physical (unscaled) one:
>> get ( 0,'ScreenSize' ) % with 100% DPI (unscaled standard) ans = 1 1 1366 768 >> get ( 0,'ScreenSize' ) % with 125% DPI (scaled) ans = 1 1 1092.8 614.4
The same phenomenon also affects other related properties, for example MonitorPositions.
Raimund Schlüßler, a reader on this blog, was kind enough to point me to this problem and its workaround, which I thought worthy to share here: To get the physical screen-size, use the following builtin Java command:
>> jScreenSize = java.awt.Toolkit.getDefaultToolkit.getScreenSize jScreenSize = java.awt.Dimension [width=1366,height=768 ] >> width = jScreenSize.getWidth width = 1366 >> height = jScreenSize.getHeight height = 768
Upcoming travels – London/Belfast, Zürich & Geneva
I will shortly be traveling to consult some clients in Belfast (via London), Zürich and Geneva. If you are in the area and wish to meet me to discuss how I could bring value to your work, then please email me (altmany at gmail):
- Belfast: Nov 28 – Dec 1 (flying via London)
- Zürich: Dec 11-12
- Geneva: Dec 13-15
Sending email/text messages from Matlab
December 7, 2016, 1:24 pm
In this day and age, applications are expected to communicate with users by sending email/text messages to alert them about applicative events ("IBM stock purchased @$99.99" or "House is on fire!"). Matlab has included the sendmail function to handle this for many years. Unfortunately, sendmail requires some tweaking to be useful on all but the most basic/insecure mail servers. Today's post will hopefully fill the missing gaps.
None of the information I'll present today is really new – it was all there already if you just knew what to search for online. But hopefully today's post will concentrate all these loose ends in a single place, so it may have some value:
- Using a secure mail server
- Emailing multiple recipients
- Sending text messages
- User configuration panel
Using a secure mail server
All modern mail servers use end-to-end TLS/SSL encryption. The sendmail function needs extra configuration to handle such connections, since it is configured for a non-encrypted connection by default. Here's the code that does this for gmail, using SMTP server smtp.gmail.com
and default port #465 (for other SMTP servers, see here):
setpref( 'Internet', 'SMTP_Server', 'smtp.gmail.com' ); setpref( 'Internet', 'SMTP_Username', username); setpref( 'Internet', 'SMTP_Password', password); props = java.lang.System.getProperties; props.setProperty ( 'mail.smtp.auth', 'true' ); % Note: 'true' as a string, not a logical value! props.setProperty ( 'mail.smtp.starttls.enable', 'true' ); % Note: 'true' as a string, not a logical value! props.setProperty ( 'mail.smtp.socketFactory.port', '465' ); % Note: '465' as a string, not a numeric value! props.setProperty ( 'mail.smtp.socketFactory.class', 'javax.net.ssl.SSLSocketFactory' ); sendmail(recipient, title, body, attachments); % e.g., sendmail('recipient@gmail.com', 'Hello world', 'What a nice day!', 'C:\images\sun.jpg')
All this is not enough to enable Matlab to connect to gmail's SMTP servers. In addition, we need to set the Google account to allow access from "less secure apps" (details, direct link). Without this, Google will not allow Matlab to relay emails. Other mail servers may require similar server-side account configurations to enable Matlab's access.
Note: This code snippet uses a bit of Java as you can see. Under the hood, all networking code in Matlab relies on Java, and sendmail is no exception. For some reason that I don't fully understand, MathWorks chose to label the feature of using sendmail with secure mail servers as a feature that relies on "undocumented commands" and is therefore not listed in the mail doc-page for sendmail . Considering the fact that no modern mail server is non-secure, this seems to make the documented sendmail rather useless. I assume that they are well aware of this, which is the reason for making a partial documentation of this, in the form of an official tech-support answer. I hope that MathWorks will incorporate these "undocumented commands" into sendmail.m as optional input args in some upcoming release, so that using sendmail with secure servers becomes fully documented and officially supported.
Emailing multiple recipients
To specify multiple email recipients, it is not enough to set sendmail 's recipient
input arg to a string with , or ; delimiters. Instead, we need to provide a cell array of individual recipient strings. For example:
sendmail( { 'recipient1@gmail.com','recipient2@gmail.com' }, 'Hello world', 'What a nice day!' )
Note: this feature is actually fully documented in sendmail 's doc-page, but for some reason I see that some users are not aware of it (to which it might be said: RTFM!).
Sending text messages
With modern smartphones, text (SMS) messages have become rather outdated, as most users get push notifications of incoming emails. Still, for some users text messages may still be a useful. To send such messages, all we need is to determine our mobile carrier's email gateway for SMS messages, and send a simple text message to that email address. For example, to send a text message to T-Mobile number 123-456-7890 in the US, simply email the message to 1234567890@tmomail.net (details).
Ke Feng posted a nice Matlab File Exchange utility that wraps this messaging for a wide variety of US carriers.
User configuration panel
Many GUI programs contain configuration panels/tabs/windows. Enabling the user to set up their own email provider is a typical use-case for such a configuration. Naturally, you'd want your config panel not to display plain-text password, nor non-integer port numbers. You'd also want the user to be able to test the email connection.
Here's a sample implementation for such a panel that I implemented for a recent project – I plan to discuss the implementation details of the password and port (spinner) controls in my next post, so stay tuned:
User configuration of emails in Matlab GUI (click to zoom-in)
Password & spinner controls in Matlab GUI
December 14, 2016, 9:28 am
I often include configuration panels in my programs, to enable the user to configure various program aspects, such as which emails should automatically be sent by the program to alert when certain conditions occur. Last week I presented such a configuration panel, which is mainly composed of standard documented Matlab controls (sub-panels, uitables and uicontrols). As promised, today's post will discuss two undocumented controls that are often useful in similar configuration panels (not necessarily for emails): password fields and spinners.
Matlab GUI configuration panel including password and spinner controls (click to zoom-in)
Password fields are basically editboxes that hide the typed text with some generic echo character (such as * or a bullet); spinners are editboxes that only enable typing certain preconfigured values (e.g., numbers in a certain range). Both controls are part of the standard Java Swing package, on which the current (non-web-based) Matlab GUIs relies. In both cases, we can use the javacomponent function to place the built-in Swing component in our Matlab GUI.
Password field
The relevant Java Swing control for password fields is javax.swing.JPasswordField
. JPasswordField
is basically an editbox that hides any typed key with a * or bullet character.
Here's a basic code snippet showing how to display a simple password field:
jPasswordField = javax.swing.JPasswordField ( 'defaultPassword' ); % default password arg is optional jPasswordField = javaObjectEDT(jPasswordField); % javaObjectEDT is optional but recommended to avoid timing-related GUI issues jhPasswordField = javacomponent(jPasswordField, [ 10,10,70,20 ], gcf );
Password control
We can set/get the password string programmatically via the Text property; the displayed (echo) character can be set/get using the EchoChar property.
To attach a data-change callback, set jhPasswordField's ActionPerformedCallback property.
Spinner control
detailed post on using spinners in Matlab GUI
The relevant Java Swing control for spinners is javax.swing.JSpinner
. JSpinner
is basically an editbox with two tiny adjacent up/down buttons that visually emulate a small round spinning knob. Spinners are similar in functionality to a combo-box (a.k.a. drop-down or pop-up menu), where a user can switch between several pre-selected values. They are often used when the list of possible values is too large to display in a combo-box menu. Like combo-boxes, spinners too can be editable (meaning that the user can type a value in the editbox) or not (the user can only "spin" the value using the up/down buttons).
JSpinner
uses an internal data model. The default model is SpinnerNumberModel
, which defines a min/max value (unlimited=[] by default) and step-size (1 by default). Additional predefined models are SpinnerListModel
(which accepts a cell array of possible string values) and SpinnerDateModel
(which defines a date range and step unit).
Here's a basic code snippet showing how to display a simple numeric spinner for numbers between 20 and 35, with an initial value of 24 and increments of 0.1:
jModel = javax.swing.SpinnerNumberModel ( 24,20,35,0.1 ); jSpinner = javax.swing.JSpinner (jModel); jSpinner = javaObjectEDT(jSpinner); % javaObjectEDT is optional but recommended to avoid timing-related GUI issues jhSpinner = javacomponent(jSpinner, [ 10,10,70,20 ], gcf );
The spinner value can be set using the edit-box or by clicking on one of the tiny arrow buttons, or programmatically by setting the Value property. The spinner object also has related read-only properties NextValue and PreviousValue. The spinner's model object has the corresponding Value (settable), NextValue (read-only) and PreviousValue (read-only) properties. In addition, the various models have specific properties. For example, SpinnerNumberModel
has the settable Maximum, Minimum and StepSize properties.
To attach a data-change callback, set jhSpinner's StateChangedCallback property.
I have created a small Matlab demo, SpinnerDemo , which demonstrates usage of JSpinner
in Matlab figures. Each of the three predefined models (number, list, and date) is presented, and the spinner values are inter-connected via their callbacks. The Matlab code is modeled after the Java code that is used to document JSpinner
in the official Java documentation. Readers are welcome to download this demo from the Matlab File Exchange and reuse its source code.
Matlab SpinnerDemo
The nice thing about spinners is that you can set a custom display format without affecting the underlying data model. For example, the following code snippet update the spinner's display format without affecting its underlying numeric data model:
formatStr = '$###,##0.0 Bn'; jEditor = javaObject( 'javax.swing.JSpinner$NumberEditor', jhSpinner, formatStr); jhSpinner.setEditor (jEditor);
Formatted spinner control
For more information, refer to my detailed post on using spinners in Matlab GUI.
Caveat emptor
MathWorks' new web-based GUI paradigm will most probably not directly support the Java components presented in today's post, or more specifically the javacomponent function that enables placing them in Matlab GUIs. The new web-based GUI-building application (AppDesigner, aka AD) does contain a spinner, although it is [currently] limited to displaying numeric values (not dates/lists as in my SpinnerDemo). Password fields are not currently supported by AppDesigner at all, and it is unknown whether they will ever be.
All this means that users of Java controls who wish to transition to the new web-based GUIs will need to develop programmatic workarounds, that would presumably appear and behave less professional. It's a tradeoff: AppDesigner does include features that improve GUI usability, not to mention the presumed future ability to post Matlab GUIs online (hopefully without requiring a monstrous Matlab Production Server license/installation).
In the past, MathWorks has posted a dedicated webpage to solicit user feedback on how they are using the figure's JavaFrame property. MathWorks will presumably prepare a similar webpage to solicit user feedback on uses of the javacomponent function, so they could add the top items to AppDesigner, making the transition to web-based GUIs less painful. When such a survey page becomes live, I will post about it on this website so that you could tell MathWorks about your specific use-cases and help them prioritize their R&D efforts.
In any case, regardless of whether the functionality eventually makes it into AppDesigner, my hope is that when the time comes MathWorks will not pull the plug from non-web GUIs, and will still enable running them on desktops for backward compatibility ("legacy mode"). Users of existing GUIs will then not need to choose between upgrading their Matlab (and redeveloping their GUI as a web-based app) and running their existing programs. Instead, users will face the much less painful choice between keeping the existing Java-based programs and developing a web-based variant at some later time, separate from the choice of whether or not to upgrade Matlab. The increased revenue from license upgrades and SMS (maintenance plan) renewals might well offset the R&D effort that would be needed to keep supporting the old Java-based figures. The traumatic* release of HG2 in R2014b, where a less-than-perfect version was released with no legacy mode, resulting in significant user backlash/disappointment, is hopefully still fresh in the memory of decision makers and would hopefully not be repeated.
* well, traumatic for some at least. I really don't wish to make this a debate on HG2's release; I'd rather focus on making the transition to web-based GUIs as seamless as possible.
Customizing uifigures part 3
November 27, 2017, 7:00 am
As I have repeatedly posted in recent years, Matlab is advancing towards web-based GUI. The basic underlying technology is more-or-less stable: an HTML/Javascript webpage that is created-on-the-fly and rendered in a stripped-down browser window (based on Chromium-based jxBrowser in recent years). However, the exact mechanism by which the controls ("widgets") are actually converted into visible components (currently based on the Dojo toolkit and its Dijit UI library) and interact with Matlab (i.e., the internal Matlab class structures that interact with the browser and Dojo) is still undergoing changes and is not quite as stable.
Customization hacks reported on this blog last year (part 1, part 2) may fail in some cases due to the changing nature of the undocumented internals. Some examples are the way by which we can extract the uifigure's URL (which changed in R2017a), the ability to display and debug uifigures in a standard webbrowser with associated dev tools (which seems to have stopped working in R2017b), and the way by which we can extract the Dijit reference of displayed uicontrols.
Greatly assisting in this respect is Iliya Romm, who was the guest blogger for part 2 of this series last year. Iliya co-authored the open-source (GitHub) mlapptools toolbox, which enables accessing and customizing uifigure components using standard CSS, without users having to bother about the ugly hacks discussed in the previous parts of the series. This toolbox is really just a single Matlab class (mlapptools
), contained within a single m-file (mlapptools.m). In addition to this class, the toolbox includes a README.md mark-down usage documentation, and two demo functions, DOMdemoGUI.m and TableDemo.m.
Here is the effect of using TableDemo, that shows how we can customize individual uitable cells (each uitable cell is a separate Dijit widget that can be customized individually):
CSS customizations of uifigure components
The mlapptools class contains several static methods that can be used individually:
- textAlign(uielement, alignment) – Modify text horizontal alignment (
'left'
,'center'
,'right'
,'justify'
or'initial'
) - fontWeight(uielement, weight) – Modify font weight (
'normal'
,'bold'
,'bolder'
,'lighter'
or'initial'
), depending on availability in the font-face used - fontColor(uielement, color) – Modify font color (e.g.
'red'
,'#ff0000'
,'rgb(255,0,0)'
or other variants) - setStyle(uielement, styleAttr, styleValue) – Modify a specified CSS style attribute
- aboutDojo() – Return version information about the Dojo toolkit
- getHTML(hFig) – Return the full HTML code of a uifigure
- getWebWindow(hFig) – Return a webwindow handle from a uifigure handle
- getWebElements (hControl) – Return a webwindow handle and a widget ID for the specified uicontrol handle
- getWidgetList(hFig, verboseFlag) – Return a cell-array of structs containing information about all widgets in the uifigure
- getWidgetInfo(hWebwindow, widgetId, verboseFlag) – Return information about a specific dijit widget
- setTimeout(hFig, seconds) – Override the default timeout (=5 secs) for dojo commands, for a specific uifigure
A few simple usage examples:
mlapptools.fontColor (hButton,'red' ) % set red text color mlapptools.fontWeight (hButton,'bold' ) % set bold text font mlapptools.setStyle (hButton,'border','2px solid blue' ) % add a 2-pixel solid blue border mlapptools.setStyle (hButton,'background-image','url(https://www.mathworks.com/etc/designs/mathworks/img/pic-header-mathworks-logo.svg)' ) % add background image
Once you download mlapptools and add its location to the Matlab path, you can use it in any web-based GUI that you create, either programmatically or with Add-Designer.
The mlapptools is quite well written and documented, so if you are interested in the inner workings I urge you to take a look at this class's private methods. For example, to understand how a Matlab uicontrol handle is converted into a Dojo widget-id, which is then used with the built-in dojo.style() Javascript function to modify the CSS attributes of the HTML <div>
or <span>
that are the control's visual representation on the webpage. An explanation of the underlying mechanism can be found in part 2 of this series of articles on uifigure customizations. Note that the mlapptools code is newer than the article and contains some new concepts that were not covered in that article, for example searching through Dijit's registry of displayed widgets.
Note: web-based GUI is often referred to as "App-Designed" (AD) GUI, because using the Matlab App Designer is the typical way to create and customize such GUIs. However, just as great-looking GUIs could be created programmatically rather than with GUIDE, so too can web-based GUIS be created programmatically, using regular built-in Matlab commands such as uifigure , uibutton and uitable (an example of such programmatic GUI creation can be found in Iliya's TableDemo.m, discussed above). For this reason, I believe that the new GUIs should be referred to as "uifigures" or "web GUIs", and not as "AD GUIs".
If you have any feature requests or bugs related to mlapptools, please report them on its GitHub issues page. For anything else, please add a comment below.
Builtin PopupPanel widget
December 6, 2017, 8:00 am
8 years ago I blogged about Matlab's builtin HelpPopup widget. This control is used by Matlab to display popup-windows with help documentation, but can also be used by users to display custom lightweight popups that contain HTML-capable text and even URLs of entire webpages. Today I'd like to highlight another builtin Matlab widget, ctrluis.PopupPanel
, which can be used to display rich contents in a lightweight popup box attached to a specific Matlab figure:
Matlab's builtin PopupPanel widget
As you can see, this popup-panel displays richly-formatted contents, having either an opaque or transparent background, with vertical scrollbars being applied automatically. The popup pane is not limited to displaying text messages – in fact, it can display any Java GUI container (e.g. a settings panel). This popup-panel is similar in concept to the HelpPopup widget, and yet much more powerful in several aspects.
Creating the popup panel
Creating a PopupPanel
is very simple:
% Create the popup-panel in the specified figure hPopupPanel = ctrluis.PopupPanel ( gcf ); % use gcf or any figure handle hPopupPanel.setPosition ( [.1,.1,.8,.8] ); % set panel position (normalized units) % Alternative #1: set popup-panel's contents to some HTML-formatted message % note: createMessageTextPane() has optional input args FontName (arg #2), FontSize (#3) jPanel = ctrluis.PopupPanel.createMessageTextPane ( 'testing <b><i>123</i></b> ...' ) hPopupPanel.setPanel (jPanel); % Alternative #2: set popup-panel's contents to a webpage URL url = 'http://undocumentedmatlab.com/files/sample-webpage.html'; jPanel = javaObjectEDT(javax.swing.JEditorPane (url) ); hPopupPanel.setPanel (jPanel);
The entire contents are embedded within a scroll-box (which is a com.mathworks.widgets.LightScrollPane
object) whose scrollbars automatically appear as-needed, so we don't need to worry about the contents fitting the allocated space.
To display custom GUI controls in the popup, we can simply contain those GUI controls in a Java container (e.g., a JPanel
) and then do hPopupPanel.setPanel(jPanel)
. This functionality can be used to create unobtrusive settings panels, input dialogs etc.
The nice thing about the popup widget is that it is attached to the figure, and yet is not assigned a heavyweight window (so it does not appear in the OS task-bar). The popup moves along with the figure when the figure is moved, and is automatically disposed when the figure is closed.
A few caveats about the ctrluis.PopupPanel
control:
- The widget's parent is expected to be a figure that has pixel units. If it doesn't, the internal computations of
ctrluis.PopupPanel
croak. - The widget's position is specified in normalized units (default: [0,0,1,1]). This normalized position is only used during widget creation: after creation, if you resize the figure the popup-panel's position remains unchanged. To modify/update the position of the popup-panel programmatically, use
hPopupPanel.setPosition(newPosition)
. Alternatively, update the control's Position property and then callhPopupPanel.layout()
(there is no need to calllayout
when you usesetPosition
). - This functionality is only available for Java-based figures, not the new web-based (AppDesigner) uifigures.
Popup panel customizations
We can open/close the popup panel by clicking on its icon, as shown in the screenshots above, or programmatically using the control's methods:
% Programmatically open/close the popup-panel hPopupPanel.showPanel; hPopupPanel.hidePanel; % Show/hide entire popup-panel widget (including its icon) hPopupPanel.setVisible ( true ); % or .setVisible(1) or .Visible=1 hPopupPanel.setVisible ( false ); % or .setVisible(0) or .Visible=0
To set a transparent background to the popup-panel (as shown in the screenshots above), we need to unset the opacity of the displayed panel and several of its direct parents:
% Set a transparent popup-panel background for idx = 1 : 6 jPanel.setOpaque ( false ); % true=opaque, false=transparent jPanel = jPanel.getParent; end jPanel.repaint
Note that in the screenshots above, the panel's background is made transparent, but the contained text and image remain opaque. Your displayed images can of course contain transparency and animation, if this is supported by the image format (for example, GIF).
iptui.internal.utilities.addMessagePane
ctrluis.PopupPanel
is used internally by iptui.internal.utilities.addMessagePane(hFig,message)
in order to display a minimizable single-line message panel at the top of a specified figure:
hPopupPanel = iptui.internal.utilities.addMessagePane ( gcf, 'testing <b>123</b> ...' ); % note the HTML formatting
The function updates the message panel's position whenever the figure's size is modified (by trapping the figure's SizeChangedFcn), to ensure that the panel is always attached to the top of the figure and spans the full figure width. This is a simple function so I encourage you to take a look at its code (%matlabroot%/toolbox/images/imuitools/+iptui/+internal/+utilities/addMessagePane.m) – note that this might require the Image Processing Toolbox (I'm not sure).
Matlab's builtin iptui.internal.utilities.addMessagePane
Professional assistance anyone?
As shown by this and many other posts on this site, a polished interface and functionality is often composed of small professional touches, many of which are not exposed in the official Matlab documentation for various reasons. So if you need top-quality professional appearance/functionality in your Matlab program, or maybe just a Matlab program that is dependable, robust and highly-performant, consider employing my consulting services.
PlotEdit context-menu customization
December 13, 2017, 4:57 am
Last week, a Matlab user asked whether it is possible to customize the context (right-click) menu that is presented in plot-edit mode. This menu is displayed by clicking the plot-edit (arrow) icon on the standard Matlab figure toolbar, then right-clicking any graphic/GUI element in the figure. Unfortunately, it seems that this context menu is only created the first time that a user right-clicks in plot-edit mode – it is not accessible before then, and so it seems impossible to customize the menu before it is presented to the user the first time.
Customized plot-edit context-menu
A few workarounds were suggested to the original poster and you are most welcome to review them. There is also some discussion about the technical reasons that none of the "standard" ways of finding and modifying menu items fail in this case.
In today's post I wish to repost my solution, in the hope that it might help other users in similar cases.
My solution is basically this:
- First, enter plot-edit mode programmatically using the plotedit function
- Next, move the mouse to the screen location of the relevant figure component (e.g. axes). This can be done in several different ways (the root object's PointerLocation property, the moveptr function, or
java.awt.Robot.mouseMove()
method). - Next, automate a mouse right-click using the built in
java.awt.Robot
class (as discussed in this blog back in 2010) - Next, locate the relevant context-menu item and modify its label, callback or any of its other properties
- Next, dismiss the context-menu by simulating a follow-on right-click using the same
Robot
object - Finally, exit plot-edit mode and return the mouse pointer to its original location
% Create an initial figure / axes for demostration purpose fig = figure ( 'MenuBar','none','Toolbar','figure' ); plot ( 1:5 ); drawnow; % Enter plot-edit mode temporarily plotedit(fig,'on' ); drawnow % Preserve the current mouse pointer location oldPos = get ( 0,'PointerLocation' ); % Move the mouse pointer to within the axes boundary % ref: https://undocumentedmatlab.com/blog/undocumented-mouse-pointer-functions figPos = getpixelposition(fig); % figure position axPos = getpixelposition( gca,1 ); % axes position figure (fig); % ensure that the figure is in focus newPos = figPos( 1:2 ) + axPos( 1:2 ) + axPos( 3:4 )/4; % new pointer position set ( 0,'PointerLocation',newPos); % alternatives: moveptr(), java.awt.Robot.mouseMove() % Simulate a right-click using Java robot % ref: https://undocumentedmatlab.com/blog/gui-automation-robot robot = java.awt.Robot; robot.mousePress (java.awt.event.InputEvent.BUTTON3_MASK ); pause ( 0.1 ) robot.mouseRelease (java.awt.event.InputEvent.BUTTON3_MASK ); pause ( 0.1 ) % Modify the <clear-axes> menu item hMenuItem = findall(fig,'Label','Clear Axes' ); if ~isempty (hMenuItem) label = '<html><b><i><font color="blue">Undocumented Matlab'; callback = 'web(' 'https://undocumentedmatlab.com' ',' '-browser' ');'; set (hMenuItem, 'Label',label, 'Callback',callback); end % Hide the context menu by simulating a left-click slightly offset set ( 0,'PointerLocation',newPos+[-2,2 ] ); % 2 pixels up-and-left pause ( 0.1 ) robot.mousePress (java.awt.event.InputEvent.BUTTON1_MASK ); pause ( 0.1 ) robot.mouseRelease (java.awt.event.InputEvent.BUTTON1_MASK ); pause ( 0.1 ) % Exit plot-edit mode plotedit(fig,'off' ); drawnow % Restore the mouse pointer to its previous location set ( 0,'PointerLocation',oldPos);
In this code, I sprinkled a few pause s at several locations, to ensure that everything has time to fully render. Different pause values, or perhaps no pause at all, may be needed on your specific system.
Modifying the default context-menu shown in plot-edit mode may perhaps be an uncommon use-case. But the technique that I demonstrated above – of using a combination of Matlab and Java Robot
commands to automate a certain animation – can well be used in many other use-cases where we cannot easily access the underlying code. For example, when the internal code is encoded/encrypted, or when a certain functionality (such as the plot-edit context-menu) is created on-the-fly.
If you have encountered a similar use-case where such automated animations can be used effectively, please add a comment below.
Toolbar button labels
January 8, 2018, 9:34 am
I was recently asked by a client to add a few buttons labeled "1"-"4" to a GUI toolbar. I thought: How hard could that be? Simply get the toolbar's handle from the figure, then use the builtin uipushtool function to add a new button, specifying the label in the String property, right?
Well, not so fast it seems:
hToolbar = findall(hFig, 'tag','FigureToolBar' ); % get the figure's toolbar handle uipushtool(hToolbar, 'String','1' ); % add a pushbutton to the toolbar Error using uipushtool There is no String property on the PushTool class.
Apparently, for some unknown reason, standard Matlab only enables us to set the icon (CData) of a toolbar control, but not a text label.
Once again, Java to the rescue:
We first get the Java toolbar reference handle, then add the button in standard Matlab (using uipushtool , uitoggletool and their kin). We can now access the Java toolbar's last component, relying on the fact that Matlab always adds new buttons at the end of the toolbar. Note that we need to use a short drawnow to ensure that the toolbar is fully re-rendered, otherwise we'd get an invalid Java handle. Finally, once we have this reference handle to the underlying Java button component, we can set and customize its label text and appearance (font face, border, size, alignment etc.):
hToolbar = findall(hFig, 'tag','FigureToolBar' ); % get the figure's toolbar handle jToolbar = hToolbar.JavaContainer.getComponentPeer; % get the toolbar's Java handle for buttonIdx = 1 : 4 % First create the toolbar button using standard Matlab code label = num2str (buttonIdx); % create a string label uipushtool(hToolbar, 'ClickedCallback',{ @myCallback,analysisIdx}, 'TooltipString',[ 'Run analysis #' label] ); % Get the Java reference handle to the newly-created button drawnow; pause ( 0.01 ); % allow the GUI time to re-render the toolbar jButton = jToolbar.getComponent (jToolbar.getComponentCount-1 ); % Set the button's label jButton.setText (label) end
The standard Matlab toolbar button size (23×23 pixels) is too small to display more than a few characters. To display a longer label, we need to widen the button:
% Make the button wider than the standard 23 pixels newSize = java.awt.Dimension ( 50, jButton.getHeight ); jButton.setMaximumSize (newSize) jButton.setPreferredSize (newSize) jButton.setSize (newSize)
Using a text label does not prevent us from also displaying an icon: In addition to the text label, we can also display a standard icon (by setting the button's CData property in standard Matlab). This icon will be displayed to the left of the text label. You can widen the button, as shown in the code snippet above, to make space for both the icon and the label. If you want to move the label to a different location relative to the icon, simply modify the Java component's HorizontalTextPosition property:
jButton.setHorizontalTextPosition (jButton.RIGHT ); % label right of icon (=default) jButton.setHorizontalTextPosition (jButton.CENTER ); % label on top of icon jButton.setHorizontalTextPosition (jButton.LEFT ); % label left of icon
In summary, here's the code snippet that generated the screenshot above:
% Get the Matlab & Java handles to the figure's toolbar hToolbar = findall(hFig, 'tag','FigureToolBar' ); % get the figure's toolbar handle jToolbar = hToolbar.JavaContainer.getComponentPeer; % get the toolbar's Java handle % Button #1: label only, no icon, 23x23 pixels h1 = uipushtool(hToolbar); drawnow; pause ( 0.01 ); jButton = jToolbar.getComponent (jToolbar.getComponentCount-1 ); jButton.setText ( '1' ) % Create the icon CData from an icon file graphIcon = fullfile ( matlabroot,'/toolbox/matlab/icons/plotpicker-plot.gif' ); [graphImg,map] = imread (graphIcon); map(map(:,1 )+map(:,2 )+map(:,3 )==3 ) = NaN; % Convert white pixels => transparent background cdata = ind2rgb(graphImg,map); % Button #2: label centered on top of icon, 23x23 pixels h2 = uipushtool(hToolbar, 'CData',cdata); drawnow; pause ( 0.01 ); jButton = jToolbar.getComponent (jToolbar.getComponentCount-1 ); jButton.setText ( '2' ) jButton.setHorizontalTextPosition (jButton.CENTER ) % Button #3: label on right of icon, 50x23 pixels h3 = uipushtool(hToolbar, 'CData',cdata); drawnow; pause ( 0.01 ); jButton = jToolbar.getComponent (jToolbar.getComponentCount-1 ); jButton.setText ( '3...' ) d = java.awt.Dimension ( 50, jButton.getHeight ); jButton.setMaximumSize (d); jButton.setPreferredSize (d); jButton.setSize (d) % Button #4: label on left of icon, 70x23 pixels h4 = uipushtool(hToolbar, 'CData',cdata); drawnow; pause ( 0.01 ); jButton = jToolbar.getComponent (jToolbar.getComponentCount-1 ); jButton.setText ( 'and 4:' ) jButton.setHorizontalTextPosition (jButton.LEFT ) d = java.awt.Dimension ( 70, jButton.getHeight ); jButton.setMaximumSize (d); jButton.setPreferredSize (d); jButton.setSize (d)
Many additional toolbar customizations can be found here and in my book " Undocumented Secrets of MATLAB-Java Programming ". If you'd like me to design a professional-looking GUI for you, please contact me.
Caveat emptor: all this only works with the regular Java-based GUI figures, not web-based ("App-Designer") uifigures.
IP address input control
January 31, 2018, 8:16 am
A few weeks ago, a user posted a question on Matlab Answers, asking whether it is possible to implement a text input control that accepts and validates an IP address (for example, '192.168.1.101'). While doing this using purely documented Matlab code is indeed possible (for those of us who are masochistically inclined and/or have nothing else to do with their spare time), a very simple-to-use and polished-looking solution is to use an undocumented built-in Matlab control.
The solution is based on the fact that Matlab comes with a huge set of professional Java-based controls by JideSoft, bundled in various JAR libraries within the %matlabroot%/java/jarext/jide Matlab installation folder. For our specific purposes (an IP-address entry/display control), we are interested in the com.jidesoft.field.IPTextField
control (online documentation), which is part of the JIDE Grids library (%matlabroot%/java/jarext/jide/jide-grids.jar). We can use it as follows:
jIPField = com.jidesoft.field.IPTextField ( '255.255.255.0' ); % set default IP [jIPField, hContainer] = javacomponent(jIPField, [ 10,10,120,20 ], hParent); % hParent: panel/figure handle
IPTextField control in a Matlab GUI
You can modify the position/size of the text-field in the javacomponent call above, or by modifying the Position / Units properties of the returned hContainer
.
We can retrieve the IP text/numeric values using:
vals = jIPField.getValue'; % 1x4 uint32 array => [255,255,255,0] vals = cell (jIPField.getRawText )'; % 1x4 string cells => {'255','255','255','0'} ip = char (jIPField.getText ); % entire IP string => '255.255.255.0'
The IPTextField
component auto-validates the IP values, ensuring that the displayed IP is always valid (for example, IP components cannot be negative or larger than 255). The component has many other features, including the ability to enable/disable, color or format the IP components etc.
We can set a callback function to process user changes, by setting the component's *StateChangedCallback* property, for example:
jIPField.StateChangedCallback = @ (jComponent,jEventData) disp (jComponent.getValue');
The JIDE libraries that come with Matlab contain numerous other similarly-useful components, including date/time/font/color/file/folder selectors, calendars in various formats, credit-card fields, and many more.
For more information about using the javacomponent function and handling Java components in Matlab GUI, see other posts on this website – particularly those marked with the "JIDE" tag.
Additional discussion of JIDE's combo-boxes, and JIDE controls in general, is available in Chapter 5 of my Matlab-Java Programming book.
If you need to integrate professional-looking controls such as these in your Matlab GUI, consider hiring my consulting services.
Caution
Remember that JIDE evolves with Matlab, and so JIDE's online documentation, which refers to the latest JIDE version, may be partially inapplicable if you use an old Matlab version. In any case, Matlab releases always lag the latest JIDE release by at least a year (e.g., Matlab R2017b still uses JIDE v3.4.1 that was released in June 2012 – MathWorks used to update the bundled JIDE libraries to newer versions, but for some reason has stopped doing that in 2013). The older your Matlab, the more such inconsistencies that you may find. For example, I believe that DateSpinnerComboBox
only became available around R2010b; similarly, some control properties behave differently (or are missing altogether) in different releases. To determine the version of JIDE that you are currently using in Matlab, run the following (the result can then be compared to JIDE's official change-log history):
>> com.jidesoft.utils.Lm.getProductVersion ans = 3.4.1
Note that JIDE is a commercial product. We may not use it without JIDESoft's permission outside the Matlab environment. It is my understanding however, that we can freely use it within Matlab. Note that this is not legal advise as I am an engineer, not a lawyer. If you have any licensing questions, contact sales@jidesoft.com.
Also note that all of JIDE's controls use the Java-based figures (that are created using GUIDE or the figure function), and will not work on the new web-based uifigures (created using App Designer or the uifigure function). There is currently no corresponding IP entry/display control for web-based GUIs, and since there is [still] no way to integrate external Javascript/CSS libraries in uifigures, the only resort is to use a plain-vanilla edit-box. If MathWorks would simply open a hook to integrate external JS/CSS libraries, that would enable users to use 3rd-party libraries that have such custom controls and MathWorks would then not need to spend a huge amount of effort to develop multiple UI control variants.
Matlab App Designer After Press Button Focus Uigetfile
Source: https://matlab172.rssing.com/chan-30911053/all_p3.html
Posted by: andersonmandist95.blogspot.com
0 Response to "Matlab App Designer After Press Button Focus Uigetfile"
Post a Comment