4
$\begingroup$

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.

$\endgroup$
6
  • 1
    $\begingroup$ Don't worry about highlighting if you know what you are doing. This question seems to be a duplicate of: Internal DM steals scope of external DM but the accepted answer doesn't address the issue. Shortly, either make up strange names for local variables or make sure they are created in unique and not exported context, like in my answer there 132269 $\endgroup$
    – Kuba
    Commented Apr 26, 2017 at 16:30
  • $\begingroup$ Do you agree this is the same issue? $\endgroup$
    – Kuba
    Commented Apr 26, 2017 at 19:02
  • $\begingroup$ Those are some very complicated examples in that thread. For me they create more questions. I have edited my post with some specific questions that I don't feel are answered in that thread. $\endgroup$
    – lmnop1234
    Commented Apr 26, 2017 at 22:19
  • $\begingroup$ Your edit can be answered in a preface to the asnwer in: mathematica.stackexchange.com/a/126426/5478 there are linked threads about automatic renaming too. The topic is tough but worth being understood. $\endgroup$
    – Kuba
    Commented Apr 26, 2017 at 22:28
  • $\begingroup$ Yes! that answers the question, thanks. So it seems RuleDelayed is the fellow who is responsible for renaming local variables. It automatically goes through and fixes any possible scoping conflicts by renaming variables as needed. However, it seems that although this is implemented for Module, it is not implemented for DynamicModule, which is why my example works. Strange that it isn't implemented for DynamicModule. I'm also surprised this isn't a more common problem. $\endgroup$
    – lmnop1234
    Commented Apr 27, 2017 at 2:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.