【问题标题】:How can one dynamically update a progress indicator while a calculation started by changing an InputField value is running?当通过更改 InputField 值开始的计算正在运行时,如何动态更新进度指示器?
【发布时间】:2026-01-28 22:45:01
【问题描述】:

我有一个 Mathematica 笔记本,它采用相当复杂的用户界面来控制长时间运行的计算。除此之外,该界面充分利用了ButtonRadioButtonBarCheckboxInputField

当单击Button 的效果是一个可能需要几秒钟以上才能完成的中间计算时,我喜欢提供一个视觉指示,表明代码没有崩溃并且实际上正在做一些有用的事情.一个很好的方法是在中间计算开始之前启动ProgressIndicator,然后在计算完成后将其关闭。我发现这对于单击Button 开始的计算很简单。

但是,对于由更改 InputField 值启动的计算,相同的方法不起作用。下面的简化代码是为此而编写的,但失败了。 Grid 的最后两行应该会在内部Dynamic 命令中的updatingQ 更改为True 时自动更改,然后在updatingQ 恢复为True 时更改回来,但它永远不会发生。看起来外部Dynamic 代码在内部Dynamic 代码运行时被阻止,因此它甚至从未注意到updatingQ 的更改。

另一方面,如果在单独的输入行上手动设置updatingQ=TrueGrid 的最后两行将按预期响应。

(BTW, i) Pause[2] 只是中间计算的替身,ii) 我将输入值乘以 Pi 只是为了在替身计算完成时更明显。)

显然,Buttonaction 部分的行为有所不同。同一Dynamic 块中的其他代码可以看到并在更改标志时快速响应。值得注意的是,在这种情况下我使用Method->"Queued"。我对 InputField 进行了同样的尝试(对此它不是一个记录的选项),但没有效果。

我尝试了这里没有显示的其他各种方法,但都没有成功。

我们将不胜感激。

Clear[ProgressIndicatorTest]
updatingQ = False;
ProgressIndicatorTest = {
   TextCell["ProgressIndicatorTest", "Subsubsection", Background -> LightBlue],
   DynamicModule[
    {filterTypes = {"Max energy", "Max length"}, filterValue, workingOn = "", iter = 0},
    Scan[(filterValue[#[[1]]] = #[[2]]) &, Transpose@{filterTypes, {0.1, 100.}}];
    Dynamic[
     Grid[
      Join[
       Map[
        Function[
         filterType,
         {filterType,
          Dynamic@
           InputField[
            Dynamic[
             filterValue[filterType],
             Function[
              value,
              If[value > 0,
               updatingQ = True;
               Pause[2];
               filterValue[filterType] = \[Pi] value;
               updatingQ = False
               ]
              ]
             ], FieldSize -> 5, Alignment -> Right
            ]
          }
         ], filterTypes
        ],
       {{updatingQ, "-------"}},
       {If[updatingQ,
         {"Updating ... ", 
          ProgressIndicator[Appearance -> "Indeterminate"]},
         Nothing
         ]}
       ], Alignment -> Left, 
      Background -> {None, {LightGreen, LightGreen, LightYellow, LightYellow}}
      ]
     ]
    ]
   };
CellGroup[ProgressIndicatorTest]

【问题讨论】:

    标签: dynamic wolfram-mathematica


    【解决方案1】:

    正如阿甘正传从未说过的那样,“Stackoverflow/Stackexchange 就像一盒巧克力......你永远不知道你会得到什么”。所以今天我找到了this的答案,解决了我的问题。

    适应我的特殊情况,得到的代码如下:

    Clear[ProgressIndicatorTest]
    calculation[n_] := Module[{a = .3}, Do[a = a (1 - a), {i, n 10^6}]]
    updatingQ = False;
    ProgressIndicatorTest = {
       TextCell["ProgressIndicatorTest", "Subsubsection", Background -> LightBlue], 
       DynamicModule[{filterTypes = {"Max energy", "Max length"}, filterValue, upToDateQ = True}, 
        Scan[(filterValue[#[[1]]] = #[[2]]) &, Transpose@{filterTypes, {0.1, 100.}}];
        Dynamic[
         Grid[
          Join[
           Map[
            Function[
             filterType, 
             {filterType, 
              DynamicWrapper[
               InputField[
                Dynamic[
                 filterValue[filterType], 
                 Function[
                  value,
                  If[value > 0,
                   upToDateQ = False;
                   filterValue[filterType] = value
                   ]
                  ]
                 ], FieldSize -> 5, Alignment -> Right
                ],
               If[! upToDateQ,
                Refresh[
                 updatingQ = True; calculation[2]; updatingQ = False;
                 upToDateQ = True,
                 None
                 ]
                ],
               SynchronousUpdating -> False
               ]
              }
             ], filterTypes
            ],
            {
            If[updatingQ,
             {"Updating ... ", 
              ProgressIndicator[Appearance -> "Indeterminate", ImageSize -> 80]},
             Nothing
             ]
            }
           ], Alignment -> Left, 
          Background -> {None, {LightGreen, LightGreen, LightYellow,}}]
         ]]
       };
    CellGroup[ProgressIndicatorTest]
    

    这段代码完全符合我的要求。

    成功的关键是将DynamicWrapper 包裹在InputField 周围,并插入一个构造巧妙的第二个参数,该参数执行标志重置(在我的例子中为upToDate=False),触发位于其他地方的ProgressIndicator

    还有几点。

    • Pause 不是一个很好的计算替代品。您可能会观察到代码的行为与 calculation 等真实函数不同。
    • 有趣的是,upToDateQ 可以是局部变量,而 updatingQ 不能。

    感谢 Albert Retey 在 2013 年提供代码。

    【讨论】:

    • 很好的解决方案。 Refresh 有必要吗?
    【解决方案2】:

    InputField 的文档说

    "一个表达式类型 [默认] 的 InputField 替换它的 每次内容都具有完全评估形式的内容 更新”。

    这似乎意味着 InputField 在释放值更改之前私下评估其内容和所有连接的动态,可能是为了防止循环评估。

    下面的例子浓缩了这个问题。第一部分工作正常...

    changed = processing = False;
    Column[{InputField[Dynamic[x, (changed = True; x = 2 #) &], FieldSize -> 5],
      Dynamic[changed],
      Dynamic[processing]}]
    

    ...直到下面的动态也被评估。然后changed 永远不会显示 True,因为它在更新结束之前已更改回 False。

    Dynamic[If[changed,
      processing = True;
      Pause[2];
      changed = processing = False]]
    

    另一种策略是使用Button,例如

    changed = False;
    processing = Spacer[0];
    Column[{InputField[Dynamic[y, (changed = True; y = #) &], FieldSize -> 5],
      Button["Enter",
       If[changed,
        processing = ProgressIndicator[Appearance -> "Indeterminate", ImageSize -> 120];
        Pause[2];
        y = 2 y;
        changed = False;
        processing = Spacer[0]], Method -> "Queued", Enabled -> Dynamic[changed]],
      Dynamic[changed],
      Dynamic[processing]}]
    

    这个较短的版本避免了在输入字段之外使用制表符的需要。

    changed = False;
    processing = Spacer[0];
    Column[{InputField[Dynamic[y], FieldSize -> 5], 
      Button["Enter", 
       processing = ProgressIndicator[Appearance -> "Indeterminate", ImageSize -> 120];
       Pause[2];
       y = 2 y;
       processing = Spacer[0], Method -> "Queued"], Dynamic[processing]}]
    

    注意Method -> "Queued" 的使用使ButtonInputField 具有优势。没有它Button 似乎也有同样的问题。

    【讨论】:

      最近更新 更多