【问题标题】:Matlab - Is there a way to capture messages sent to the workspace?Matlab - 有没有办法捕获发送到工作区的消息?
【发布时间】:2016-03-17 13:20:06
【问题描述】:

我正在使用 Matlab 中的 GUI(没有 GUIDE)。 GUI 最终将被编译为应用程序并发布给我的同事。

我的 GUI 调用了我多年前编写的一些自定义函数。这些自定义函数向工作区窗口显示状态/进度消息。

据我了解,我可以让我的可执行文件将这些消息写入日志文件,但是这会使用户在 GUI 上没有任何状态更新,而程序正在运行。

我正在处理一些非常密集的 3D 数据操作,它有可能在函数调用之间运行 5-10 分钟,所以虽然我可以在函数调用之间提供状态更新,但最终用户仍然不知道发生了什么和/或程序锁定的外观。

我想做的是有一些类似于“try-catch”方法的东西,我可以通过某种方式执行函数并捕获用于工作区的消息并将它们重定向到 uicontrol 文本框。

:编辑:

我为后代添加这个,以防万一有人想使用它。这是一个功能演示,展示了如何在下面实现彼得的答案。

首先,创建并保存一个名为“EndlessLoop”的函数:

function EndlessLoop(handles,loopCallback)
if nargin<1
    handles = [];
    loopCallback = @loop_Callback;
else
    disp('Callback already set!');
end
tic;
abort = false;
while true
    statusText = sprintf('Current Elapsed Time:\n%.2f',toc);
    abort = loopCallback(handles,statusText);
    if abort
        statusText = sprintf('Abort request processed.\nEnding now.');
        [~] = loopCallback(handles,statusText);
        break;
    end
    pause(0.1);
end
return;

function abort = loop_Callback(~,myText)
clc;
abort = false;
disp(myText)
return;

然后,创建一个调用 EndlessLoop 的 GUI:

function TestGUI
close all;
myTest = figure('Visible','on','Units','normalized','Position',[0.1 0.1 0.8 0.8],'Name','Test GUI','NumberTitle','off');
set(myTest,'menubar','none');
handles = guihandles(myTest);

handles.goButton = uicontrol('Style','pushbutton','Units','normalized','Position',[0 0.5 0.5 0.5],'String','Go');
handles.abortButton = uicontrol('Style','pushbutton','Units','normalized','Position',[0 0 0.5 0.5],'String','Abort','Enable','off');
handles.statusText = uicontrol('Style','text','Units','normalized','Position',[0.5 0 0.5 1],'String','Press Go when ready.');

set(handles.goButton,'Callback',@goButton_Callback,'interruptible','on');
set(handles.abortButton,'Callback',@abortButton_Callback,'interruptible','on');
handles.abortButton.UserData = false;
guidata(myTest,handles);
return;

function goButton_Callback(hObject,~)
handles = guidata(gcbo);
hObject.Enable = 'off';
handles.abortButton.Enable = 'on';
EndlessLoop(handles,@StatusUpdate)
handles.abortButton.String = 'Abort';
handles.abortButton.Enable = 'off';
hObject.Enable = 'on';
pause(0.5);
handles.statusText.String = 'Press Go to start again.';
handles.abortButton.UserData = false;
guidata(gcbo,handles);
return;

function abortButton_Callback(hObject,~)
handles = guidata(gcbo);
if handles.abortButton.UserData
    handles.abortButton.UserData = false;
    hObject.String = 'Abort';
else
    handles.abortButton.UserData = true;
    hObject.String = sprintf('Abort pending...');
end
guidata(gcbo,handles);
return;

function abort = StatusUpdate(handles,statusText)
clc;
abort = handles.abortButton.UserData;
disp(handles.abortButton.UserData)
handles.statusText.String = statusText;
return;

我在尝试让它工作时发现了几件事:

  1. 我只是将变量添加到句柄结构中以满足我的需要。在这种情况下,它将是handles.abortRequest = false;。但是,当我将handles 传递给 EndlessLoop 函数时,它似乎变得陈旧 - 句柄不再更新。为了解决这个问题,我必须将我想要的内容存储在 abortButton 的 UserData 部分中。我认为这是因为 abortButton 的句柄仍然有效,因为它没有改变,并且我得到了新的 UserData 因为我能够使用有效的句柄进行轮询。我不能访问handles.abortRequest,因为它不是一个对象——我不能轮询它;它只是存在,它存在于我将句柄发送到 EndlessLoop 时的“快照”中。至少,这是我的理解。

  2. 我需要将 goButton 回调的 'Interruptible' 属性设置为 'on',以便 abortButton 在进程“挂起”在 EndlessLoop 上时起作用。将 Interruptible 设置为 off 后,在该特定回调完成之前,不会处理其他回调,而无限循环永远不会发生这种情况。

因此,总而言之,这是一个完整的功能示例,可以回答我的问题。感谢 Peter 的回答 - 这还包括他的 ProTip,即能够将中止选项传递回需要很长时间才能完成的过程。我以前从未使用过这样的回调,所以希望其他人将来会发现这很有用。

【问题讨论】:

  • 如果您可以修改旧例程,您可以允许它们接受状态更新的回调函数,这将默认为控制台显示,但可以被覆盖为您的 GUI 中的函数。如果您的例程可以计算出大致的完成百分比,请将其直接提供给回调,以便 GUI 可以创建 waitbar
  • @Peter - 你能详细说明一下吗?我不完全确定你的意思。我将如何配置它?我从来没有对函数使用回调,只对 uicontrol 对象使用。
  • @LuisMendo - evalc 不会等到函数完成后再向我提供文本吗?在函数完成之前,我不知道如何使用它。
  • @Chuck 是的,一直等到函数完成

标签: matlab


【解决方案1】:

此答案详细说明了我对该问题的评论。

假设您有将内容输出到命令窗口的函数:

function out = longcomputation(param1, param2)
%
while(for_a_long_time)
   process_one_step();
   percent_complete = something;
   fprintf('Step result: %f   Complete: %f', stuff, percent_complete);
end

您可以通过传递函数句柄使输出函数可配置。在这里,我通过测试 nargin 使其成为可选:

function out = longcomputation(param1, param2, outputfcn)

function display_progress_to_console(msg, percentage)
  sprintf('%s, Complete %f', msg, percentage);
end

if(nargin < 3)  % Supply a default output function if none passed in
  outputfcn = @display_progress_to_console;
end

while(for_a_long_time)
  process_one_step();
  percent_complete = something;
  msg = sprintf('Step result: %f', stuff);
  outputfcn(msg, percent_complete);
end

现在,在您的 GUI 中,您可以定义并传入不同的输出回调。这可以直接存在于您的 GUI 代码中,以便该函数可以访问您需要的 GUI 对象。

专业提示:此外,让此回调函数返回 true 或 false 以表明用户希望中止。 GUI 然后可以显示一个取消按钮来驱动该返回值,并且长时间运行的代码可以定期发送状态消息并检查是否已请求中止。

【讨论】:

  • 我编辑了问题以包含您方法的功能示例。这是我希望做的所有事情,还有更多 - 能够将中止选项传递回长期运行的进程是一个巨大的帮助!
【解决方案2】:

您可能会使用内置的diary 功能来完成与您尝试做的类似的事情。

diary on               % Uses "diary" for filename
diary('logfile.log')   % Custom filename

这会将所有命令行输出写入指定文件。然后,您可以定期轮询此文件并使用内容更新您的uicontrol(或最后几行,如果需要)。

logfile = 'logfile.log';
diary(logfile);

u = uicontrol('style', 'text');

% Check every 5 seconds
t = timer('Period', 5, ...
          'ExecutionMode', 'FixedRate', ...
          'TimerFcn', @(s,e)populate(s));

start(t);

function populate(src)
    fid = fopen(logfile, 'rb');
    contents = fread(fid, '*char').';
    set(src, 'String', contents);
    fclose(fid);
end

【讨论】:

  • 定时器是否像中断一样工作?我的意思是,@LuisMendo 在他的评论中提到的evalc 的问题是,我认为在函数完成之前我不会得到 evalc 的输出,这意味着在函数运行时没有动态更新跑步。我没有使用 tic/toc 之外的计时器,所以我不熟悉它们的操作。
  • @Chuck 它们本身不会触发中断,因此如果您有一个长时间运行的任务,您可能希望在将输出打印到命令行后放置一个drawnow,这将刷新 MATLAB 的事件队列并允许执行计时器回调(和图形更新)。
猜你喜欢
  • 2010-10-29
  • 2023-03-27
  • 2021-07-23
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 2016-05-29
  • 1970-01-01
  • 2022-06-24
相关资源
最近更新 更多