0

您好,我正在寻找 gtkmm 的信号。基本上我正在做一些模拟,我想要的是这样的:

我假设我做了 5 次模拟:

progressBar.set_fraction(0);

1 simulation
progressBar.set_fraction(progressBar.get_fraction()+1/5)


2 simulation
progressBar.set_fraction(progressBar.get_fraction()+1/5)


3 simulation
progressBar.set_fraction(progressBar.get_fraction()+1/5)


4 simulation
progressBar.set_fraction(progressBar.get_fraction()+1/5)


5 simulation
progressBar.set_fraction(progressBar.get_fraction()+1/5)

但我不知道我必须使用哪个信号以及如何转换为这个信号。

非常感谢您的帮助!!!

4

1 回答 1

0

您在问题中提出的伪代码实际上应该可以工作 - 不需要信号。但是,您可以在模拟中引入一个信号以更新进度条。恕我直言,这不会解决您的问题,我将尝试解释为什么以及如何解决它:

您提供的上下文太少了一点,因此,我将介绍一些更多假设:您有一个带有按钮或工具栏项或菜单项(或什至全部)的主窗口,它们开始模拟。

假设您在Gtk::ProgressBar::set_fraction().

一旦调试器在此断点处停止,您将在堆栈跟踪中找到以下调用(可能还有许多其他调用):

  • Gtk::Main::run()
  • 开始模拟的小部件或动作的信号处理程序
  • 运行五个模拟的函数
  • 最后调用Gtk::ProgressBar::set_fraction().

如果你能检查你的内部,Gtk::ProgressBar你会发现里面的一切Gtk::ProgressBar::set_fraction()都做得很好。那么有什么问题呢?

当您调用Gtk::ProgressBar::set_fraction()它时,它可能会生成一个公开事件(即,将一个事件添加到内部的事件队列中,Gtk::Main并请求其自己的刷新)。问题是您可能在完成所有五次模拟运行之前不处理请求。(请记住,Gtk::Main::run()对此负责的是我想象中的堆栈跟踪的最上面/最外面的调用。)因此,在模拟结束之前不会发生刷新 - 为时已晚。(顺便说一句,Gtk+ 的作者在手册中某处提到了他们优化事件的聪明才智。也就是说,事件队列中可能最终只有一个暴露事件,Gtk::ProgressBar但这并不能改善您的情况。)

因此,在您调用之后,Gtk::ProgressBar::set_fraction()您必须以某种方式刷新事件队列,然后才能进一步进行模拟。这听起来像是离开模拟,离开调用小部件信号处理程序,返回以Gtk::Main::run()进行进一步的事件处理,最后返回进行下一个模拟步骤 - 糟糕的想法。但我们做得更简单。为此,我们基本上使用以下代码(在 gtkmm 2.4 中):

while (Gtk::Main::events_pending()) Gtk::Main::iteration(false);

(希望这在您使用的 gtkmm 版本中应该是相同的,但如果有疑问,请参阅手册。)

它应该在更新进度条分数之后和继续模拟之前立即完成。

这递归地进入(部分)主循环并处理事件队列中的所有未决事件,Gtk::Main因此在模拟继续之前显示进度条。您可能担心“递归地进入主循环”,但我在 GTK+ 手册中的某处读到它是允许的(并且解决此类问题是合理的)以及需要关心的内容(即限制递归次数并授予适当的“回滚”)。

在您的情况下,我们通常称之为长期运行函数的模拟。因为这样长时间运行的函数是不会被任何 GUI 东西污染的算法(在任何库中),我们围绕这个基本概念构建了一些管理基础结构,包括

  • 带有update(double)方法和信号槽的进度“代理”对象
  • 一个定制的进度对话框,可以将信号处理程序连接到这样的进度对象(即它的信号槽)。

长时间运行的函数获取一个进度对象(作为参数)并负责以Progress::update()适当的间隔以适当的进度因子调用该方法。(我们只使用 [0, 1] 范围内的值。)

一个问题是调用进度更新的间隔。如果经常调用它,GUI 会显着减慢您长时间运行的功能。相反的情况(调用它的频率不够)会导致 GUI 的响应性降低。因此,我们决定更频繁地更新进度。为了降低 GUI 的耗时,我们在进度对话框中记住上次更新的时间并跳过下一次刷新,直到测量到上次刷新后的特定持续时间。因此,长时间运行的功能仍然需要花费一些额外的精力来更新进度,但它不再可识别了。(恕我直言,一个好的刷新间隔是 0.1 秒——人类的感知阈值,但如果有疑问,您可以选择 0.05 秒。)

刷新所有未决事件也会导致处理鼠标事件(和其他 GTK+ 信号)。这允许另一个有用的功能:中止长时间运行的功能。

当我们按下进度对话框的“取消”按钮时,它会设置一个内部标志。如果在下次检查标志时更新进度。如果标志变成true它会抛出一个特殊的异常。throw 会立即中止进度更新(长时间运行的函数)的调用者。此异常必须在按钮的信号处理程序(或称为长时间运行的函数)中捕获。否则,它会“落入”事件调度程序,在Gtk::Main那里它被确定地捕获,这将中止您的应用程序。(每当我忘记捕捉时,我经常看到它。)另一方面:捕捉特殊异常清楚地表明长时间运行的函数已被中止(与定期返回结束相反)。这可能是也可能不是可以在 GUI 上说明的东西。

最后,上述解决方案可能会导致另一个问题:它可以在模拟已经运行时启动模拟(通过 GUI)。这是可能的,因为可以在更新过程中处理模拟开始的按钮按下。为了防止这种情况,实际上有一个简单的解决方案:在 GUI 中的模拟开始时设置一个标志,直到它完成,并在设置标志时防止进一步启动。另一种选择是在模拟开始时使小部件/动作不敏感。如果您的应用程序中有多个不同的长时间运行的函数,这些函数可能会或可能不会相互排斥 - 会导致类似于排除矩阵的东西,那么这个主题会变得更加复杂。好吧,我们务实地解决了它……(但没有矩阵)。

最后但并非最不重要的一点是,我想提一下,我们使用类似的概念来输出日志视图(例如,在任何长时间运行的过程中,可视化记录信息、警告和错误)。恕我直言,为最终用户提供一些视觉操作总是好的。否则,他们可能会感到无聊并使用电话抱怨(太)慢的软件,这实际上会占用您时间使其更快(您必须打破恶性循环......)

于 2017-04-20T16:29:26.457 回答