Как можно динамически обновлять индикатор выполнения во время выполнения вычисления, начатого путем изменения значения входного поля?

#dynamic #wolfram-mathematica

Вопрос:

У меня есть ноутбук Mathematica, в котором используется довольно сложный пользовательский интерфейс для управления длительными вычислениями. Помимо прочего, интерфейс широко использует преимущества Button , RadioButtonBar , Checkbox , и InputField .

Когда эффект нажатия кнопки a Button является промежуточным вычислением, которое может занять более пары секунд, мне нравится предоставлять визуальное указание на то, что код не разбился и на самом деле делает что-то полезное. Хороший способ сделать это-запустить a ProgressIndicator непосредственно перед началом промежуточного расчета, а затем выключить его после завершения расчета. Я обнаружил, что это просто для расчетов, запускаемых одним Button щелчком мыши.

Однако тот же метод не работает для вычислений, которые инициируются изменениями InputField значения. Приведенный ниже упрощенный код был написан для этого, но не удался. Последние две строки Grid должны автоматически изменяться при updatingQ изменении True во внутренней Dynamic команде , а затем изменяться обратно при updatingQ возврате True , но этого никогда не происходит. Похоже, что внешний Dynamic код блокируется во время выполнения внутреннего Dynamic кода, поэтому он даже не замечает изменений updatingQ .

С другой стороны, последние две строки Grid отвечают так, как ожидалось, если вручную задать updatingQ=True отдельную строку ввода.

(Кстати, i) Pause[2] -это просто дополнение для промежуточного расчета, и ii) я умножаю входное значение на число Pi, чтобы сделать его более очевидным, когда выполняется дополнительный расчет.)

По-видимому, часть действия а Button ведет себя по-другому. Другие фрагменты кода в том же 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]]) amp;, 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]
 

Ответ №1:

Как никогда не говорил Форрест Гамп, «Переполнение стека/обмен стеками похожи на коробку конфет … никогда не знаешь, что получишь». И вот сегодня я нашел этот ответ, который решает мою проблему.

Адаптированный к моему конкретному случаю, полученный код выглядит следующим образом:

 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]]) amp;, 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 как не может.

Спасибо Альберту Рети за предоставление кода еще в 2013 году.

Комментарии:

1. Отличное решение. Это Refresh необходимо?

Ответ №2:

Документация для InputField says

«Поле ввода типа Выражение [по умолчанию] заменяет его содержимое полностью оцененной формой каждый раз, когда содержимое обновляется».

Это, по-видимому, означает, что поле ввода в частном порядке оценивает свое содержимое и всю связанную динамику, прежде чем публиковать изменения значений, вероятно, для предотвращения циклических оценок.

Следующий пример сглаживает проблему. Первая часть работает нормально …

 changed = processing = False;
Column[{InputField[Dynamic[x, (changed = True; x = 2 #) amp;], 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 = #) amp;], 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" дает Button преимущество перед InputField . Без этого Button , похоже, возникает та же проблема.