Search code examples
matlabmatlab-gui

Caret stuck when setting String property of "edit" field


I am building a GUI in Matlab, using the programmatic approach (so wihtout GUIDE and without AppDesigner). My GUI includes an edit field that should only accept certain inputs. Therefore, I am using a callback that sanitizes the input and then updates the String property of the edit field accordingly. However, when I update the String property, the caret (the "cursor) gets stuck: It stops blinking, and although you can still move it left and right, a ghost copy of it will stay painted at the left edge of the input.

Minimum working example, using random numbers:

figure;
edit_cb = @(h, e) set(h, 'String', num2str(rand(1)));
uicontrol('Style', 'edit', 'String', '0', 'Callback', edit_cb);

Result (on Win7, using Matlab2016a or Matlab2014b):

enter image description here

How can I update the string inside the field wihtout the caret becoming stuck?


Solution

  • I found a workaround: In the callback, you can first switch the focus to a dummy-element, then update the element-of-interest, and finally switch the focus back to the element-of-interest. Drawback of this solution: The text is highlighted in its entirety. Also, the solution is somewhat fragile: For non-obvious reasons, the visibility of the dummy element must be set to 'off' in a separate set call.

    Since the new callback spans several lines, it can no longer be stated as an anonymous function. This makes the minimal example slightly longer:

    function caret_stuck_hack()
    
    figure
    hedit = uicontrol('Style', 'edit', 'String', '0', 'Callback', @edit_cb);
    hdummy = uicontrol('Style', 'edit', 'String', 'dummy', ...
        'Position', [0, 0, 1, 1]); % Position: HAS to be non-overlapping with other edit field
    hdummy.Visible = 'off'; % Don't merge with upper line! HAS to be called seperately! 
    
    function edit_cb(h, e)
        uicontrol(hdummy);
        h.String = num2str(rand(1));
        uicontrol(h);
        drawnow;
    end
    
    end
    

    Result:

    enter image description here

    Addendum

    You can change the location of the caret by manipulating the underlying Java Swing Object. Using Yair Altman's excellent findjobj function, the code becomes:

    function caret_stuck_hack()
    
    figure
    hedit = uicontrol('Style', 'edit', 'String', '0', 'Callback', @edit_cb);
    hdummy = uicontrol('Style', 'edit', 'String', 'dummy', ...
        'Position', [0, 0, 1, 1]); % Position: HAS to be non-overlapping with other edit field
    hdummy.Visible = 'off'; % Don't merge with upper line! HAS to be called seperately! 
    
    jhedit = findjobj(hedit, 'nomenu');
    
    function edit_cb(h, e)
        caret_pos = get(jhedit, 'CaretPosition');
        uicontrol(hdummy);
        h.String = num2str(rand(1));
        uicontrol(h);
        drawnow;
        set(jhedit, 'CaretPosition', caret_pos)
    end
    
    end
    

    You could (and maybe should) add additional code to check that the caret index is not illegal when the length of the string changes. But for this minimal example, the result already looks pretty nice:

    enter image description here