I figured it out. The key is the distinction between "WindownButtonDownFcn" and "ButtonDownFcn". The former is called for all clicks in the window; the latter only for clicks that are not on some contained element (like my dot). Unfortunately, 'figure" doesn't seem to have (or respond to) ButtonDownFcn in the way I'd expect, so I had to apply that to the axes instead. (It's important that the axes cover the entire figure, of course!)
Here's a working version that does what I expected:
function mwe
close;
hFig = figure( 'windowstyle', 'normal');
%set(hFig,'ButtonDownFcn', @MasterButtonDown );
ax = axes ( 'parent', hFig, 'position', [0.0 0.0 1.0 1.0], 'nextplot', 'add' );
set(ax,'ButtonDownFcn', @MasterButtonDown );
set(gca, 'XLim', [-4,4], 'YLim', [-4, 4]);
% draw a single dot
hPlot = plot (ax, [1], [1], '-ro', 'ButtonDownFcn', @DotMouseDown );
% callback from the user clicking on a dot
function DotMouseDown ( obj, event)
disp('DotMD');
pause(0.05);
end
% callback from the user clicking on empty space in the window
function MasterButtonDown ( obj, event)
disp('MasterMD');
pause(0.05);
end
figure(gcf);
end