Append new simulation into CDF without resetting current simulations
Introduction
I present two methods below. The new way has some advantages over the old way. The original is a straightforward method that maintains a variable that stores the states of the copies of the DynamicModule
/Manipulate
myCDF
added, which is similar to Mike Honeychurch's answer. (The primary difference is that mine injects the state variable into the definition of myCDF
and Mike's passes it as an argument, which seems a better way.) The problem with these two methods is that the existing myCDFs
are all replaced by new ones when another is added or one is removed. This would be a problem if they take a long time to initialize. Another is if myCDF
has many state variables. They all have to be updated. The new way adds a new myCDF
or removes the last one, but it leaves all the others intact without regenerating them. This seems the right behavior to strive for. The code is only slightly more complicated than the OP's starting point.
The problem is that the thing you want to copy is a front end object, not a kernel variable. You can copy front end objects from the front end (select/copy) and paste them into the front end manually. I don't know how to do this programmatically. Perhaps there are undocumented functions that give access to the dynamic objects in the front end.
The problem with grid
being a kernel variable is that the code in the Manipulates
each set their variable a
equal to 1
on instantiation by the front end. Whenever grid
is changed by AppendTo
, all of the Manipulates
are re-instantiated by in the front, which resets the values of a
.
New way
Instead of a Grid
or even a simple list, here is a way using a sort linked list structure that ends with a pair of buttons. The add button replaces the pair with a new Manipulate
and another pair buttons. (Actually, it switches views using PaneSelector
.) The remove button cause the parent to switch back, replacing the Manipulate
/Button
combo with another pair of buttons.
One particular odd problem was getting the remove button linked back to the parent. If you don't do something to make the variable sel
unique, the symbol that gets passed around is $CellContext`sel$$
. If that symbol is passed to the new DynamicModule
in the call
newMan[Dynamic @ sel]
then the symbol for oldsel
and sel
will be the same! And the remove button is linked to the current element and not the parent. I thought of various ways to get around this: Use Module
for sel
and inject and Unique
symbol with ReplaceAll
. All gave red highlighting, a warning for a local scope conflict. They all seemed to work, on this example at least. I chose to present the one with Function
. (I had to choose one.) Another odd problem is that without ImageSize -> Automatic
, the ImageSizeCache
of the enclosing Dynamic
is not resized automatically. I don't know if that's a bug, nor exactly why the ImageSize
option fixes it.
ClearAll[newMan, myCDF];
myCDF = Manipulate[Plot[Sinc[a x], {x, 0, 10}], {a, 1, 10}];
newMan[] := newMan[Dynamic[0]]; (* initial call *)
newMan[Dynamic[oldsel_]] :=
Function[{sel},
DynamicModule[{addbtn, rembtn, sel},
sel = "buttons";
addbtn = Button["Add", sel = "manipulate"];
rembtn = Button["Remove", oldsel = "buttons", Enabled -> oldsel =!= 0];
Column[{myCDF, PaneSelector[{
"buttons" -> Grid[{{addbtn, rembtn}}],
"manipulate" -> Dynamic @ newMan[Dynamic @ sel]},
Dynamic @ sel,
ImageSize -> Automatic]}]
]] @ Unique["sel"]
Panel@newMan[]
Original way
The first way I could figure out how to work around this limitation was to keep track of all the states of the Manipulates
that are created. If the Manipulate
has many state variables, perhaps this might be aided by auxiliary functions. In any case, I hope following might help in the actual use case. The state variable a0
is updated whenever one of the Manipulates
reevaluates. In addition to a state variable, I added Initialization
code that makes sure each Manipulate
is properly initialized whenever they are recreated after grid
is appended to.
DynamicModule[{myCDF, controls, grid, btn, a0, i},
i = 0;
myCDF[] := With[{j = ++i},
If[j == 1, a0 = {1}, AppendTo[a0, Last@a0]];
Manipulate[Plot[Sinc[(a0[[j]] = a) x], {x, 0, 10}], {a, 1, 10},
Initialization :> (a = a0[[j]])]];
grid = {myCDF[]};
btn["Add"] = Button["Add", AppendTo[grid, myCDF[]]];
btn["Rem"] = Button["Remove", If[Length[grid] > 1, grid = grid[[1 ;; -2]]]];
controls = Grid[{{btn["Add"], btn["Rem"]}}];
Dynamic @ Panel[Column[{Grid[Transpose @ {grid}], controls}]]]
Okay after the comments here is a rewrite that delivers what you need in a relatively simple way. You have mentioned that in your real life example you are not using Manipulate
. Good. I don't like it so I'll use DynamicModule
. So I'll start with a new version of myCDF
:
myCDF[Dynamic[var_], num_] := DynamicModule[{a = var[[num]]},
Panel@Column[{
Manipulator[Dynamic[a, (a = #; var[[num]] = #) &], {1, 10}],
Framed[
Dynamic@
Plot[Sinc[a x], {x, 0, 10}, ImageSize -> 300, PlotLabel -> num],
Background -> White,
FrameStyle -> GrayLevel[0.7]]
}]
]
the value of a
gets carried over to the next version of myCDF
. var
is a list of the initial values of a
in each plot. Stick this all in a small DynamicModule
(I have only included the "Add" button. Adding the remove should be straight forward, just Drop
and Decrement
).
DynamicModule[{var = {1}, i = 1, grid},
grid = {myCDF[Dynamic[var], 1]};
Column[{
Dynamic@Column[grid],
Button["Add", AppendTo[var, var[[-1]]]; ++i;
grid = myCDF[Dynamic[var], #] & /@ Range[1, i], ImageSize -> 100],
Button["Remove", If[Length@var>1,var=Drop[var,-1]; --i;
grid = Drop[grid,-1]], ImageSize -> 100]
}],
Initialization :> {myCDF[Dynamic[var_], num_] :=
DynamicModule[{a = var[[num]]},
Panel@Column[{
Manipulator[Dynamic[a, (a = #; var[[num]] = #) &], {1, 10}],
Framed[
Dynamic@
Plot[Sinc[a x], {x, 0, 10}, ImageSize -> 300,
PlotLabel -> num],
Background -> White,
FrameStyle -> GrayLevel[0.7]]
}]
]}]
And this now works fine: