MATLAB GUI drawnow renderes button 'pushed down' instead of 'disabled' - matlab

I am running the drawnow statement from a callback function within a MATLAB GUI to update the state of a button. At the beginning of the callback (which has high runtime) I alter the properties of the button and force an update with drawnow. While updateing properly, the button remains rendered 'pushed down' instead of 'disabled'. After the callback is finished, the button is updated again and now rendered 'disabled'.
Take following minmal (not) working example:
function simple_example()
h = figure();
% add a button, give it some properties and a callback
uicontrol(h,...
'Style','pushbutton',...
'String','I am enabled',...
'Units','normalized',...
'Position',[0.5 0.5 0.4 0.4],...
'Callback',#btn_callback);
end
function btn_callback(hObject, ~)
set(hObject,'Enable','off');
set(hObject,'String','I am disabled');
drawnow;
pause(3);
end
Is there a way to change this behavior and have the button appear disabled while the callback is still executing?

As you are asking about appearance here's a workaround using uibuttongroup:
function simple_example()
h = figure();
b = uibuttongroup('Position',[0.5 0.5 0.4 0.4]);
bgcolor = b.BackgroundColor;
% add a button, give it some properties and a callback
uicontrol(b,...
'Style','pushbutton',...
'String','I am enabled',...
'Units','normalized',...
'Position',[-0.05 -0.05 1.1 1.1],...
'Callback',#btn_callback);
end
function btn_callback(hObject, ~)
set(hObject,'Enable','off');
set(hObject,'String','I am disabled');
drawnow;
pause(3);
end
Here, you fit the button within a uibuttongroup, which normally groups several uibuttons and then set the button size bigger than the actual uibuttongroup, so the borders don't appear.
However, this let's you lose the button down rendering. You could get that back by altering the uicontrolgroup's border properties.
Update:
This seems to be OS-specific. On OS X your code works just fine, as far as I can see. Windows, I don't know, but according to your comment neither my version, nor yours seems to fix the issue. On Ubuntu, on the other hand, my answer solves the problem.

Related

datacursormode: spurious MouseMotion event processing when UpdateFcn takes time to return

I'm using DATACURSORMODE in Matlab 8.1.0.604 (R2013a) on Mac OS 10.11.6 (El Capitan).
In the simple example below, the data cursor only moves when a new location is clicked, not when the mouse is merely moved. This is exactly the behaviour I want. However, in my code I get a different behaviour: after the first click, the cursor moves whenever the mouse just moves, until I subsequently double-click. I did not knowingly ask for this latter behaviour and do not want it.
The key difference seems to be that my UpdateFcn callback code takes time to complete (and let's assume it always will: my callback is intended to perform fairly complex actions, and is already as vectorized and optimized as it can get). This is emulated by the pause statement in the example below, which should hopefully replicate the problem if uncommented (might have to fiddle with the duration, depending on platform/setup).
In datacursormode.m the "UNDOCUMENTED FUNCTIONALITY" comments mention that both MouseMotion and ButtonDown events are thrown by a data cursor mode object. This is clearly not the full story, because by default there's no response to mouse motion alone, but something happens, presumably due to the delay, to make it do so.
So my question is: is this cursor-moving-on-mouse-motion-until-you-doubleclick a known feature/mode, or just unintended "undefined behaviour" as a side effect of the delay? In either case, how can I (programmatically) prevent it from happening, assuming I can't actually speed up the callback code?
function demo
fig = 1;
figure(fig), close, figure(fig) % ensure virgin figure
image
h = datacursormode(fig);
datacursormode(fig, 'on')
set(h, 'UpdateFcn', #callback)
function txt = callback(cbo, event)
% pause(0.1) % uncomment this line (and/or lengthen the delay as necessary) to replicate the problem
txt = evalc('cbo,event,disp(get(event))');
I couldn't actually reproduce your problem on R2012a or R2016a on Windows, but it sounds like on your system MATLAB is failing to catch the ButtonUp event when you click. The reason MouseMotion events are processed is because you should be able to drag the cursor when you have the mouse button held down.
Assuming that it is indeed the slow response of the UpdateFcn that causes this, you may be able to resolve this by triggering the slow part in an asynchronous operation which then triggers another update when it is done. The most universal form of this, I think, is to use a timer with a very short StartDelay. In the example below I've used appdata to share handles between the datacursormode object and the timer, but you could approach this in many different ways in your specific implementation.
function demo
fig = 1;
figure(fig), close, figure(fig) % ensure virgin figure
image
h = datacursormode(fig);
datacursormode(fig, 'on')
set(h, 'UpdateFcn', #cursormodecallback)
setappdata(fig, 'cursormode',h);
setappdata(fig, 'timer',timer('StartDelay',0.001, 'TimerFcn',#timercallback));
setappdata(fig, 'lasttxt','Initial text');
function txt = cursormodecallback(cbo, event)
txt = getappdata(1,'lasttxt'); % Display the most recently generated text in the tooltip
t = getappdata(1,'timer');
if strcmp(get(t,'Running'),'off') % If we have already asked for an updated tooltip and haven't got one yet then don't ask for another one
set(t, 'UserData',{cbo,event}); % Store the data needed by the slow callback to generate the tooltip
start(t); % Ask the timer to generate new text
end
function timercallback(cbo, event)
cursordata = get(cbo,'UserData');
[cbo,event] = cursordata{:};
pause(1)
txt = evalc('cbo,event,disp(get(event))');
if ~isequal(txt,getappdata(1,'lasttxt'))
setappdata(1,'lasttxt',txt); % Store the latest text
updateDataCursors(getappdata(1,'cursormode')); % Update the cursor so the text is displayed
end
Here's an edited version of Will's solution that works nicely. The StartDelay value is critical: <=6ms fails to solve the mouse motion problem. 7ms solves it most of the time, but occasionally lapses. 10ms seems to work fairly reliably (except the very first time in a given new Matlab session, when things are slow to wake up). Typical Matlab implementation flakiness...
function demo
fig = 100;
figure(fig), close, figure(fig) % ensure virgin figure
img = image;
EnableDataCursor(img)
function EnableDataCursor(img)
ax = get(img, 'parent');
fig = get(ax, 'parent');
dcm = datacursormode(fig);
datacursormode(fig, 'on')
set(dcm, 'UpdateFcn', #CursorModeCallback)
setappdata(img, 'CursorModeObject', dcm);
setappdata(img, 'Timer', timer('StartDelay', 0.010, 'TimerFcn', #TimerCallback));
setappdata(img, 'LastText', 'Initial text');
function txt = CursorModeCallback(cbo, event)
img = get(event, 'Target');
t = getappdata(img, 'Timer');
if strcmp(get(t, 'Running'), 'off') % If we have already asked for an updated tooltip and haven't got one yet then don't ask for another one
txt = 'updating...';
set(t, 'UserData', {cbo, event}); % Store the data needed by the slow callback to generate the tooltip
start(t); % Ask the timer to generate new text
else
txt = getappdata(img, 'LastText'); % Display the most recently generated text in the tooltip
end
function TimerCallback(t, varargin)
ud = get(t, 'UserData');
[cbo, event] = deal(ud{:});
img = get(event, 'Target');
pause(1) % numbercrunch, numbercrunch, numbercrunch
txt = num2str(get(event, 'Position'));
if ~isequal(txt, getappdata(img, 'LastText'))
setappdata(img, 'LastText', txt); % Store the latest text
updateDataCursors(getappdata(img, 'CursorModeObject')); % Update the cursor so the text is displayed
end

Simply entering the debugger during execution of MATLAB GUI fixes error that persists during normal execution

I'm using a programmatic GUI in MATLAB which uses multiple figure windows. When I press the button 'Redraw' in Figure A, a new figure appears (Figure B) with some data plotted. I want the focus to immediately switch back to Figure A because there are many hotkeys (WindowKeyPressFcn) that I use in that window to update the plots in Figure B.
There are two problems here:
1) The last line of the callback for the button 'Redraw' does switch focus back to Figure A, BUT only if Figure B exists already. That is, the first time Figure B is created, it remains in focus. If I then use Figure A to update the plots in Figure B, the focus correctly switches back to Figure A. I can't think of why it behaves differently during the first redraw and all subsequent calls.
2) The even bigger issue is that if I set a breakpoint anywhere in the code and then resume execution, the focus switches back to Figure A as I want. So, why does entering the debugger and doing nothing else fix the problem? How can I find the issue if everything works in the debugger?
Thanks in advance!
EDIT: To my great surprise, I was able to reproduce this "Heisenbug" by writing my first ever programmatic GUI. This should be the simplest example of my problem. To see it in action, simply run the code below and click on the push button. For some reason, when Window 2 is created for the first time, the focus does NOT switch back to Window 1 as intended. It works properly for all subsequent button presses. Try closing Window 2 and pushing the button again, the error will keep occurring.
As mentioned in the original post, setting a breakpoint in the code resolves the issue. Set a breakpoint at line 27, then resume execution and Window 1 will be in focus.
What is happening here?
function heisenbug
%% Main problem:
% After clicking the push button, I want the focus to
% always switch back to Window 1 (the one containing the button).
% However, this does not work when Window 2 is first created.
%%
%% Create and then hide the GUI as it is being constructed
f = figure('Visible','off','name','Window 1','units','normalized','Position',[0.1 0.1 0.5 0.5]);
%% Initialize handles structure
handles = guihandles(f);
handles.window2 = [];
guidata(f,handles)
%% Make a button
hbutton = uicontrol('Style','pushbutton','String','Push me','units','normalized',...
'Position',[0.1 0.1 0.8 0.8],...
'Callback',#button_Callback);
%% Make the GUI visible
f.Visible = 'on';
%% Button callback
function button_Callback(source,eventData)
handles = guidata(gcbo);
% If Window 2 already exists, plot a circle, then switch focus back to Window 1.
if ~isempty(handles.window2) && ishandle(handles.window2)
figure(handles.window2);
plot(1,1,'bo')
figure(f);
% Otherwise, create Window 2 and do the same thing.
else
handles.window2 = figure('Name','Window 2','units','normalized','position',[0.4 0.1 0.5 0.5]);
plot(1,1,'bo')
figure(f)
end
guidata(source,handles)
end
end
My post was quickly answered by Adam (thanks!) on the MathWorks site: http://se.mathworks.com/matlabcentral/answers/299607-simply-entering-the-debugger-during-execution-of-matlab-gui-fixes-error-that-persists-during-normal.
I needed to insert a pause(0.05) after Window 2 is created, before trying to switch the focus back with figure(f). Otherwise the focus is stolen back to Window 2 when it finishes plotting.

How to make a simple borderless button in MATLAB GUI

Quite simply, I'm trying to create a borderless button in a MATLAB GUI. The reasons are mostly aesthetics, so no need for a debate on why it should be borderless.
I already know that this cannot be done using the built-in MATLAB uicontrol alone, since the border of the button is not an accessible property in MATLAB. Thus, the underlying JAVA code (upon which MATLAB is written), must be accessed in order to manipulate the border. This is where I get lost, since I've only ever programmed in MATLAB.
I followed an example from here:
http://undocumentedmatlab.com/blog/borderless-button-used-for-plot-properties
But I'm still not getting a borderless button.
Here is a simple code example (NOTE the use of Yair Altman's findjobj which is available on the MATLAB file exchange):
f=figure('Menubar','none', 'Position',[200 200 300 200]);
p=uipanel(f, 'BackgroundColor', [0 0 1]);
h = uicontrol('parent', p, ...
'Style','pushbutton', ...
'String','click', ...
'TooltipString', 'you should click this' ...
'Units','normalized', ...
'Position',[0.3 0.3 0.5 0.5], ...
'BackgroundColor', [0 0 1]);
jh = findjobj(h);
jh.setBorder(javax.swing.BorderFactory.createEmptyBorder());
%attempt 1 does not remove border
jh.border=[];
%attempt 2 does not remove border
jh.setBorder([]);
%attempt 3 does not remove border
jh.border=javax.swing.BorderFactory.createEmptyBorder();
%attempt 4 does not remove border
Any thoughts on where I've gone wrong? Thanks!
You shoud add two lines:
jh.setBorderPainted(false);
jh.setContentAreaFilled(false);
It is not clear to me what you mean with "borderless".
Looking at the example on the Web page you posted, I assume you are looking for something like an "invisible" pushbutton.
If so, you might consider this alternative way:
insted of having a pushbutton you might have a static text uicontrol
make its backgroundcolor the same of the GUI backgroundcolor (it will become "invisible" and without any border)
do not set any string in the static text uicontrol
set the enable property of the static text uicontrol to off
define, for the static text uicontrol the ButtonDownFcn
write the code you want to execute by pushing the pushbutton in the ButtonDownFcn
When you press the mouse button on the "invisible" static text uicontrol, its ButtonDownFcn will be executed.
You just have to remember ... were the "invisible" static text uicontrol is.
Hope this helps.
The border is affected by the fly-over appearance feature. http://undocumentedmatlab.com/blog/undocumented-button-highlighting
You need to add
jh.setFlyOverAppearance(true);
Worked for me.

Trigger CellEditCallback before Button Callback

I have a GUI with a uitable that the user can edit values in. I have a CellEditCallback function for that table that triggers and checks for input errors in the cells whenever a user presses enter while editing a cell or clicks outside the cell. That works great, but I also have a pushbutton that uses data from that table and my current problem is that when the pushbutton is clicked before any other spot outside the cell is clicked, or before enter is pressed for that matter, the pushbutton callback runs first, and after that callback finishes then the CellEditCallback runs. This is not ideal, as I need to check for errors before I use the data in my calculations. So, does anybody have any ideas on how to have the CellEditCallback function run first?
This code produces the problem I'm having:
% If you edit a cell and immediately click the button before clicking
% outside the cell or before hitting enter, the button's callback triggers
% before the CellEditCallback
function temp
% Create Figure
mainFig = figure('Units','characters',...
'Position',[45 5 200 50],...
'Renderer','opengl');
% Create uitable
tempData(1:10,1:5) = {''};
mainTable = uitable('parent',mainFig,...
'Units','characters',...
'Position',[5 25 180 20],...
'ColumnEditable',[true],...
'ColumnFormat',{'char'},...
'ColumnWidth',{150 150 150 150 150},...
'Data',tempData,...
'CellEditCallback',#enterDataCallback);
% Create Button
mainButton = uicontrol('Parent',mainFig,...
'Units','characters',...
'Position',[5 10 180 10],...
'Style','pushbutton',...
'String','Button',...
'Callback',#buttonCallback);
% Function for when cell data is edited
function enterDataCallback(src,evt)
disp('Cell Edited')
end
% Function for when a button is pressed
function buttonCallback(src,evt)
disp('Button Pressed')
end
end
Note 1: I did try using uiwait and waitfor but the problem isn't that the CellEditCallback function gets interrupted, it just is triggered after the pushbutton callback.
Note 2: That was a very basic description of what the functions do, but I do need the callbacks to trigger in that order because other things like flags and important variables in an outer function are set in the CellEditCallback so I need to have that callback run before the pushbutton one.
Thanks!
I contacted MATLAB Support about this problem and they told me that the callbacks occurring in that order is indeed an error and that it is fixed in the 2014b prerelease. However, to work around the error, I managed to do some messy coding to call the CellEditCallback from inside the Push Button Callback and then set a flag to make sure the CellEditCallback doesn't fire after the Push Button Callback is done.

Pressing a button disables keyboard input

I have a strange problem in my Matlab GUI. The GUI contains uipanel and icontrol objects, some of which are buttons. Usually, the GUI is controlled with the directional arrow keys.
However, once I click one of my buttons, the keyboard events are not recorded any more. I've set breakpoints in the keypress callback to find out what's happening and it turns out the callback is never called. If I manage to click the GUI background, it works once again, which makes me think it's related to the active control. But how can I give control back to the main window? uicontrol(hFigure) doesn't work, neither does figure(hFigure).
The following code snippet reproduces the problem. Copy it into a new file (ideally called test.m, otherwise Code Analyzer will complain) and run it to open a GUI window that shows this behaviour. Once the button is clicked, the arrow keys aren't recorded any more unless the user clicks the area outside the text uicontrol.
function test
figure('KeyPressFcn',#key)
clf
p = uipanel('position',[0 0 1 1],'BackgroundColor',[.7 .7 .7]);
uicontrol('Style','push','String','Click me','Units','norm',...
'Position',[0.43 0.91 0.14 0.06],'Callback',#button);
t = uicontrol(p,'Style','text','String','Use arrow keys','Units','norm',...
'Position',[0.2 0.4 0.6 0.2],'FontSize',20);
function button(~,~)
set(t,'String','Button pressed.');
end
function key(~,e)
set(t,'String',['Key ' e.Key ' pressed.']);
end
end
You are right about why this doesn't work. When you click on the button, the figure is no longer the active control. The best way to fix this is to additionally set the KeyPressFcn property of the button to be the same as the KeyPressFcn of the figure.
function test
figure('KeyPressFcn',#key)
clf
p = uipanel('position',[0 0 1 1],'BackgroundColor',[.7 .7 .7]);
uicontrol('Style','push','String','Click me','Units','norm',...
'Position',[0.43 0.91 0.14 0.06],'Callback',#button, ...
'KeyPressFcn', #key);
t = uicontrol(p,'Style','text','String','Use arrow keys','Units','norm',...
'Position',[0.2 0.4 0.6 0.2],'FontSize',20);
function button(~,~)
set(t,'String','Button pressed.');
end
function key(~,e)
set(t,'String',['Key ' e.Key ' pressed.']);
end
end
You could also set the WindowKeyPressFcn instead of KeyPressFcn.
For more information see my answer here:
matlab: difference between KeyPressFcn and WindowKeyPressFcn