21
$\begingroup$

Take the following example for hiding/showing a notebook through a Checkbox:

x = True;
CreateDocument[{"hello"}, Visible -> Dynamic[x]];
Checkbox[Dynamic[x]]

It works correctly until the kernel is restarted, but not after.

The documentation explains that the variables in a DynamicModule are owned by the front end, not the kernel. So to make this piece of GUI resistant to kernel quits/crashes, I tried:

DynamicModule[
 {x = True},
 CreateDocument[{"hello"}, Visible -> Dynamic[x]];
 Checkbox[Dynamic[x]]
]

This one does not work though. Can someone explain why, and whether there is to use this DynamicModule variable outside the DynamicModule? Is this behaviour somehow related to the DynamicModuleParent option?


Update: As Brett Champion has pointed out in the comments below, there's a similar example in the documentation in the DynamicModule Wormholes section. This shows how to make it possible to access a particular DynamicModule's variable from another DynamicModule using the InheritScope -> True setting. However, my example I don't have a second DynamicModule in the newly created notebook, only a single Dynamic, so I am not sure how to connect the variables.

$\endgroup$
3
  • $\begingroup$ I think the last section (DynamicModule Wormholes) of the documentation covers this topic. $\endgroup$ Jan 28, 2012 at 19:09
  • $\begingroup$ @Brett You are right, I see a similar example now. But I do not have a DynamicModule in the window I create, so I am not sure where to put the InheritScope -> True this time. It won't work in either Dynamic[x] or in CreateDocument, nor is it fully documented to figure out the correct usage in this case. Do you have any hints? I'll update the main post in the meantime. $\endgroup$
    – Szabolcs
    Jan 28, 2012 at 19:18
  • $\begingroup$ Wait, you mean there is actually a documentation section called Wormholes? I thought that was a joke of some kind. $\endgroup$
    – Mr.Wizard
    Jan 28, 2012 at 19:19

5 Answers 5

19
$\begingroup$

What you want to do (make this piece of GUI resistant to kernel quits) can be achieved simply like this:

DynamicModule[
  {x = True, tag = Unique[StringJoin["g", ToString[
            $SessionID]]]}, CreateDocument[{"hello"},
     TaggingRules -> tag, Visible -> True];
   Checkbox[Dynamic[x,
       (x = #1; TrueQ[Select[Notebooks[],
                 CurrentValue[#1, TaggingRules] === tag & ] /.
               {b_NotebookObject} :> (CurrentValue[b,
                      Visible] =  ! CurrentValue[b,
                        Visible])]) & ]]]

The "wormhole" is a unique identitfier attached to TaggingRules.

$\endgroup$
3
  • $\begingroup$ Rolf, this is not just an answer to this question, it is an answer to the actual greater problem I wanted to solve! Thank you! Also related: mathematica.stackexchange.com/questions/267/… $\endgroup$
    – Szabolcs
    Jan 28, 2012 at 21:05
  • $\begingroup$ Szabalocs, you are welcome. I always liked TaggingRules. Very useful. $\endgroup$ Jan 28, 2012 at 21:08
  • 2
    $\begingroup$ A unique string is generated and added to the generated notebook (TaggingRules -> tag). This same tag is also coded into the Checkbox. I.e., the second argument of Dynamic is a function which finds that very notebook with such a tag. The joke is that this whole procedure is independent of whether the kernel is quit in between or not. What the OP wanted to do (use a dynamic variable outside DynamicModule) across kernel restarts is just not possible (with the specific limitations of the whole Dynamic business). However, the really nice feature of M- is that you can extend its limitations, with M $\endgroup$ Jan 29, 2012 at 18:15
20
$\begingroup$

The answer here is TaggingRules. Internally, the FE uses this all the time, and it solves many problems. And, BTW, the technique I'm about to describe is hinted at in the documentation for TaggingRules, so go look it up.

What we're looking for is something which doesn't rely on temporary kernel variables, crosses between notebooks, and doesn't require a displayed DynamicModule output to resolve. Ideally, we should be able to serialize its value across M-- sessions as well. DynamicModule wormholes almost solve the problem, but there's no way to access such a variable on the rhs of a notebook-level option, such as Visible->Dynamic[x].

Dynamic can access the current value of any option using CurrentValue. TaggingRules is an option which exists purely to hold metadata, so setting values on it will have no effect on any part of the system. If you happen to be using a palette, Mathematica will automatically remember the TaggingRules attached to that palette notebook between sessions, which means that you can store palette state without having write access to the palette file (this is how, for example, the Basic Math Assistant palette remembers which sections are open across sessions).

Here's a first cut at a TaggingRules solution...

CurrentValue[EvaluationNotebook[], {TaggingRules, "x"}] = True;
With[{nb = EvaluationNotebook[]},
  CreateDocument[{"hello"}, 
   Visible -> Dynamic[CurrentValue[nb, {TaggingRules, "x"}]]]];
Checkbox[Dynamic[
  CurrentValue[EvaluationNotebook[], {TaggingRules, "x"}]]]

Note that I used the string "x" rather than the symbol 'x'. This isn't absolutely necessary, but it does prevent unnecessary round trips to the kernel, conflicts with existing symbols, etc., so it's good practice. This is a good start, but let's improve on it. First, let's get rid of the first line. The requirement to initialize a value is annoying and requires extra code. CurrentValue has an undocumented (but don't worry, it's not going away) third argument which gives assigns a default value in case one doesn't exist. So, in a fresh notebook, try this code (ignore the red syntax coloring):

With[{nb = EvaluationNotebook[]},
  CreateDocument[{"hello"}, 
   Visible -> Dynamic[CurrentValue[nb, {TaggingRules, "x"}]]]];
Checkbox[Dynamic[
  CurrentValue[EvaluationNotebook[], {TaggingRules, "x"}, True]]]

Some consequences of this...

  • The document you're creating has a dependency on a specific NotebookObject which can't possibly survive the parent notebook being closed or Mathematica quitting.
  • The setting will now be saved with the parent notebook, which is probably a good thing. But when you open the notebook in the future, you'll have to re-create the child notebook.

There are further permutations which allow you some different behaviors. For example, if you want something which only lasts in this session of Mathematica, you could apply the TaggingRules to $FrontEndSession.

CreateDocument[{"hello"}, 
  Visible -> 
   Dynamic[CurrentValue[$FrontEndSession, {TaggingRules, "MyApp", "x"}]]];
Checkbox[Dynamic[
  CurrentValue[$FrontEndSession, {TaggingRules, "MyApp", "x"}, True]]]

This will clean itself up as soon as you quit Mathematica, as do all changes to $FrontEndSession, but it requires none of the tracking of NotebookObjects and all of the disadvantages that may entail. This is essentially a system-wide DynamicModule of sorts. Because it is global, note that I added an extra selector ("MyApp"). This effectively defines a namespace so that we don't collide with others wanting to do the same thing we just did.

$\endgroup$
1
  • $\begingroup$ I did discover tagging rules in the meantime and realized that they are a much better solution than any dynamic module, but I did not know that it's possible to set them for $FrontEndSession. This makes them even more convenient. $\endgroup$
    – Szabolcs
    Feb 1, 2012 at 15:47
6
$\begingroup$

The exhibited expression...

DynamicModule[
 {x = True},
 CreateDocument[{"hello"}, Visible -> Dynamic[x]];
 Checkbox[Dynamic[x]]
]

... does not work because the scope of a DynamicModule only extends to the definitions that comprise a visible screen area within a notebook. In this case, the relevant screen area is the checkbox created by Checkbox[Dynamic[x]]. Thus, x is visible only within that expression. The notebook window is created as an incidental side-effect and does not lie within the generated screen area.

Unfortunately, it is not (presently) possible to wrap a DynamicModule around an entire window. It can only wrap an area within a notebook.

It is possible to extend the scope of the DynamicModule into another notebook. Say, for example, we wanted to echo the checkbox in the new notebook. We could do this:

DynamicModule[{x = True}
, Checkbox[Dynamic[x]]
, Initialization :>
    CreateDocument[
      DynamicModule[{}, Checkbox[Dynamic[x]], InheritScope -> True]
    ]
]

This is what the Mathematica documentation refers to as DynamicModule wormhole. The inner DynamicModule is set up to inherit the scope of the outer DynamicModule. The inner DynamicModule cannot obtain a reference to the outer one until the generated screen area exists. That is why the CreateDocument has been moved to the Initialization option. If it had remained in its original location, as part of the content argument, the outer scope would not yet exist.

The Visible argument to CreateDocument can host a dynamic reference, but it does not create a screen area within a notebook. Therefore, there is no way to wrap a DynamicModule around it and worm our way into the scope of the checkbox.

Addendum

Incidentally, the original show/hide functionality that prompted this question can be achieved more simply thus:

DynamicModule[{x = True, nb = CreateDocument[{"hello"}]}
, Checkbox[Dynamic[x, (x = #; SetOptions[nb, Visible-> #])&]]
]

No wormholes required.

$\endgroup$
3
$\begingroup$

There are several things that seem to make this special case complicated:

  1. when using a Dynamic notebook option, there is nothing that could hold a surrounding DynamicModule to inherit scope of the original
  2. InheritScope will only work for evaluations triggered from within the DynamicModule to be inherited from.
  3. Once invisible, all dynamics within (or attatched to) the notebook won't be updated anymore by design, so there is no way to make it visible again.

I think that there are ways to probably overcome 1 and 2, but I don't see how 3 could be fixed. Here is something that demonstrates the problem, here the CreateDocument is triggered by a Button within the DynamicModule, so that InheritScope will work, and I'm using a dummy dynamic to avoid the notebook option problem:

DynamicModule[{x = True},
 Column[{
   Button["test",
    CreateDocument[{"hello", DynamicModule[{},
       Dynamic[CurrentValue[EvaluationNotebook[], Visible] = x],
       InheritScope -> True
       ]}
     ]
    ],
   Checkbox[Dynamic[x]]
   }]
 ]

once the notebook isn't visible anymore, the Dynamic will not update anymore and so the notebook stays invisible, although the connection still exists which you can check when you make the notebook visible with something like SetOptions[Notebooks[][[k]],Visible->True] where k is the number of the invisible notebook. There is mentioned DynamicModuleParent in the wormhole documentation which probably could be used to achieve a more direct connection between the DynamicModule-varialbe in the checkbox and the one in the notebook option. But I don't know any documentation about it and since 3 there seems no point in even trying to get that to work.

On the other hand there seems to be a relatively simple way to achieve what you described with a somewhat different approach, like here:

DynamicModule[{nb},
 nb = CreateDocument[{"hello"}];
 Checkbox[
  Dynamic[CurrentValue[nb, 
    Visible], (CurrentValue[nb, Visible] = #) &]]
 ]

so we don't store the visible setting but the notebook object. I wasn't sure whether that would really survive a kernel restart, but it obviously does. Of course the dynamic module will break completely when the containing notebook is opened in a new frontend session, but you probably could write some extra code in it's initialization to handle that case.

$\endgroup$
0
$\begingroup$

The reason is the context of the variable. On the second case the variable is local to the module and as such cannot be "seen" on the new notebook. A possible workaround might be to define a context for this variable and initialize it inside the module (a quick try didn't give the result you wanted).

$\endgroup$
3
  • $\begingroup$ It is not nearly as simple as you make it sound. Also, this is a DynamicModule, not a Module: don't confuse the two. If it were a Module, if would work, as "localization" in Mathematica is just renaming (I recommend reading Leonid's discussion here: mathematica.stackexchange.com/questions/559/… ) Second, you are confusing contexts with localization. A context is just a namespace in Mathematica, and are unrelated to the localization mechanism of either Module or Block. $\endgroup$
    – Szabolcs
    Jan 28, 2012 at 20:08
  • $\begingroup$ Finally, if you read the DynamicModule Wormholes link from my post, you'll see that DynamicModule variables can be shared between separate DynamicModules. The only point of using a DynamicModule here was to have the variable owned by the front end (so it can survive kernel restarts) $\endgroup$
    – Szabolcs
    Jan 28, 2012 at 20:09
  • $\begingroup$ @Szabolcs In betweens modules as you correctly say. Here, the value of switch, must be communicated between different notebooks. The context is a possible suggestion for finding a solution and not in relation to teh localization of the variables. "DynamicModule[{x,y,...},expr] represents an object which maintains the same local instance of the symbols x, y, ... in the course of all evaluations of Dynamic objects in expr. Symbols specified in a DynamicModule will by default have their values maintained even across Mathematica sessions."This is from help.I am aware of the wormhole effect. $\endgroup$
    – Spawn1701D
    Jan 28, 2012 at 21:32

Your Answer

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

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