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}
]
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}
]
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}
]
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
]
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
]
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:
Manipulate
are localized. You need mark them as global to make them interacting with outside code. Look onto theLocalizeVariables
$\endgroup$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$