2
$\begingroup$

I want to have dynamic controls that depend on other controls. For example, the code

Manipulate[use,
 Dynamic@Grid[{
    {"Use slider", Control[{{use, False, ""}, {True, False}}]},
    If[use,
     {"Slider", Control[{{sli, 10, ""}, 1, 100}]},
     Unevaluated[Sequence[]]]
    }, Alignment -> {{Right, Left}, Automatic}]]

allows me to have the slider control only when the control checkbox is ticked

enter image description here

enter image description here

Since I have a huge control grid, I was wondering if it's possible to define the controls outside Manipulate, in a different cell, and call them from within Manipulate. I tried defining

cont =
  Grid[{
     {"Use slider", Control[{{use, False, ""}, {True, False}}]},
     If[use,
      {"Slider", Control[{{sli, 10, ""}, 1, 100}]},
      Unevaluated[Sequence[]]]
     }, Alignment -> {{Right, Left}, Automatic}];

and then simply write

Manipulate[use, Dynamic@cont]

However, even though I can update the box, the slider remains hidden. Alternatively, I tried to set cont = Dynamic@Grid ... but, in this case, when I check the box, the slider appears only for a brief moment. It flashes and then disappears. The box remains unticked in this case.

It must be related to how Dynamic works but I'm not sure how to solve this.

Any ideas?

Edit: Following Lukas Lang's answer below, I wrote a Manipulate version for the case of controls with customized names. For example, with two sliders we have

Attributes[cont] = {HoldAll};
    cont[vis_, sli1_, sli2_] :=
 Dynamic@Grid[{
    {"Use slider", Checkbox@Dynamic@vis},
    If[vis,
     Sequence @@ {
       {"Slider 1", Slider[Dynamic@sli1, {0, 100}]},
       {"Slider 2", Slider[Dynamic@sli2, {0, 50}]}},
     Nothing]},
   Alignment -> {{Right, Left}, Automatic}]

and, using Manipulate and removing the panels, we may write

Manipulate[Dynamic[{vis1, sli1, sli2}],
 Column@{Column@
    {cont[vis1, sli1, sli2]}},
 FrameMargins -> 0,
 Initialization :> ({vis1 = False, sli1 = 20, sli2 = 20})]
$\endgroup$
2
  • 1
    $\begingroup$ all dynamic variables inside the Manipulate are localized. You need mark them as global to make them interacting with outside code. Look onto the LocalizeVariables $\endgroup$
    – Rom38
    Jun 24 at 14:30
  • $\begingroup$ I've set LocalizeVariables to false, but no change. I also tried defining the outside controls as a function with no arguments, but that also doesn't seem to do it. What do you suggest? $\endgroup$
    – sam wolfe
    Jun 24 at 14:39
2
$\begingroup$

As noted in the comments, Manipulate by default localizes the variables. But instead of trying to disable the localization, I would suggest you use custom controls to achieve what you want:

icont[Dynamic@var_] := Dynamic@Grid[
   {
    {"Use slider", Checkbox@Dynamic@var[[1]]},
    If[var[[1]], {"Slider", Slider[Dynamic@var[[2]], {0, 100}]}, Nothing]
    }, Alignment -> {{Right, Left}, Automatic}
   ]
cont = icont[#] &;

This defines a function cont that will produce a control of the desired type when a variable of the form Dynamic[var] is passed to it (which is what Manipulate does). The second definition is needed since Manipulate only accepts expressions with head Function for custom controls, so we simply return one that calls the actual code.

You can then use this inside Manipulate:

Manipulate[
 sli,
 {{sli, {True, 10}}, cont}
 ]

enter image description here

Note that I set the variable to a two-element list that contains both the checkbox value and the slider value. This is because Manipulate only provides us with a single variable per control, so it needs to perform double-duty.

If you don't care about the checkbox state outside of your custom control, you can do the following:

icont[Dynamic@var_] := DynamicModule[{vis = True}, Dynamic@Grid[
    {
     {"Use slider", Checkbox@Dynamic@vis},
     If[vis, {"Slider", Slider[Dynamic@var, {0, 100}]}, Nothing]
     }, Alignment -> {{Right, Left}, Automatic}
    ]
  ]
cont = icont[##] &;

Manipulate[
 sli,
 {{sli, {True, 10}}, cont}
 ]

enter image description here

Here, we use a DynamicModule to localize the visibility toggle manually.

You can also take advantage of the fact that Manipulate will pass the limits to the custom function if they are given:

icont[Dynamic@var_, lims_ : {0, 100}] := Dynamic@Grid[
   {
    {"Use slider", Checkbox@Dynamic@var[[1]]},
    If[var[[1]], {"Slider", Slider[Dynamic@var[[2]], lims]}, Nothing]
    }, Alignment -> {{Right, Left}, Automatic}
   ]
cont = icont[##] &;

Manipulate[
 sli,
 {{sli, {True, 10}}, 0, 200, cont}
 ]

enter image description here

If you need even more control, I would suggest avoiding Manipulate completely and to go directly with DynamicModule:

Attributes[cont] = {HoldAll};
cont[vis_, sli_, lims_ : {0, 100}] := Dynamic@Grid[
   {
    {"Use slider", Checkbox@Dynamic@vis},
    If[vis, {"Slider", Slider[Dynamic@sli, {0, 100}]}, Nothing]},
   Alignment -> {{Right, Left}, Automatic}
   ]

The above version of cont can be whatever we want since we don't have to make it compatible with Manipulate, and so we can then easily pass several variables and settins. To use it, we build our own "UI":

DynamicModule[
 {
  vis1 = False, sli1 = 20,
  vis2 = True, sli2 = 30
  },
 Panel[
  Column@
   {
    Column@{
      cont[vis1, sli1],
      cont[vis2, sli2, {0, 200}]
      },
    Panel@Dynamic[
      {vis1, vis2, sli1, sli2}
      ]
    }
  ],
 FrameMargins -> 0
 ]

enter image description here

If you really want to use Manipulate (e.g. because you want "normal" controls as well as your custom ones), you can use the following. As a baseline, I use the control from the updated question:

Attributes[cont] = {HoldAll};
cont[vis_, sli1_, sli2_] := Dynamic@Grid[{
  {"Use slider", Checkbox@Dynamic@vis}, 
  If[vis, 
    Sequence @@ {
      {"Slider 1", Slider[Dynamic@sli1, {0, 100}]}, 
      {"Slider 2", Slider[Dynamic@sli2, {0, 50}]}
    }, Nothing]
  }, Alignment -> {{Right, Left}, Automatic}
]

The Manipulate itself would look like this:

Manipulate[
 {vis1, sli1, sli2},
 {{vis1, False}, None},
 {{sli1, 20}, None},
 {{sli2, 20}, None},
 Item@cont[vis1, sli1, sli2],
 FrameMargins -> 0
 ]

enter image description here

While in principle, the solution in the question "works", it's a bit pointless in the sense that manipulate does nothing but provide some styling. This is because Manipulate does not know about the variables at all, so e.g. the bookmarks and the initialization after the kernel was killed will not work. In contrast, the above solution explicitly tells Manipulate about the variables and their initial values, and tells it to not display a control for them. We are then free to add a custom expression as control, in the form Item[control].

Sidenote: As you can see, the Dynamic around the first argument of Manipulate is not needed, since Manipulate effectively wraps the first argument in Dynamic anyway.

If you find the above syntax distracting, you can write your own wrapper for Manipulate that declares all the dummy variables based on a list you provide:

Attributes[customManipulate] = {HoldAll};
customManipulate[expr_, {vars___}, controls___] :=
 Replace[
   Hold[vars],
   {
    HoldPattern[var_ = init_] :> {{var, init}, None},
    var_ :> {var, None}
    },
   1
   ] /. Hold[v___] :> Manipulate[
    expr,
    v,
    controls
    ]

You use it as:

customManipulate[
 {vis1, sli1, sli2},
 {vis1 = False, sli1 = 20, sli2 = 20},
 Item@cont[vis1, sli1, sli2],
 FrameMargins -> 0
 ]
(* same result as above *)

Note however that the coloring of the variable names no longer works like this:

enter image description here

$\endgroup$
9
  • $\begingroup$ Is it possible to specify each of the controls instead of using var? My code has lots of controls and I'm afraid that using a list would be confusing after a while. Great answer nonetheless! $\endgroup$
    – sam wolfe
    Jun 24 at 19:04
  • $\begingroup$ @samwolfe I'm not sure I understand what you mean. Are you asking for a way to just give Manipulate a list of things that should act as controls, without having Manipulate know about the variables? $\endgroup$
    – Lukas Lang
    Jun 24 at 19:08
  • $\begingroup$ As far as I understood, you are listing the variables by setting var[[1]], var[[2]] and so on. Instead of using these, I would like them to have custom names, like use and sli in my code. Your answer does what I want, but I specifically want to give Manipulate a list of controls which initial values and "variable names" I can edit within cont (or icont, in your case). I think writing icont[Dynamic@use_,Dynamic@sli_] could work, but your initial values are still inside Manipulate. $\endgroup$
    – sam wolfe
    Jun 24 at 20:01
  • $\begingroup$ Essentially, how do I rename the var entries so that I can more easily track the variables? $\endgroup$
    – sam wolfe
    Jun 24 at 21:16
  • $\begingroup$ @samwolfe The variables are actually always created in pairs, so e.g. Manipulate[{sli1,sli2},{{sli1, {True, 10}}, cont},{{sli2, {True, 10}}, cont}] would result in variables sli1[[1]],sli1[[2]] for the first control, and sli2[[1]],sli2[[2]] for the second control. So if you can live with the visibility toggle and the slider having a common name, I'd suggest you keep it like that. If you really need completely separate names for the two variables for the slider and checkbox, let me know, then I'll write up one approach that comes to mind (even if it's not the nicest) $\endgroup$
    – Lukas Lang
    Jun 24 at 21:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.