matlabfocusmatlab-app-designerkeyboard-inputmatlab-gui

How to set the focus on a specific element in a uifigure?


I have an application where an SVG image is presented to the user, and they need to fill in two edit fields based on what is presented. Since the process needs to be repeated many times, I concluded it would be best for speed and efficiency if user interactions required the keyboard alone. Towards that end, I must ensure several things:

  1. That the figure is in focus.
  2. That Tab ⭾ cycles the elements in the correct order (edit1 → edit2 → button).
  3. That the correct edit field is focused whenever the image is refreshed.

The 1st requirement can be fulfilled by toggling figure visibility, as explained here.
The 2nd requirement is also fairly simple to fulfill, and merely requires that graphical elements are defined in a specific order, as discussed here (for uifigures) and here (for figures).

My difficulty is with the 3rd requirement, and specifically - I have no idea how to ensure the desired edit field is focused when needed. Please consider the following class for reference, in which the focusControl method is just a placeholder.

classdef SVGAxisLimit < handle
  
  properties (GetAccess = private, SetAccess = immutable)
    hF (1,1)
    hI (1,1) matlab.ui.control.Image
    hLL (1,1) matlab.ui.control.NumericEditField
    hRL (1,1) matlab.ui.control.NumericEditField
    hDone (1,1) matlab.ui.control.Button
  end
  
  methods    
    function obj = SVGAxisLimit()
      % Create figure:
      hF = uifigure('WindowState','maximized','Color','w'); drawnow;
      
      % Create image:      
      hI = uiimage(hF, 'Position', [1,100,hF.Position(3),hF.Position(4)-100]);
      
      % Create controls:
      uilabel(hF, 'HorizontalAlignment', 'left', 'Position', [600 20 150 42],...
        'Text', 'Left Limit:', 'FontSize', 22);
     
      % Create LeftLimitEditField
      hLL = uieditfield(hF, 'numeric', 'Position', [710 20 80 42], 'FontSize', 22);

      % Create RightLimitEditFieldLabel
      uilabel(hF, 'HorizontalAlignment', 'left', 'Position', [900 20 150 42],...
        'Text', 'Right Limit:', 'FontSize', 22);

      % Create RightLimitEditField
      hRL = uieditfield(hF, 'numeric', 'Position', [1025 20 80 42], 'FontSize', 22);

      % Create DoneButton
      hDone = uibutton(hF, 'push', 'Text', 'Done', 'Position', [1200 20 80 42], ...
        'FontWeight', 'bold', 'FontSize', 22, 'ButtonPushedFcn', @(varargin)uiresume(hF));
      
      % Store handles:
      obj.hF = hF;
      obj.hI = hI;
      obj.hLL = hLL;
      obj.hRL = hRL;
      obj.hDone = hDone;
    end    
  end
  
  methods (Access = public)
    function [realLims] = showSVG(salObj, svgPath)
      salObj.hI.ImageSource = svgPath;
      % Focus left edit field
      SVGAxisLimit.focusControl(salObj.hLL);
      % Wait for a click on "done"
      uiwait(salObj.hF);
      % When resume, capture values:
      realLims = [salObj.hLL.Value, salObj.hRL.Value];
    end
  end  
  
  methods (Access = private, Static = true)
    function [] = focusControl(hObject)
      % hObject is the handle of the uicontrol which needs to be focused
      % ???
    end
  end
end

I am using MATLAB R2020a.

P.S.

I have decided to use uifigures for this, because their uiimage component natively supports the presentation of SVGs (although workarounds avoiding this component exist).


Solution

  • Seeing how a UIFigure is mostly a webpage, it turns out that the JavaScript Web API methods of .focus() and .select() can be useful here. The only difficulty that remains is finding some way to refer to the web element (widget) in question. Fortunately, the HTML elements corresponding to edit fields in R2020a are identified by a unique id attribute, which makes it easy to refer to them using very simple DOM commands such as getElementById. In summary:

    function [] = focusControl(hObject)
      % hObject is the handle of the uicontrol which needs to be focused
      [hWin, widgetID] = mlapptools.getWebElements(hObject);
      hWin.executeJS(sprintf(...
        'W = document.getElementById("%s"); W.focus(); W.select();', widgetID.ID_val));
    end
    

    Where mlapptools can be found here (disclosure: I am a co-author of this utility).