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 myCDFadded, 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:

enter image description here

Tags:

Cdf Format