#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
, похоже, возникает та же проблема.