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):
How can I update the string inside the field wihtout the caret becoming stuck?
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:
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: