I am trying to build a custom control that is just a combination of a group of smaller controls that combine to give the final value, but with the final value only updating when the controls stop moving.
Specifically, I want to build something like this.
ClearAll[SomeControl]
SetAttributes[SomeControl, HoldFirst]
SomeControl[var_] := DynamicModule[{temp1, temp2},
Column[{
Slider[Dynamic[temp1], {1, 2}],
Slider[Dynamic[temp2], {1, 2}],
Dynamic[
ControlActive[, var = temp1 + temp2; count++]; {temp1,
temp2, var, $ControlActiveSetting, count},
TrackedSymbols :> {temp1, temp2}]
}]
]
count=1;
SomeControl[var]
(*slider*)
(*slider*)
{temp1,temp2,var, $ControlActiveSetting, count}
SomeControl[var]
creates a controller object that can be manipulated to edit the value of var
. Nothing happens to var
, but when manipulation stops, var
should be updated to temp1+temp2
.
Typically the var = temp1+temp2
line will be much more complicated and processor intensive. I included the count++
to ensure that code only gets run once when the controls are moved.
This code words fine: The output updates correctly with temp1
, temp2
, and $ControlActiveSetting
updating continuously, and var
and count
only updating when the manipulation stops.
However, if I run SomeControl[temp1]
then it breaks because the var->temp1
is inserted into the DynamicModule
before the DynamicModule
is evaluated. I fixed the problem by wrapping in a Module
:
ClearAll[SomeControl]
SetAttributes[SomeControl, HoldFirst]
SomeControl[var_] :=
Module[{temp1, temp2}, DynamicModule[{temp1, temp2},
(*SAME AS BEFORE*)
]]
count = 1;
SomeControl[temp1]
This fixes the problem, but the Mathematica front end shows all red on temp1
and temp2
, indicating that Mathematica doesn't think I am doing the right thing. Sticking ControlActive[, var = temp1 + temp2; count++]
in there the way I did, and relying on the updating of the control image to update var
, seems a bit strange to me too. Is there a more correct way to do this?
EDIT
According to Kuba a similar question has been answered here. The problems and solutions in that link seem very complicated, so let me rephrase some specific questions that aren't explicitly covered there:
I just realized I have no idea why my solution works. i.e.
ClearAll[SomeControl]
SetAttributes[SomeControl, HoldFirst]
SomeControl[var_] := Module[{var1,var2},DynamicModules[{var1,var2},
(*insert code here*)
]]
SomeControl[var1]
At first glance I assumed it was because Module was acting first, and modifying all the values of var1, and var2 to be temporary names. After that, var ->var1 was being injected into the code block. But, this is not ordinary behavior. For example the following code gives
ClearAll[SomeControl, temp1]
SetAttributes[SomeControl, HoldFirst]
SomeControl[var_] := Replace[var, temp1 -> "variable replaced"]
SomeControl[temp1]
SomeControl[x]
(*"variable replaced"*)
(*x*)
So in the normal case, when SomeControl[temp1] is called, var->temp1 is injected into the RHS before Replace[var, temp1 -> "variable replaced"] is called. But when I run
ClearAll[SomeControl, temp1]
SetAttributes[SomeControl, HoldFirst]
SomeControl[var_] := Module[{temp1}, DynamicModule[{temp1},
Print[temp1];
DynamicModule[{temp1}, Print[temp1]];
Print[var];
]]
SomeControl[temp1]
I get
(*temp1$372013$372014*)
(*temp1$372013$372014$372015*)
(*temp1*)
Clearly, Module has been evaluated before var->temp1 is injected into the RHS. Is this kind of special behavior for when Module is associated with the SetDelayed operator? Or, am I completely misunderstanding something here?
2) This leads me to another question about how DynamicModule works at all. I was trying to inject the name of the global variable into a DynamicModule after it had evaluated (so that the global variable wouldn't be localized by DynamicModule), but I am getting extremely strange results. Take for example the following code
ClearAll[SomeControl, temp1, varlocal]
SetAttributes[SomeControl, HoldFirst]
SomeControl[var_] := DynamicModule[{temp1},
Print[temp1];
Print[varlocal];
varlocal
] /. varlocal :> var
(*temp1$376446*)
(*varlocal*)
(*temp1$$*)
I don't understand why this is happening. It seems like DynamicModule runs through the whole code, evaluates all the lines but remains wrapped around varlocal, then after /. varlocal :> var
is evaluated, DynamicModule is run again, and the variable localized again (if var=temp1). Is there any way to inject the global variable into the DynamicModule after it has localized all the variables?
EDIT2
Ok it seems I was completely misunderstanding something. It seems that Module and DynamicModule are not responsible for localizing the local variables at all. It is in fact RuleDelayed that checks for possible scoping conflicts and renames variables as needed. This is outlined in this post that Kuba pointed me too.
It seems that RuleDelayed fixes variable conflicts on things like With and Module, but it is not implemented for DynamicModule. This is why wrapping the DynamicModule in a Module fixes the problem.