让 MATLAB 只接受一次 KeyPress
Make MATLAB accept KeyPress only once
我目前正在尝试在 MATLAB 中编写一个实验。作为它的一部分,它应该接受并记录一个键响应,1 或 0。问题是我只需要在一个特定的时间段记录键,而在实验的其他部分被忽略。响应必须连同响应时间一起记录,并且应该只做一次,最早(这样一旦用户按下一个键,程序就不会记录后续的)。
到目前为止,我尝试了很多方法。这些可能是笨拙的解决方法,但我不擅长面向对象编程。
一种选择是使用
set(gcf,'KeyPressFcn',@KeyDownListener)
KeyDownListener 在哪里
function KeyUpListener(key_hand, key_obj, starting_time)
toc(starting_time)
key_pressed = key_obj.Key; return; end
但是,有两个问题:1) 我正在努力尝试将此函数的值返回给调用脚本; 2) 一旦 MATLAB 读取了这段 set(...)
代码,它就会不断捕获按下的每个键。所以,基本上,如果在循环中的实验中有 100 个试验(每个试验由 5 个阶段组成,其中按键应该只在第 4 阶段被接受),set(...)
将被忽略第一个 运行 在它首次出现之前的第 1-3 阶段,但随后会出现在从第二个开始的所有 运行 中,在每个阶段,1-5.
然后我尝试将调用脚本和被调用函数都放入另一个从外部脚本调用的函数中,这样一旦控制权返回到更高级别的脚本,我就放入另一个
set(gcf,'KeyPressFcn',@mute)
其中 mute
函数不执行任何操作。这似乎适用于问题 2,但它仍然不允许我获取按键回调的值,并且,因为我使用 pause(..)
来让用户有时间进行响应,它不会中断按下第一个键,它等待括号中分配给 pause
.
的全部时间
传递变量:
我建议使用 setappdata
and getappdata
在 GUI 的回调之间传递变量。
您还可以阅读 Share Data Among Callbacks 以获取有关这方面的更多信息。
分配和禁用回调:
要禁用回调函数,您不需要将回调重新分配给什么都不做的函数,只需分配一个空数组即可:
% assign the 'KeyDownListener' function and pass one parameter ('var1') with it
set( h.fig, 'KeyPressFcn',{@KeyDownListener,var1})
% then later when you don't need it anymore:
% Disable the 'KeyPressFcn listener, assign 'empty' to it
set( h.fig, 'KeyPressFcn',[])
完整示例:
下面是一个最小的 GUI,它演示了如何捕获单个按键(和时间),然后将收集的数据保存到应用程序的 appdata
space 中,在那里收集它们再次通过 'display' 函数(您也可以在其中对收集到的数据做任何您想做的事)。
代码 SingleKeyPressDemo.m
:
function h = SingleKeyPressDemo
% create a minimal figure with a button and 2 text fields
h.fig = figure ;
h.btn = uicontrol('Style','Pushbutton','String','Start capture',...
'units','Normalized','Position',[0.1 0.6 0.8 0.3],...
'Callback',@StartKeyCapture) ;
h.txtKey = uicontrol('Style','text','String','Key pressed:',...
'units','Normalized','Position',[0.1 0.4 0.8 0.1]) ;
h.txtTime = uicontrol('Style','text','String','Elapsed time:',...
'units','Normalized','Position',[0.1 0.3 0.8 0.1]) ;
% assign a callback to the KeyRelease event to intercept passing the
% eventdata to the command window
set(h.fig,'KeyReleaseFcn',@KeyReleased)
% initialise appdata variables to hold the captured key and the time
setappdata( h.fig , 'CapturedKey' , [] )
setappdata( h.fig , 'Elapsed_time' , [] )
% save the handle structure
guidata( h.fig , h)
function StartKeyCapture(hobj,~)
h = guidata( hobj ) ; % retrieve the handle structure
StartTime = tic ; % Start a stopwatch
% assigne the callback funtion, passing the stopwatch in parameter
set(h.fig,'KeyPressFcn',{@KeyDownListener,StartTime})
% disable the button until a key is pressed (also makes it loose the
% focus, which is handy otherwise the button keeps the focus and hides
% the 'KeyPressedFcn'
set( h.btn , 'Enable','off')
function KeyDownListener(hobj, key_obj, starting_time)
% Detect key pressed and time elapsed
Elapsed_time = toc(starting_time) ;
key_pressed = key_obj.Key;
h = guidata( hobj ) ; % retrieve the handle structure
setappdata( h.fig , 'CapturedKey' , key_pressed ) ; % save the captured key
setappdata( h.fig , 'Elapsed_time' , Elapsed_time ) ; % save the elapsed time
set(h.fig,'KeyPressFcn',[]) % remove listener so new key press will not trigger execution
set( h.btn , 'Enable','on') % re-enable the button for a new experiment
% (optional) do something after a key was pressed
display_results(h.fig) ;
function display_results(hobj)
h = guidata( hobj ) ; % retrieve the handle structure
% retrieve the saved data
CapturedKey = getappdata( h.fig , 'CapturedKey' ) ;
Elapsed_time = getappdata( h.fig , 'Elapsed_time' ) ;
% update display
set( h.txtKey , 'String', sprintf('Key pressed: %s',CapturedKey) ) ;
set( h.txtTime , 'String', sprintf('Elapsed time: %f ms',Elapsed_time) ) ;
function KeyReleased(~,~)
% do nothing
% The only purpose of this function is to intercept the KeyReleased event
% so it won't be automatically passed on to the command window.
在 Matlab 中以编程方式创建 GUI 非常冗长,请关注 2 个函数 StartKeyCapture
和 KeyDownListener
中的代码以实现您的要求。
此示例将生成以下 GUI:
补充说明:
我还建议不要在 GUI 应用程序中使用 gcf
。当调试或在控制台中打开图形时,这是一个方便的快捷方式,但在 GUI 中,对其自身元素的调用应该尽可能独立。 MATLAB 提供了保存所有 GUI 元素(所有 uicontrol,包括主图形)句柄的方法,因此您可以在需要时显式调用它们 。这降低了出错的风险(假设您的用户也是同一 MATLAB 会话中的 运行 其他数字,如果您的回调触发并调用 gcf
当用户正在摆弄另一个数字时,您将尝试在不同于预期的图形上执行代码...这很容易导致错误)。
阅读 guidata
上的文档以获取更多信息(and/or 观察我在上面的示例中是如何使用它的)。
编辑:
为了避免每次按下时焦点都回到命令window,你还必须为相应的KeyRelease
事件定义一个回调函数,否则该事件将被自动转发到命令 window(这将获得焦点)。
一种简洁的方法是简单地添加回调分配
set(h.fig,'KeyReleaseFcn',@KeyReleased)
在图形定义中一劳永逸(你不必在每个实验中都set/undo这个回调),然后定义一个什么都不做的空函数function KeyReleased(~,~)
。这种方式在上面的修改代码示例中实现。
另一种没有额外空函数的方法是在赋值时简单地定义回调:
set(h.fig,'KeyReleaseFcn','disp([])')
这样你就不需要一个空的 KeyReleased
函数。
但是请注意,回调函数 必须 定义(内联或稍后在代码中)。简单地将 empty
分配给回调是行不通的。 (例如,下面的两个选项都无法拦截事件并将其转发到命令 window:
set(h.fig,'KeyReleaseFcn','') % not working
set(h.fig,'KeyReleaseFcn',[]) % not working either
我目前正在尝试在 MATLAB 中编写一个实验。作为它的一部分,它应该接受并记录一个键响应,1 或 0。问题是我只需要在一个特定的时间段记录键,而在实验的其他部分被忽略。响应必须连同响应时间一起记录,并且应该只做一次,最早(这样一旦用户按下一个键,程序就不会记录后续的)。
到目前为止,我尝试了很多方法。这些可能是笨拙的解决方法,但我不擅长面向对象编程。
一种选择是使用
set(gcf,'KeyPressFcn',@KeyDownListener)
KeyDownListener 在哪里
function KeyUpListener(key_hand, key_obj, starting_time)
toc(starting_time)
key_pressed = key_obj.Key; return; end
但是,有两个问题:1) 我正在努力尝试将此函数的值返回给调用脚本; 2) 一旦 MATLAB 读取了这段 set(...)
代码,它就会不断捕获按下的每个键。所以,基本上,如果在循环中的实验中有 100 个试验(每个试验由 5 个阶段组成,其中按键应该只在第 4 阶段被接受),set(...)
将被忽略第一个 运行 在它首次出现之前的第 1-3 阶段,但随后会出现在从第二个开始的所有 运行 中,在每个阶段,1-5.
然后我尝试将调用脚本和被调用函数都放入另一个从外部脚本调用的函数中,这样一旦控制权返回到更高级别的脚本,我就放入另一个
set(gcf,'KeyPressFcn',@mute)
其中 mute
函数不执行任何操作。这似乎适用于问题 2,但它仍然不允许我获取按键回调的值,并且,因为我使用 pause(..)
来让用户有时间进行响应,它不会中断按下第一个键,它等待括号中分配给 pause
.
传递变量:
我建议使用 setappdata
and getappdata
在 GUI 的回调之间传递变量。
您还可以阅读 Share Data Among Callbacks 以获取有关这方面的更多信息。
分配和禁用回调:
要禁用回调函数,您不需要将回调重新分配给什么都不做的函数,只需分配一个空数组即可:
% assign the 'KeyDownListener' function and pass one parameter ('var1') with it
set( h.fig, 'KeyPressFcn',{@KeyDownListener,var1})
% then later when you don't need it anymore:
% Disable the 'KeyPressFcn listener, assign 'empty' to it
set( h.fig, 'KeyPressFcn',[])
完整示例:
下面是一个最小的 GUI,它演示了如何捕获单个按键(和时间),然后将收集的数据保存到应用程序的 appdata
space 中,在那里收集它们再次通过 'display' 函数(您也可以在其中对收集到的数据做任何您想做的事)。
代码 SingleKeyPressDemo.m
:
function h = SingleKeyPressDemo
% create a minimal figure with a button and 2 text fields
h.fig = figure ;
h.btn = uicontrol('Style','Pushbutton','String','Start capture',...
'units','Normalized','Position',[0.1 0.6 0.8 0.3],...
'Callback',@StartKeyCapture) ;
h.txtKey = uicontrol('Style','text','String','Key pressed:',...
'units','Normalized','Position',[0.1 0.4 0.8 0.1]) ;
h.txtTime = uicontrol('Style','text','String','Elapsed time:',...
'units','Normalized','Position',[0.1 0.3 0.8 0.1]) ;
% assign a callback to the KeyRelease event to intercept passing the
% eventdata to the command window
set(h.fig,'KeyReleaseFcn',@KeyReleased)
% initialise appdata variables to hold the captured key and the time
setappdata( h.fig , 'CapturedKey' , [] )
setappdata( h.fig , 'Elapsed_time' , [] )
% save the handle structure
guidata( h.fig , h)
function StartKeyCapture(hobj,~)
h = guidata( hobj ) ; % retrieve the handle structure
StartTime = tic ; % Start a stopwatch
% assigne the callback funtion, passing the stopwatch in parameter
set(h.fig,'KeyPressFcn',{@KeyDownListener,StartTime})
% disable the button until a key is pressed (also makes it loose the
% focus, which is handy otherwise the button keeps the focus and hides
% the 'KeyPressedFcn'
set( h.btn , 'Enable','off')
function KeyDownListener(hobj, key_obj, starting_time)
% Detect key pressed and time elapsed
Elapsed_time = toc(starting_time) ;
key_pressed = key_obj.Key;
h = guidata( hobj ) ; % retrieve the handle structure
setappdata( h.fig , 'CapturedKey' , key_pressed ) ; % save the captured key
setappdata( h.fig , 'Elapsed_time' , Elapsed_time ) ; % save the elapsed time
set(h.fig,'KeyPressFcn',[]) % remove listener so new key press will not trigger execution
set( h.btn , 'Enable','on') % re-enable the button for a new experiment
% (optional) do something after a key was pressed
display_results(h.fig) ;
function display_results(hobj)
h = guidata( hobj ) ; % retrieve the handle structure
% retrieve the saved data
CapturedKey = getappdata( h.fig , 'CapturedKey' ) ;
Elapsed_time = getappdata( h.fig , 'Elapsed_time' ) ;
% update display
set( h.txtKey , 'String', sprintf('Key pressed: %s',CapturedKey) ) ;
set( h.txtTime , 'String', sprintf('Elapsed time: %f ms',Elapsed_time) ) ;
function KeyReleased(~,~)
% do nothing
% The only purpose of this function is to intercept the KeyReleased event
% so it won't be automatically passed on to the command window.
在 Matlab 中以编程方式创建 GUI 非常冗长,请关注 2 个函数 StartKeyCapture
和 KeyDownListener
中的代码以实现您的要求。
此示例将生成以下 GUI:
补充说明:
我还建议不要在 GUI 应用程序中使用 gcf
。当调试或在控制台中打开图形时,这是一个方便的快捷方式,但在 GUI 中,对其自身元素的调用应该尽可能独立。 MATLAB 提供了保存所有 GUI 元素(所有 uicontrol,包括主图形)句柄的方法,因此您可以在需要时显式调用它们 。这降低了出错的风险(假设您的用户也是同一 MATLAB 会话中的 运行 其他数字,如果您的回调触发并调用 gcf
当用户正在摆弄另一个数字时,您将尝试在不同于预期的图形上执行代码...这很容易导致错误)。
阅读 guidata
上的文档以获取更多信息(and/or 观察我在上面的示例中是如何使用它的)。
编辑:
为了避免每次按下时焦点都回到命令window,你还必须为相应的KeyRelease
事件定义一个回调函数,否则该事件将被自动转发到命令 window(这将获得焦点)。
一种简洁的方法是简单地添加回调分配
set(h.fig,'KeyReleaseFcn',@KeyReleased)
在图形定义中一劳永逸(你不必在每个实验中都set/undo这个回调),然后定义一个什么都不做的空函数function KeyReleased(~,~)
。这种方式在上面的修改代码示例中实现。
另一种没有额外空函数的方法是在赋值时简单地定义回调:
set(h.fig,'KeyReleaseFcn','disp([])')
这样你就不需要一个空的 KeyReleased
函数。
但是请注意,回调函数 必须 定义(内联或稍后在代码中)。简单地将 empty
分配给回调是行不通的。 (例如,下面的两个选项都无法拦截事件并将其转发到命令 window:
set(h.fig,'KeyReleaseFcn','') % not working
set(h.fig,'KeyReleaseFcn',[]) % not working either