使 MATLAB 只接受一次 KeyPress

CAS*_*LLI 1 matlab key response

我目前正在尝试在 MATLAB 中编写一个实验。作为它的一部分,它应该接受并记录一个键响应,1 或 0。问题是我只需要在一个特定的时间段记录键,而在实验的其他部分被忽略。响应必须与响应所花费的时间一起记录,并且应该只做一次,最早的(这样一旦用户按下一个键,程序就不会记录后续的)。

到目前为止,我已经尝试了很多方法。这些可能是 nooby 解决方法,但我不擅长面向对象编程。

一种选择是使用

set(gcf,'KeyPressFcn',@KeyDownListener)
Run Code Online (Sandbox Code Playgroud)

KeyDownListener 在哪里

function KeyUpListener(key_hand, key_obj, starting_time)

     toc(starting_time)
     key_pressed = key_obj.Key; return; end
Run Code Online (Sandbox Code Playgroud)

但是,有两个问题:1)我正在努力尝试从这个函数中获取值回到调用脚本;2) 一旦 MATLAB 读取这段set(...)代码,它就会不断捕获按下的每个键。因此,基本上,如果有 100 次试验(每个试验由 5 个阶段组成,其中按键只能在第 4 阶段接受)放入循环中,则set(...)在第 1 阶段的第一次运行时将被忽略-3 在它第一次出现之前,但随后会出现在从第二个开始的所有运行中,在每个阶段,1-5。

然后我尝试将调用脚本和被调用函数都放入从外部脚本调用的另一个函数中,这样一旦控制返回到更高级别的脚本,我就放入另一个

set(gcf,'KeyPressFcn',@mute)
Run Code Online (Sandbox Code Playgroud)

通过哪个mute函数不执行任何操作。这似乎适用于问题 2,但它仍然不允许我获取按键回调的值,并且,由于我使用的pause(..)是让用户有时间进行响应,因此它不会因按下第一个键而中断,它等待括号中为 分配的全部时间pause

Hok*_*oki 5

传递变量:

我建议在 GUI 的回调之间使用setappdatagetappdata传递变量。
您还可以阅读在回调之间共享数据以获取有关这方面的更多信息。

分配和禁用回调:

要禁用回调函数,您无需将回调重新分配给一个什么都不做的函数,您只需分配一个空数组即可:

% 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',[])
Run Code Online (Sandbox Code Playgroud)

完整示例:

下面是一个最小的 GUI,它演示了如何捕获单个按键(和时间),然后将收集的数据保存到appdata应用程序的空间中,在那里通过“显示”功能再次收集它们(您也可以在其中执行任何操作)您想要收集的数据)。

代码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.
Run Code Online (Sandbox Code Playgroud)

在 Matlab 中以编程方式创建 GUI 充满冗长,专注于 2 个函数中的代码StartKeyCaptureKeyDownListener实现您的要求。

此示例将生成以下 GUI:

在此处输入图片说明


补充说明:

我还建议不要gcf在 GUI 应用程序中使用。在打开图形的控制台中调试或工作时,这是一个方便的快捷方式,但在 GUI 中,对其自身元素的调用应尽可能独立。MATLAB 提供了保存所有 GUI 元素(所有 uicontrol,包括主图)的句柄的方法,因此您可以在需要时显式调用它们。这降低了出错的风险(假设您的用户还在同一个 MATLAB 会话中运行其他图形,如果您的回调gcf在用户摆弄另一个图形时触发并调用,您将尝试在与它不同的图形上执行代码旨在......这很容易导致错误)。

阅读有关guidata更多信息的文档(和/或观察我在上面的示例中如何使用它)。


编辑:

为了避免在每次按下键时焦点都回到命令窗口,您还必须为相应的KeyRelease事件定义一个回调函数,否则该事件将自动转发到命令窗口(它将获得焦点)。

一个干净的方法是简单地添加回调分配

set(h.fig,'KeyReleaseFcn',@KeyReleased)
Run Code Online (Sandbox Code Playgroud)

在图形定义中一劳永逸(您不必在每个实验中设置/撤消此回调),然后定义一个function KeyReleased(~,~)不执行任何操作的空函数。这种方式在上面修改后的代码示例中实现。

另一种没有额外空函数的方法是在赋值时简单地定义回调:

set(h.fig,'KeyReleaseFcn','disp([])')
Run Code Online (Sandbox Code Playgroud)

这样你就不需要有一个空KeyReleased函数。

但是请注意,必须定义回调函数(内联或代码中的后面)。简单地分配empty给回调是行不通的。(例如,下面的两个选项都将无法拦截事件并将其转发到命令窗口:

set(h.fig,'KeyReleaseFcn','') % not working
set(h.fig,'KeyReleaseFcn',[]) % not working either
Run Code Online (Sandbox Code Playgroud)