进度条设计模式?

19
我正在编写的应用程序执行一个长度算法,通常需要几分钟才能完成。在此期间,我希望向用户显示进度条,尽可能精确地指示算法已完成多少部分。
该算法分为几个步骤,每个步骤都有自己的典型时间。例如:
- 初始化(500毫秒) - 读取输入(5秒) - 步骤1(30秒) - 步骤2(3分钟) - 写输出(7秒) - 关闭(10毫秒)
每个步骤可以通过设置其工作范围(例如[0到150])并报告其主循环中已完成的值来轻松报告其进度。
目前我设置了一个嵌套进度监视器的方案,形成了一种隐式的进度报告树。所有进度监视器都继承自接口IProgressMonitor。
class IProgressMonitor
{
public:
  void setRange(int from, int to) = 0;
  void setValue(int v) = 0;
};

树的根是ProgressMonitor,它连接到实际的GUI界面:

class GUIBarProgressMonitor : public IProgressMonitor
{
   GUIBarProgressMonitor(ProgressBarWidget *);
};

树中的任何其他节点都是监视器,它们控制父进度的一部分:

class SubProgressMonitor : public IProgressMonitor
{
  SubProgressMonitor(IProgressMonitor *parent, int parentFrom, int parentLength)
  ...
};

一个SubProgressMonitor控制其父进度条的范围为[parentFrom,parentFrom + parentLength]

使用这种方案,我能够根据全局定时中每个步骤的预期相对部分静态地划分顶层进度。然后可以进一步将每个步骤细分为多个片段等。

这种方法的主要缺点是划分是静态的,并且根据在运行时发现的变量进行更改会很痛苦。

因此问题是:是否有任何已知的进度监视设计模式可以解决此问题?


我曾经思考过同样的问题,但是还没有找到令人满意的解决方案。我想到的方法与你的类似,但是它需要将IProgressMonitor对象从高层传递到实际递增发生的地方。这样做可能会对代码进行大量修改,并且耗费时间。你有没有更好的方法来传递父进度监视器,或者你有没有想到完全不同的模式? - undefined
5个回答

5
一个非常有趣的方法是用户感知。 Chris Harrison 发表了一篇关于用户如何根据进度条报告的进度感知时间流逝的论文(尽管所有实验的实际持续时间显然相同)。
请注意,首选的显示公式是 (x + (1-x) / 2 )8,其中 x 是在 0 到 1 的比例尺上的实际进度 :)
因此,我建议:
- 收集一些有关给定任务所需时间百分比的统计数据 - 测量初始化并将其用于在进度条上缩放您的进度,保持悲观(例如准备一个 10-15% 的缓冲区) - 在最后一个任务(或最后几个任务,只要它们具有确定性持续时间)之前,全力以赴以便及时完成进度条(带有渐进加速)
我知道,这不太准确,但如果用户认为更快,我会接受它!

4
彼得的方法是我在一个大项目中采用的方法; 在我们的试点和初始推出期间,我们数千台移动设备都发送了时间和使用数据,我们使用所花费时间的平均值、中位数和标准偏差来微调任务的配置(当任务允许运行时,允许运行多长时间,在进度条显示中使用哪些值等)。由于我们的解决方案有点像你的解决方案,但是驱动值是通过XML配置文件提供的,我们考虑将其构建为自动化系统(例如,服务器会定期检查这些值,注意到某些任务最近花费的时间比以前长,并更新配置文件以重新安排或延长它们),但我们认为这并不值得麻烦,只是为了防止每隔几周进行快速人工审查。

由于我不知道你的问题是否有技术解决方案,因此我认为向用户展示什么(以及您花费多少时间开发解决方案)应基于功能问题:谁在使用这个?信息需要多精确?这是一个交互过程,在此过程中他们不能做其他工作,还是可以让它在后台运行并回来?您的长时间运行函数发生的工作过程是时间敏感还是任务关键的?

很抱歉,我实际上无法给您所寻找的答案,但也许思考您试图在广泛范围内实现什么会想到一个好主意 = )


2
构建一个AggregateProgressMonitor,它会根据子进度监视器报告的信息自动计算子进度划分。子进度监视器至少应通知父级其“预期”运行时间。然后,各自操作基于运行时参数可以更新子监视器估计的运行时间,整体进度报告将相应地自动调整。
class IAggregateProgressMonitor : public IProgressMonitor
{
   void setChildValue(IProgressMonitor *, int v);
   void setChildEstimatedTime(IProgressMonitor *, int v);
}

class AggregateProgressMonitor : public IAggregateProgressMonitor 
{
   void setChildValue(IProgressMonitor * child, int v)
   {
      int aggregateValue = mapChildValueToAggregateValue(child, v);
      setValue(aggregateValue);
   }

   void setChildEstimatedTime(IProgressMonitor * child, ulong ms)
   {
      children[child]->estimatedTime = ms;
      updateChildProgressRatios();
   }
}

class SubProgressMonitor : public IProgressMonitor
{
  SubProgressMonitor(IAggregateProgressMonitor *parent, int parentFrom, 
                     int parentLength) ... ;
  void setValue(int v)
  {
     parent->setChildValue(this, v);
  }

  void setEstimatedRunningTime(ulong ms)
  {
    parent->setChildEstimatedTime(this, ms);
  } 
};

您甚至可以使用第一步的观察时间,重新映射后续进度报告者以使其更加准确。

您需要在AggregateProgressMonitor中保持某种有序映射,以便能够跟踪和计算来自子级的所有信息。

完成后,您可以扩展AggregateProgressMonitor(覆盖IProgressMonitor方法)以向用户显示进度。


1

这是一个棘手的问题,我们在以前的项目中也曾经苦苦挣扎过。

我能想到的最好的解决方法是收集每个阶段实际花费的时间统计数据,并相应地调整相对间隔长度。

虽然我们在那个项目中没有实施它(至少在我在那里的时候没有),所以这只是一个理论上的想法 :-)


-1
你可以考虑用进度圆替换进度条。如果任务有N个步骤,那么在饼图中制作N个楔形,并在每个步骤运行时像进度条一样填充每个楔形。
另外,可以考虑为每个步骤显示一些文本,这样他们在步骤进行时就有东西可以阅读了。

1
因为回答侧重于可视化,而问题是关于代码设计模式的。显然,提问者已经超越了使用相等进度的不同长度步骤的琐碎解决方案。 - Hubert Grzeskowiak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接