Do you ever get a feeling when designing a Matlab GUI, that existing components/controls are simply not enough to achieve the desired functionality/appearance?
Such a case happened to me, when a consulting client asked me to integrate an auto-completion widget in a GUI that I designed for them. The idea was simple enough: the user selects a class of financial assets from a drop-down, then one or more actual financial securities from a dynamically-populated drop-down (based on the asset class), then the date range and analysis function, and finally the results are plotted in the main panel. The idea was for Matlab to automatically auto-complete the financial instruments matching the entered text, as it is being typed, similarly to other standard auto-completion widgets (e.g., Google’s search box), including the use of wildcards and regular expressions:
Note that in this particular case, I use the term “auto-completion” loosely. The correct term should actually be “auto-winnowing” or “auto-filtering”. Auto-completion is usually reserved for the case of the user-entered text being automatically completed as they type, whereas auto-winnowing only updates the drop-down options on-the-fly. These two functionalities are often correlated, and today’s article will discuss both.
AutoCompletionList
Before I dive into details of the implementation I ended up with, note that there are simpler alternatives. For example, we can use Matlab’s internal com.mathworks.widgets.AutoCompletionList
widget:
strs = {'This','is','test1','test2'}; strList = java.util.ArrayList; for idx = 1 : length(strs), strList.add(strs{idx}); end jPanelObj = com.mathworks.widgets.AutoCompletionList(strList,''); javacomponent(jPanelObj.getComponent, [10,10,200,100], gcf); |
The
AutoCompletionList
control is actually a list (rather than a drop-down), where the user types an entry in the header row. The control automatically selects the first corresponding list item and auto-completes the rest of the entry. Invalid user entries generate a beep and are not allowed by default (unless the widget’s Strict property is cleared: jPanelObj.setStrict(false)
). The visible list size can be controlled by setting the widget’s VisibleRowCount property (jPanelObj.setVisibleRowCount(6)
).Items can be selected by either typing in the header row, by selecting a list item, or programmatically (
jPanelObj.setSelectedValue('test1')
). The currently selected item is retrieved via the same SelectedValue property (selectedValue = char(jPanelObj.getSelectedValue)
). As with many other actionable Java controls, we can attach a callback on the ActionPerformed event:
set(handle(jPanelObj,'callbackproperties'), 'ActionPerformedCallback', @myMatlabCallbackFunc); |
We can attach a similar callback to key-typing within the header row (text field), by accessing the widget’s internal component (which is the one that is actually being displayed via the javacomponent function):
set(handle(jPanelObj.getComponent,'callbackproperties'), 'KeyTypedCallback', @myMatlabCallbackFunc); |
SearchTextField
For my client’s purposes, however, AutoCompletionList
could not be used. We wanted a drop-down selector that takes up far less space than a listbox. The first thought was to use a modified drop-down (editable combo-box). This turned out to be less effective than hoped, because of the interference between the Matlab KeyTypedCallback function and the automatic behavior of the combo-box. I do not rule out the use of such an editable combo-box in other use-cases, but for this implementation I chose to use a different control, namely Matlab’s internal com.mathworks.widgets.SearchTextField
, which has some nice additional features such as an optional light-gray prompt, and a clickable search icon that changes its appearance and behavior based on the entered text:
jPanelObj = com.mathworks.widgets.SearchTextField('Enter search term:'); [jhPanel,hContainer] = javacomponent(jPanelObj.getComponent, [10,10,150,25], gcf); |
An optical illusion
As with a regular combo-box, the dropdown-menu integration in Matlab proved a bit difficult, especially due to the auto-completion feature. Again, I do not rule out using it in other use-cases, but for this implementation I chose to use a visual illusion: an actual combo-box is placed beneath (hidden by) the SearchTextField
control. So basically, we are seeing two separate parts of two different components: the edit-box of the SearchTextField
, and the dropdown panel (a JPopupMenu
) of the hidden combo-box. They appear attached, providing the optical illusion of being a single widget, when in fact they are not. Neat, right?
function createMainGUI() ... handles.cbAssetSearch = createAssetSelector(hAssetSearchPanel); ... end % Create the asset-name selector control within a parent container (panel/figure/tab/...) function hContainer = createAssetSelector(hParent) % Create a uipanel to hold both sub-components below, one occluding the other: hContainer = handle(uipanel('BorderType','none', 'Parent',hParent)); % Create a standard editable combo-box (drop-down) control callback = {@searchComboUpdated,hContainer}; % set to [] to disable asset combo selection callback hContainer1 = createComboSelector(hContainer, {'All'}, callback, true); set(hContainer1, 'Units','pixels', 'Position',[1,1,2,2]); % Create a SearchTextField control on top of the combo-box jAssetChooser = com.mathworks.widgets.SearchTextField('Enter search:'); jAssetComponent = jAssetChooser.getComponent; [jhAssetComponent, hContainer2] = javacomponent(jAssetComponent,[],hContainer); hContainer2 = handle(hContainer2); set(hContainer2, 'tag','hAssetContainer', 'UserData',jAssetChooser, 'Units','norm', 'Position',[0,0,1,1]); setappdata(hContainer,'jAssetChooser',jAssetChooser); % Expand the SearchTextField component to max available width jSize = java.awt.Dimension(9999, 20); jAssetComponent.getComponent(0).setMaximumSize(jSize); jAssetComponent.getComponent(0).setPreferredSize(jSize); % Add callback handlers hjSearchButton = handle(jAssetComponent.getComponent(1), 'CallbackProperties'); set(hjSearchButton, 'MouseClickedCallback', {@updateSearch,jCombo,jAssetChooser}); hjSearchField = handle(jAssetComponent.getComponent(0), 'CallbackProperties'); set(hjSearchField, 'KeyPressedCallback', {@updateSearch,jCombo,jAssetChooser}); jCombo = handle(hContainer1.UserData, 'CallbackProperties'); jComboField = handle(jCombo.getComponent(2), 'CallbackProperties'); set(jComboField, 'KeyPressedCallback', {@updateSearch,jCombo,[]}); set(jCombo, 'FocusLostCallback', @(h,e)jCombo.hidePopup); % hide the popup when another component is selected % Return the containing panel handle hContainer.UserData = [jCombo, jhAssetComponent, handle(jAssetChooser)]; end % createAssetSelector function hContainer = createComboSelector(hParent, strings, callback, isEditable) % Note: MJComboBox is better than JComboBox: the popup panel has more width than the base control if needed jComboBox = com.mathworks.mwswing.MJComboBox(strings); % =javax.swing.JComboBox(strings); jComboBox.setEditable(isEditable); jComboBox.setBackground(java.awt.Color.white); % unfortunately, this only affects editable combos [jhComboBox, hContainer] = javacomponent(jComboBox, [], hParent); set(jhComboBox, 'ActionPerformedCallback', callback); hContainer = handle(hContainer); set(hContainer, 'tag','hAssetContainer', 'UserData',jComboBox); end |
where the callback function for item selection might look something like this (here’s an example of the callback for the asset-search selector):
% Callback function for the asset selector combo function searchComboUpdated(jCombo, eventData, hPanel) selectedItem = regexprep(char(jCombo.getSelectedItem),'<[^>]*>',''); % strip away HTML tags jSearchTextField = hPanel.UserData(2).getComponent(0); jSearchTextField.setText(selectedItem); jSearchTextField.repaint; drawnow; pause(0.01); jAssetChooser = getappdata(hPanel,'jAssetChooser'); updateSearch([],[],jCombo,jAssetChooser); end % searchComboUpdated |
Callback events are used to synchronize the components, update the combo-box’s dropdown options and display the dropdown panel. We attach the same callback function, updateSearch()
, to 3 separate events: MouseClickedCallback on the SearchTextField
‘s search button (icon), KeyPressedCallback on the search text-box, and another KeyPressedCallback on the combo-box’s text-box (which is not visible, but automatically receives focus when the user interacts with the popup menu (drop-down panel):
% Create the SearchTextField component (after the hidden combo was created) jAssetChooser = com.mathworks.widgets.SearchTextField('Enter search:'); jAssetComponent = jAssetChooser.getComponent; [jhAssetComponent, hContainer] = javacomponent(jAssetComponent,[],hPanel); % Set callbacks: % 1) search icon mouse-click hjSearchButton = handle(jAssetComponent.getComponent(1), 'CallbackProperties'); set(hjSearchButton, 'MouseClickedCallback', {@updateSearch,jCombo,jAssetChooser}); % 2) search textbox key-click hjSearchField = handle(jAssetComponent.getComponent(0), 'CallbackProperties'); set(hjSearchField, 'KeyPressedCallback', {@updateSearch,jCombo,jAssetChooser}); % 3) popup panel key-click jComboField = handle(jCombo.getComponent(2), 'CallbackProperties'); set(jComboField, 'KeyPressedCallback', {@updateSearch,jCombo,[]}); |
(note that these callbacks were included in the createAssetSelector()
example above)
The user can now select an item either from the combo-box’s dropdown panel, or by typing in the search text-box. Here is the implementation of the updateSearch()
callback function:
% Asset search popup combo button click callback function updateSearch(hObject, eventData, jCombo, jAssetChooser) %#ok<inusl> persistent lastSearchText if isempty(lastSearchText), lastSearchText = ''; end try % event occurred on the search field component try searchText = jAssetChooser.getSearchText; jSearchTextField = jAssetChooser.getComponent.getComponent(0); catch % Came via asset change - always update jSearchTextField = jAssetChooser.getComponent(0); searchText = jSearchTextField.getText; lastSearchText = '!@#$'; end catch try % event occurred on the jCombo-box itself searchText = jCombo.getSelectedItem; catch % event occurred on the internal edit-field sub-component searchText = jCombo.getText; jCombo = jCombo.getParent; end jSearchTextField = jCombo.getComponent(jCombo.getComponentCount-1); end searchText = strrep(char(searchText), '*', '.*'); % turn into a valid regexp searchText = regexprep(searchText, '<[^>]+>', ''); if strcmpi(searchText, lastSearchText) && ~isempty(searchText) jCombo.showPopup; return; % maybe just clicked an arrow key or Home/End - no need to refresh the popup panel end lastSearchText = searchText; assetClassIdx = getappdata(handles.cbAssetClass, 'assetClassIdx'); if isempty(assetClassIdx) jCombo.hidePopup; return; elseif isempty(searchText) assetNamesIdx = assetClassIdx; else searchComponents = strsplit(searchText, ' - '); assetCodeIdx = ~cellfun('isempty',regexpi(data.header.AssetCode(assetClassIdx),searchComponents{1})); assetNameIdx = ~cellfun('isempty',regexpi(data.header.AssetName(assetClassIdx),searchComponents{end})); if numel(searchComponents) > 1 assetNamesIdx = assetClassIdx(assetCodeIdx & assetNameIdx); else assetNamesIdx = assetClassIdx(assetCodeIdx | assetNameIdx); end end setappdata(handles.cbAssetSearch, 'assetNameIdx', assetNamesIdx); if isempty(assetNamesIdx) jCombo.hidePopup; jSearchTextField.setBackground(java.awt.Color.yellow); jSearchTextField.setForeground(java.awt.Color.red); newFont = jSearchTextField.getFont.deriveFont(uint8(java.awt.Font.BOLD)); jSearchTextField.setFont(newFont); return; else jSearchTextField.setBackground(java.awt.Color.white); jSearchTextField.setForeground(java.awt.Color.black); newFont = jSearchTextField.getFont.deriveFont(uint8(java.awt.Font.PLAIN)); jSearchTextField.setFont(newFont); end % Compute the filtered asset names (highlight the selected search term) assetNames = strcat(data.header.AssetCode(assetNamesIdx), ' -=', data.header.AssetName(assetNamesIdx)); assetNames = regexprep(assetNames, '(.+) -=', '$1', 'ignorecase'); assetNames = unique(strrep(assetNames, ' -=', ' - ')); if ~isempty(searchText) assetNames = regexprep(assetNames, ['(' searchText ')'], '<b><font color=blue>$1</font></b>', 'ignorecase'); assetNames = strcat('<html>', assetNames); end % Redisplay the updated combo-box popup panel jCombo.setModel(javax.swing.DefaultComboBoxModel(assetNames)); jCombo.showPopup; end % updateSearch |
Here is what the final result looks like and behaves:
Would you believe that this entire program is only 400 lines of code?!
Conclusion
I’ve heard it say on occasion that Matlab GUI is not really suited for professional applications. I completely disagree, and hope that today’s article proves otherwise. You can make Matlab GUI do wonders, in various different ways. Matlab does have limitations, but they are nowhere close to what many people believe. If you complain that your GUI sucks, then it is likely not because of Matlab’s lack of abilities, but because you are only using a very limited portion of them. This is no different than any other programming environment (admittedly, such features are much better documented in other environments).
In short, to improve your GUI’s functionality and appearance, you just need to spend a bit of time searching for the right components (possibly using my book), or hire a professional consultant to do it for you. But of course, just bitching about Matlab’s supposed limitations is much easier…
Do you have a GUI that you find hard to believe can be done (or improved) in Matlab? Contact me for a consulting proposal and let me surprise you!
Hi Yair,
Another excellent example of what you can do with Matlab GUI’s. With some cool tricks and stretching the core Matlab capability you can design highly professional GUI’s, something that I have also done for many years.
I agree with you that the opinion of so many users is that Matlab cannot produce high quality graphical applications, its a real shame.
Regards
I think what most people mean when bitching about MATLAB’s GUI are the limitations of the standard programming language. Most of your great tricks involve the java miracle (or nightmare) under the hood of MATLAB, which for most people might qualify as getting your hands dirty. You can learn to produce decent and efficient MATLAB code in a relatively short time; getting to grips with the standard GUI takes longer; but for someone who’s java-illiterate (like me) it seems like overkill to learn java for the sole purpose of exploiting undocumented MATLAB functionality. So I totally understand those who bitch about MATLAB, especially GUI-wise 🙂
That said, your solutions are awesome and clear in retrospect, but I don’t think a non-MATLAB-guru could develop such codes (defining a MATLAB-guru as someone who doesn’t only rely on the native functions of MATLAB, but tackles the java backend as well), hence comments about MATLAB’s (standard) functionalities.
Thanks for the inspiration….
I totally agree. Matlab does not limit the creativity regarding UI design. It is really nice to see that it integrates Swing UI components so well. I am now in the process of extending a program written in Matlab with custom components. It really makes the possibilities endless (Only limited by time)
It would be nice to see Mathworks upgrade Matlab to Java 8 and add JavaFX.
@Ulrik
You can upgrade Java by copying the Java 8 JRE to your MATLAB app folder with many fewer problems than occurred with Java 6 -> Java 7.
To enable JavaFX, add javafxrt.jar (from the JRE) to your Java path using javaaddpath, then create an instance of
javafx.embed.swing.JFXPanel
. This will cause the JRE to do the work for you and initialise the JavaFX application thread.If you create a JavaFX application, be sure to prevent javafx.application.Platform.exit() being called when you close it. The JavaFX thread will not robustly initialise twice in a given MATLAB instance. See https://docs.oracle.com/javase/8/javafx/api/javafx/application/Platform.html
There are threading issues between the the AWT/Swing EDT and the JavaFX application thread but I understand that these have been reduced for contents of a
JFXPanel
in the latest Java 8 releases.JFXPanel
is a Swing component able to display JavaFX and can be added to a MATLAB GUI using javacomponent (or Yair’s uicomponent or my jcontrol, which build on that).M
Thank you for your post.
I am using combobox and editbox to do the same thing right now. However, Matlab will report
java.lang.OutOfMemoryError: GC overhead limit exceeded
So I am looking for another solution to approach my goal.
I try to reproduce your code on my machine. But I have question on the following statement.
What is “handles” here for? jCombo? jAssetChooser? Or something else?
@ikc – here’s a pared-down code-snippet that explains how cbAssetClass was created:
@Yair
Thank you for your reply.
I think I have figured out how it works.
This could be a fancy search method we can use in MATLAB.
However,
I still encounter the Java OutOfMemory Error.
My MATLAB Java Heap Memory is 384 MB (Default).
What my Java script doing here is reading the description from the database and display the description on the GUI.
When user press and release the down arrow on the keyboard, selection will be moved to next item and description will be updated.
The challenge is, when user holding the down arrow, selection will be moved to next item very quickly, but the description update speed can’t catch up with the selection move speed.
I am using the following statement to capture the memory usage.
I can see the Java is consuming a ton of memory.
Do you have any thought on releasing the Java memory or only way we can do is increasing the heap size?
Thanks a lot.
Hello,
I’m trying to implement simple Auto Completion box in my simplistic GUI. And I have few Questions as a newbie to this area. I hope you would be patient enough to help me.
1. Can I implement these objects in my GUIDE built GUI? I started with GUIDE since it’s very easy to start going with. But I found that I don’t have this component. So How I get along?
2. As a follow-up to the last question, should I try to program my GUI from scratch? what are the advantages compared to the workload of creating everything manually?
3. Can you explain what each of the following commands in your example mean? I read your javacomponent article but I don’t understand how each argument affects the output.
4. While playing around I tried to update the list. Updating the array was easy but I couldn’t find the object / property to update nor any proper update function to use. I tried different get options but it didn’t get me far enough.
Thanks,
Rami
@Rami – This blog discusses advanced Matlab topics, which are typically way beyond the basics. GUIDE only includes basic controls in its designer. However, you can add any control, including all the ones that I highlight in this blog, within the m-file that is generated by GUIDE. Advanced Matlab users often find that it becomes easier to generate the entire GUI by hand, without using GUIDE at all, but you can add the advanced controls in either. The upcoming AppDesigner will include more advanced controls than GUIDE, but there will always be a place for custom-made GUIs for professional appearance and functionality.
In the 2 code rows you provided, the first command created the Java component
jPanelObj
, and the second command placed this component in the GUI figure using the javacomponent function.If you wish to learn more about creating advanced Matlab GUIs and/or working with Java in Matlab, get my Matlab-Java book. If you have a commercial need, consider hiring me for a consulting project.
Hi Yair,
I’m looking for an edit box which gives suggestions of what you might want to type (It’s a form that people need to fill out), but is not limited to the predefined options. I was thinking the auto-completion function might be an option (or are there other options for this?).
I tried to go through your code but it seems I missed something. The variables hPanel and jCombo could not be found. Is there some code I missed?
@Efraim – I’m not sure what you want. I showed exactly how to do this in the main article above. If you did not understand my code then try to read it carefully and test the code in your Matlab, step-by-step. Good luck!
Hi Yair,
Sorry for the unclarity. I’m making a form with edit boxes which people need to fill out.
I would like to show underneath the edit box some suggestions of commonly used input, based on what somebody is typing. However, like you suggested, I copied your code in my Matlab and tried to run it. I got two errors: The variables hPanel and jCombo could not be found. I went through all your code on this page but these two variables are not created. Do I need some code from another page maybe(e.g. where you explain the Editable combo-box)?
@Efraim – hPanel is (of course) the panel handle that should contain the search-text field component; jCombo is the left-most combo-box that contains the asset names. If you need any assistance with getting it all to work in your specific project, consider hiring me for a short consultancy.
Hi Yair,
I’m trying to implement your Autocomplete widget in my GUI (more simple than yours).
I’ve decided to split the code to better understand it, but i have two problems:
1) I can’t select the word from the combobox dropdown panel or by typing in the search text-box
How can i do that? How can i get the selected word to use it?
I’ve tried with jAssetChooser.getSearchText or jCombo.getSelectedItem but it didn’t work.
Must i use the setappdata comand?
2) If the word doesn’t match with my the list i get this Java error:
I’m using matlab 2016a.
This is my example code:
[*** very large code section removed ***]
Thanks in advance.
Stefano
@Stefano – answering this is beyond the time that I can spend on a blog comment. You’re invited to email me (altmany at gmail) for a short consultancy on your specific program.
Hello Yair,
Im having a problem with getting a keypress callback on R2015b for the simpe autocompletewidget:
I would like to have this callback to register a ‘enter’ keystroke signifying a completed selection.
Any help would be greatly appreciated!
Hello together,
I am iteressted in the same point. I can implemement
and this works fine, but with this callback I can not trigger on return or enter.
Do anybody have an idea to this topic.
Best Regards Stev
Yair,
I am trying to implement something very similar in a GUI I am making but after creating the Combo Box and running your above code, I am getting an error at the line:
because there is no variable “
handles.
” What does this variable need to be?@Eric – read my answer above
This looks great but as others have pointed out, the example is incomplete. Could you provide an actual working code as the code snippets you provide does not work by them self?
Thanks.
@Peter – My aim in this post, as in my entire blog, is to provide general guidelines, code snippets and sample output. I expect my readers to fill in the missing blanks. Admittedly, not all readers can do this, but I aim high, unapologetically. I rarely spoon-feed users with complete code programs. This would be quite beyond the scope of a typical blog post, or the amount of time that I can spend pro-bono. Remember that this blog has 400 articles, so if I had to spend a full day on each one, this would translate to almost two full work-years!
I have now added some extra code and explanations to the main text, which should be more than enough for most Matlab developers. If it’s still not enough for you, then consider asking a professional Matlab developer to assist you.
Hi!
This is quite elegant. Unfortunately, there is a problem with Matlab hanging (matlab 2018b, windows 8.1) in the searchComboUpdated(obj, jCombo, eventData, hPanel) function. The hang occurs at jSearchTextField.setText(selectedItem). From other articles on your blog I learned that it could be EDT related and I hence added the drawnow; pause(0.1); that sometimes solves this. In this case it doesn’t help. In a newly started Matlab I get a hang everytime. It doesn’t hang in debug mode, but as soon as I run it without a breakpoint, it hangs. So frustrating.. and so unfortunate that Matlab has all these issues. I tried longer pause-times as well..didn’t help. Do you have any ideas?
Best,
Peter
PS: the code:
It seems i solved the issue i reported a couple of hours ago. I use javaObjectEDT (thanks for explaining that in your other post) on jSearchTextField before the setText invocation..