Auto-completion widget

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:

Interactive Matlab auto-completion widget

Interactive Matlab auto-completion widget

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);

AutoCompletionList widget

AutoCompletionList widget

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);
SearchTextField initial view

SearchTextField initial view

user clicks in entry box (prompt text disappears)

user clicks in entry box (prompt text disappears)

user types something (icon changes, clicking it will clear the text)

user types something
(icon changes, clicking it will clear the text)

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 disparate parts of two separate 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?

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 to 3 separate events: MouseClickedCallback on the 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
hjSearchButton = handle(jAssetComponent.getComponent(1), 'CallbackProperties');
set(hjSearchButton, 'MouseClickedCallback', {@updateSearch,jCombo,jAssetChooser});
 
hjSearchField = handle(jAssetComponent.getComponent(0), 'CallbackProperties');
set(hjSearchField, 'KeyPressedCallback', {@updateSearch,jCombo,jAssetChooser});
 
jComboField = handle(jCombo.getComponent(2), 'CallbackProperties');
set(jComboField, 'KeyPressedCallback', {@updateSearch,jCombo,[]});

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', '$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 the final result:

Matlab GUI with integrated auto-completion & date selection widgets

Matlab GUI with integrated auto-completion & date selection widgets


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!

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

Tags: , , , ,

Bookmark and SharePrint Print

13 Responses to Auto-completion widget

  1. Robert Cumming says:

    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

  2. lou says:

    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.

  3. Ulrik says:

    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.

    • Malcolm Lidierth says:

      @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

  4. ikc says:

    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.

    assetClassIdx = getappdata(handles.cbAssetClass, 'assetClassIdx');

    What is “handles” here for? jCombo? jAssetChooser? Or something else?

    • @ikc – here’s a pared-down code-snippet that explains how cbAssetClass was created:

      function createMainGUI()
         ...
         handles.cbAssetClass = createComboSelector(hPanel, ['All'; assetClassNames], @assetClassUpdatedCallback, false);
         ...
      end
       
      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
         ...  % fixing the gray bgcolor of non-editable combos
         [jhComboBox, hContainer] = javacomponent(jComboBox, [], hParent);
         set(jhComboBox, 'ActionPerformedCallback', callback);
         hContainer = handle(hContainer);
         set(hContainer, 'tag','hAssetContainer', 'UserData',jComboBox);
      end
    • ikc says:

      @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.

      free = java.lang.Runtime.getRuntime.freeMemory
      total = java.lang.Runtime.getRuntime.totalMemory
      max = java.lang.Runtime.getRuntime.maxMemory

      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.

  5. Rami says:

    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.

    jPanelObj = com.mathworks.widgets.AutoCompletionList(strList,'');
    javacomponent(jPanelObj.getComponent, [10,10,200,100], gcf);

    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.

  6. Efraïm Salari says:

    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!

    • Efraïm Salari says:

      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.

Leave a Reply


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