I have Matlab application where the user needs to click on a point in a graph, and then confirm the click in a message box. I'm using getCursorInfo to get the current clicked location. If the user doesn't confirm the point selection, another point should be selected.
The problem is that sometimes after the user choose 'No' in the message box, getCursorInfo returns a new position without requiring the user to click on the mouse. It is as if getCursorInfo thinks the mouse button is continuously down.
Here is a minimal sample of code that demonstrates this - after the 3rd or 4th time the message box is shown, Matlab thinks the mouse button is down and changes the cursor position without any clicks.
fig = figure;
data = peaks();
plot(data);
old_pos = [-2 -2]
cur_pos = old_pos
while 1
datacursormode on
dcm = datacursormode(fig);
while all(cur_pos==old_pos)
pause(0.25);
ci = getCursorInfo(dcm);
if ~isempty(ci)
cur_pos = ci.Position;
end
end
display(cur_pos);
old_pos = cur_pos
datacursormode off
questdlg('Question?');
end
Without the dialog this code allows the user to select as many points as needed without getting confused (simply comment the questdlg call to see).
Turns out the solution was here, I needed to delete the data cursors with the following line:
delete(findall(fig,'Type','hggroup'));
Related
I created a uitable (new version using appdesigner) in MATLAB and wanted to support right clicking on cells and showing a cell specific context menu. Much to my surprise there seemed to be no way to support this.
The context menu only seems to trigger with right click on the uitable, but there is no way of knowing which cell was selected (I think, maybe not?). I created a workaround where I left clicked to select a cell, and during that selection I right clicked using a Java Mouse robot to trigger the context menu. This is super ugly but sort of works. Except, if you need to bring up the menu twice on the same cell. Apparently the cell selected callback only fires once for the cell, until a new cell is selected. I tried literally putting two tables in the same spot and upon selecting one toggling to the other, but the memory of cell selection is table specific, so this only worked for two clicks before both tables had been clicked on the same cell, and toggling visibility back to the first resulted in the cell selection callback not firing (since the cell had not changed) . I tried various approaches to try and deselect the cell (disable/enable, visibility change, data change, etc.), but the cell selection callback never changed.
I even tried having duplicate columns, where the goal was to hide a column, where normally columns 1 and 2 would be visible (column 3 out of view due to size), and then on clicking on column 2, column 2 would hide itself (0 width) and column 3 (an exact duplicate) would move into its place, thus seeming to the user like multi-clicking was supported. Unfortunately I can't set the column width to 0 -- or rather, setting it to 0 doesn't completely hide the column. Instead there seems to be some minimal width to the column and the whole thing looked awful.
I wanted to do something similar with a listbox (right click support), but again I couldn't figure out how to identify where I was right clicking. I eventually settled on left clicking on a listbox and using the mouse robot approach to right click to bring up the context menu. Unlike the uitable, it was fairly easy to clear the selection on the listbox (set listbox.Value = {}). However, I strongly dislike the left click instead of right click approach and I'd rather have multiple columns.
Any suggestions would be much appreciated!!!
So I found an approach that is better than using a robot. I had tried this but was missing a critical portion which I will describe below.
Upon selecting a row in the table, the open command can be used to launch a context menu. My problem was that I didn't know where to launch the menu. I tried CurrentPoint for the figure, but it was 0,0 (or in general not valid)
Here's the current documentation for CurrentPoint:
Current point, returned as a two-element vector. The vector contains
the (x, y) coordinates of the mouse pointer, measured from the
lower-left corner of the figure. The values are in units specified by
the Units property.
The coordinates update when you do any of the following:
Press the mouse button within the figure.
Release the mouse button after pressing it within the figure.
Press the mouse button within the figure, and then release it outside
the figure.
Rotate the scroll wheel within the figure.
Move the mouse within the figure (without pressing any buttons),
provided that the WindowButtonMotionFcn property is not empty.
If the figure has a callback that responds to mouse interactions, and
you trigger that callback faster than the system can execute the code,
the coordinates might not reflect the actual location of the pointer.
Instead, they are the location when the callback began execution.
If you use the CurrentPoint property to plot points, the coordinate
values might contain rounding error.
Here's the critical line again:
"Move the mouse within the figure (without pressing any buttons), provided that the WindowButtonMotionFcn property is not empty."
So when a selection of a cell happens, the CurrentPoint is not valid. However, if we simply define a WindowButtonMotionFcn, then it is!
So the general idea is to have a callback for the table when a cell is selected (SelectionChangedFcn) and to set a dummy callback for WindowButtonMotionFcn
The final point is that a context menu can be launched with the open function if you specify a given location to launch it at. This is different from attaching it to an object and having it automatically launch on right click.
Here's some example code. If you comment out the callback for windows motion then the whole thing doesn't work! Unfortunately it is a left click for targeting the cell but at least it avoids the non-sense I was using with a java robot right click.
classdef wtf < handle
properties
h %struct, this was an appdesigner handle
cm %context menu
end
methods
function obj = wtf()
h = struct;
h.UIFigure = uifigure();
h.UITable = uitable(h.UIFigure);
obj.h = h;
obj.h.UITable.CellSelectionCallback = #obj.tableCall;
%obj.h.UITable.SelectionChangedFcn = #obj.tableCall;
%Some data ...
s = struct;
s.a = (1:4)';
s.b = (5:8)';
obj.h.UITable.Data = struct2table(s);
%Our context menu
cm = uicontextmenu(obj.h.UIFigure);
m = uimenu(cm,'Text','Menu1');
obj.cm = cm;
%WTF ... without this we don't get a valid CurrentPoint
obj.h.UIFigure.WindowButtonMotionFcn = #obj.mouseMove;
end
function tableCall(obj,x,y)
%y - event info
%x - impacted object
cp = get (obj.h.UIFigure, 'CurrentPoint');
open(obj.cm,cp(1),cp(2));
selected_cell = y.Indices;
%selected_cell = y.Selection;
x.Selection = []; %allows reselecting same cell without
%needing to select another cell first
%Now we can run something on the context menu
%that targets the selected cell
end
function mouseMove(obj,x,y)
%we could store a point here
end
end
end
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
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.
I'm making a MATLAB GUI to control a set of cameras (Thorlabs DCC1545M) which offer an ActiveX interface. One of the functions I'd like to achieve is to be able to click on the camera display window and obtain the currentPoint data (with respect to the window).
I can easily achieve this with a generic figure, but once an ActiveX control is loaded, mouse events which occur over the ActiveX region are consumed by the ActiveX control, and do not call the figure's event functions. Mouse events which occur outside this region respond as expected.
Any suggestions on how to disable the ActiveX's priority over the mouse events? Or other workarounds?
Cheers,
Here is a working Matlab file that should do what you're looking for:
exl = actxserver('excel.application');
exlWkbk = exl.Workbooks;
exlFile = exlWkbk.Open(['C:\mypath\ExcelCOM.xls']);
exlSheet1 = exlFile.Sheets.Item('Sheet1');
robj = exlSheet1.Columns.End(4); % Find the end of the column
numrows = robj.row; % And determine what row it is
dat_range = ['D2:G' num2str(numrows)]; % Read to the last row
rngObj = exlSheet1.Range(dat_range);
exlData = rngObj.Value;
for ii = 1:size(exlData,2)
matData(:,ii) = reshape([exlData{2:end,ii}],size(exlData(2:end,ii)));
lBoxList{ii} = [exlData{1,ii}];
end
exl.visible = true;
for j=1:size(exlData,2)
for k=1:13
A(k+2,j)=0
A(k,j)=matData(k,j)
end
end
exlWkbk.Close
exl.Quit
So my current workaround will be to do a getImage of the current camera frame, turn off visibility to the ActiveX component, and replace it with the image.
"windowbuttonmotionfcn" works over images in MATLAB, and all I really lose out on is a continuous live feed from the camera.
I am writing a program in which at some point a graph is plotted and displayed on screen. The user then needs to press 'y' or 'n' to accept or reject the graph. My current solution uses the PsychToolbox (the actual solution doesn't need to), which includes a command called 'KbCheck' which checks at the time of calling the state of all the keyboard buttons. My code looks like this:
function [keyPressed] = waitForYesNoKeypress
keyPressed = 0; % set this to zero until we receive a sensible keypress
while keyPressed == 0 % hang the system until a response is given
[ keyIsDown, seconds, keyCode ] = KbCheck; % check for keypress
if find(keyCode) == 89 | find(keyCode) == 78 % 89 = 'y', 78 = 'n'
keyPressed = find(keyCode);
end
end
The problem is, that the system really does 'hang' until a key is pressed. Ideally, I would be able to scroll, zoom, and generally interact with the graphs that are plotted onscreen so that I can really decide whether or not I want to press 'y' or 'n'!
I have tried adding 'drawnow;' into the while loop above but that doesn't work: I still am unable to interact with the plotted graphs until after I've accepted or rejected them.
The solution doesn't have to use PsychToolbox; I assume there are plenty of other options out there?
Thanks
I'd use the input function:
a = input('Accept this graph (y/n)? ','s')
if strcmpi(a,'y')
...
else
...
end
Although admittedly it requires two keypresses (y then Enter) rather the one.
Wait for buttonpress opens up a figure, which may be unwanted. Use instead
pause('on');
pause;
which lets the user pause until a key is pressed.
Why not using waitforbuttonpress instead?
Documentation: http://www.mathworks.fr/help/techdoc/ref/waitforbuttonpress.html
You don't want to use waitforbuttonpress since it locks the figure gui (no zooming, panning etc).
pause can cause the command window to steal the focus from the figure.
The solution I find to work best is to open the figure with a null keyPressFcn in order to avoid focus problems:
figure('KeyPressFcn',#(obj,evt) 0);
and then wait for CurrentCharacter property change:
waitfor(gcf,'CurrentCharacter');
curChar=uint8(get(gcf,'CurrentCharacter'));
Wait for key press or mouse-button click:
Example:
w = waitforbuttonpress;
if w == 0
disp('Button click')
else
disp('Key press')
end
for more information visit:
http://www.mathworks.com/help/matlab/ref/waitforbuttonpress.html
The waitforbuttonpress command is good but is triggered by either a mouse click or a key press. If you want it to trigger only from a key press, you can use the following hack:
while ~waitforbuttonpress
end