datacursormode:当 UpdateFcn 花费时间 return 时虚假的 MouseMotion 事件处理

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

我在 Mac OS 10.11.6 (El Capitan) 上使用 Matlab 8.1.0.604 (R2013a) 中的 DATACURSORMODE

在下面的简单示例中,数据光标仅在 单击 新位置时移动,而不是仅在移动鼠标时移动。这正是我想要的行为。但是,在我的代码中,我得到了不同的行为:第一次单击后,只要鼠标刚移动,光标就会移动,直到我随后双击。我并没有故意要求后一种行为,也不想要它。

主要区别似乎是我的 UpdateFcn 回调代码需要 时间 才能完成(假设它总是会:我的回调旨在执行相当复杂的动作,并且已经尽可能地进行了矢量化和优化)。这是由下面示例中的 pause 语句模拟的,如果未注释(可能必须 fiddle 持续时间,取决于 platform/setup)。

datacursormode.m 中,"UNDOCUMENTED FUNCTIONALITY" 评论提到 MouseMotionButtonDown 事件均由数据游标模式抛出目的。这显然不是完整的故事,因为默认情况下没有单独对鼠标移动的响应,但是 某些事情 发生了,大概是由于延迟,让它这样做。

所以我的问题是:这个光标在鼠标上移动直到你双击是已知的 feature/mode,还是只是意外的 "undefined behaviour" 作为延迟的副作用?在任何一种情况下,假设我实际上无法加速回调代码,我如何(以编程方式)防止它发生?

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))');

我实际上无法在 Windows 的 R2012a 或 R2016a 上重现您的问题,但听起来您的系统上的 MATLAB 在您单击时未能捕捉到 ButtonUp 事件。 MouseMotion 事件被处理的原因是当您按住鼠标按钮时应该能够拖动光标。

假设这确实是 UpdateFcn 的缓慢响应导致的,您可以通过触发异步操作中的缓慢部分来解决此问题,然后在完成时触发另一个更新。我认为最通用的形式是使用 timer 和非常短的 StartDelay。在下面的示例中,我使用 appdatadatacursormode 对象和计时器之间共享句柄,但您可以在具体实现中以多种不同方式处理此问题。

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

这是 Will 解决方案的编辑版本,效果很好。 StartDelay 值很关键:<=6ms 无法解决鼠标移动问题。 7ms 大部分时间解决,但偶尔会失效。 10ms 似乎工作得相当可靠(除了第一次在给定的新 Matlab 会话中,当事情唤醒很慢时)。典型的 Matlab 实现不可靠...

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