Confusing interaction between "Callback" and "KeyPressFcn" for "edit"-style UIControl
2 visualizaciones (últimos 30 días)
Mostrar comentarios más antiguos
Aaron
el 16 de Sept. de 2024
Hello --
I'm having difficulty understanding an interaction between a "Callback" and "KeyPressFcn" in my figure- and uicontrol-based tool.
I have an "edit"-style uicontrol with both a "KeyPressFcn" and "Callback" defined. The "Callback" initiates an input validation sequence and is working as intended. I want the "KeyPressFcn" to perform a pseudo-autocomplete as defined here. Here's a minimal version of what I have:
% Initial setup ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
% Set allowed strings
str = ["FOO", "BAR", "BAZ", "QUX", "FOOBAR", "BAZQUX", "FARBOO"];
% Set up figure
f = figure;
e = uicontrol('Style', 'edit', 'String', str(3), 'Callback', @cb, ...
'KeyPressFcn', @kp, 'Interruptible', 'off', 'BusyAction', 'queue');
% Set up guidata
data.AllowedStrings = str; % Store allowed strings
data.EditBoxString = e.String; % Store a copy of the current string
data.AutoComplete.String = []; % Autocomplete: initial string
data.AutoComplete.Counter = 1; % Autocomplete: which completion are we on?
data.AutoComplete.Options = []; % Autocomplete: List of possible completions
guidata(f,data)
% Functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
% Callback: Data Validation
function cb(src,~)
% Get guidata
data = guidata(src);
% Check supplied string against valid strings
val = erase(lower(src.String), [" ", ":", "-", "(", ")"]) == ...
lower(data.AllowedStrings);
% If invalid, reset the string to its previous value and return
if ~nnz(val)
src.String = data.EditBoxString;
return
end
% Set editbox string to validated string and repack data
src.String = data.AllowedStrings(val);
data.EditBoxString = data.AllowedStrings(val);
guidata(src,data)
end
% KeyPressFcn: "Autocomplete" on leftbracket push
function kp(src,k)
% Get guidata
data = guidata(src);
% Autocomplete if "leftbracket" was pressed
if k.Key == "leftbracket"
% Get autocomplete string if none exists
if isempty(data.AutoComplete.String)
data.AutoComplete.String = erase(lower(src.String), ...
["["," ", ":", "-", "(", ")"]);
end
% Get possible completions using "startsWith"
data.AutoComplete.Options = find(startsWith(...
lower(data.AllowedStrings), data.AutoComplete.String));
% Fill text with next possibility
if ~isempty(data.AutoComplete.Options)
% Set string
src.String = data.AllowedStrings(...
data.AutoComplete.Options(data.AutoComplete.Counter));
% Iterate counter
data.AutoComplete.Counter = mod(data.AutoComplete.Counter, ...
length(data.AutoComplete.Options)) + 1;
end
else
% Set autocomplete string to empty & reset counter
data.AutoComplete.String = [];
data.AutoComplete.Counter = 1;
data.AutoComplete.Options = [];
end
% Repack guidata
guidata(src,data)
end
This does not seem to work. However, if I place a breakpoint on lines 25 (data = guidata(src) in the cb function) and 54 (if isempty(data.AutoComplete.String) in the kp function) and use F5 / F10 to sequentially complete each function, it works exactly as intended! I'm able to supply an input, say "b", then repeatedly press "[" to cycle through the relevant options: "BAR", "BAZ", and "BAZQUX". I just need to roll forward through all the breakpoints before I press "[" again.
This "almost-right" behavior is dependent on the "Interruptible" property being set to "off", so I imagine it's due to some subtlety in when the "Callback" is executed; it's just baffling to me that everything is (or appears to be) working in the right order when I include breakpoints but not without them. I think I need my "Callback" never to execute while I'm cycling through my autocomplete options.
Any suggestions? I'd prefer not to migrate the edit box into the more modern UI elements if possible.
Thanks!
4 comentarios
Stephen23
el 17 de Sept. de 2024
Editada: Stephen23
el 18 de Sept. de 2024
"...particularly the "normalized" units"
I know exactly where you are coming from, I had exactly the same fight with the function IREGEXP: specifying UI element sizes was a complete pain as everything is in terms of pixels. Resizing all of the UI elements was an awkward, complex, fiddly task...
Until I discovered UIGRIDLAYOUT.
Only then did UIFIGURE resizing make sense.
Forget about normalized units. Forget about setting the UI element sizes yourself. With UIFIGUREs trying to set the UI element sizes yourself only causes code bloat and loss of hair. In contrast, with UIGRIDLAYOUT it is simple and quite intuitive: it lets you specify absolute sizes, normalized sizes, and lots more. I recommend giving it a try.
When I rewrote IREGEXP (motivated by the irreplaceable VALUECHANGINGFCN) I made a false start by trying to specify the element sizes myself (i.e. as a minimal rewrite of the original FIGURE-based GUI). Bad idea. Finding UIGRIDLAYOUT meant that I very quickly rewrote the relevant code and bingo! Easier and better.
"...a lack of visual customization options"
I have not tried such color properties, so cannot comment.
Más respuestas (1)
Walter Roberson
el 17 de Sept. de 2024
Editada: Walter Roberson
el 17 de Sept. de 2024
Changing the String property of a uicontrol is not reflected on the display until the user presses Enter or moves the focus away from the control. The String property changes, but the display is not updated. Therefore, you will not be able to do pseudo autocomplete without the user continually pressing enter or clicking elsewhere.
3 comentarios
Walter Roberson
el 17 de Sept. de 2024
If you unfocus and refocus then you lose the internal positioning of the cursor for the cases where the user edits in the middle of a string.
Ver también
Categorías
Más información sobre Migrate GUIDE Apps en Help Center y File Exchange.
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!