7
$\begingroup$

I need to give a notebook a few simple state variables (counters) that will be saved with the notebook, persist across kernel restarts, etc. From what I've read, using variables defined within a DynamicModule is the appropriate strategy, especially since I'm going to have a cell (docked at the top of the notebook) that displays the current value of those variables.

My problem is that these variables should NOT be changed via UI widgets, by the user. Instead, I need to change them programmatically, from code that's resident in a package the notebook loads. I can't figure out how to do that. I've read the documentation on DynamicModule "Wormholes", but that only discusses state variables that are changed by UI widgets (sliders, buttons, etc.). I've also read this post, which might be relevant, but I don't grok it well enough to see how.

A different approach might possibly be to store the state variables in TaggingRules associated with the notebook, and have the display area query those within a Dynamic[] function... but that seems like it's well outside the intended purpose of Dynamic, and the subtleties involved -- wrapping Dynamic around a TaggingRule value query, probably with some string-to-number coercion involved -- daunts me.

So: Can I programmatically alter the values of variables within a DynamicModule from outside that module? Or is there a different/better way to proceed?

Edit: What makes my question different from others I've found on this site, and other discussions of DynamicModule wormholes, is that they all use a UI widget to change the state variable. I'm specifically asking how to do it programmatically.

Edit 2: As the discussion here has evolved, I realize that the question I originally posted did not in fact capture my ultimate aim. Therefore, the perfectly good answers below don't answer the question stated in the title, although they do match the intent communicated in the description. Therefore, I've changed the title.

$\endgroup$
4
  • $\begingroup$ Sorry, I didn't pay attention. So you have seen that post already. $\endgroup$
    – Szabolcs
    Commented Jul 15, 2015 at 21:28
  • $\begingroup$ No worries. I made a mistake once, too. ;-) $\endgroup$
    – ibeatty
    Commented Jul 15, 2015 at 21:29
  • 1
    $\begingroup$ BTW you don't need string-to-number conversions with TaggingRules ... you can store non-string expressions in them. You can change TaggingRules programmatically, without using any GUI widgets, you just need to have a handle to the notebook that they're associated with (EvaluationNotebook[]?) $\endgroup$
    – Szabolcs
    Commented Jul 15, 2015 at 21:35
  • $\begingroup$ My initial fear of using TaggingRules primarily stemmed from having to wrap a Dynamic[] around a rule-extraction rather than a variable, but as I spend more time reading up on them, I'm starting to think that may be possible. Time to experiment… $\endgroup$
    – ibeatty
    Commented Jul 15, 2015 at 21:38

2 Answers 2

5
$\begingroup$

After commenters and answerers pushed me to investigate TaggingRules rather than DynamicModule as a strategy, I've worked out a solution. After the fact, it's actually embarrassingly simple. In the package that supports this notebook (or, equivalently, in notebook initialization cells), I define helper functions:

SetAttributes[getCounter,Listable];
getCounter[counterName_String]:=
  Module[{value},value=CurrentValue[EvaluationNotebook[],
    {TaggingRules,"persistentCounters",counterName}];
    If[value===Inherited,initializeCounter[counterName],value]
  ];

initializeCounter[counterName_String]:=
  CurrentValue[EvaluationNotebook[],
    {TaggingRules,"persistentCounters",counterName}]=0;

incrementCounter[counterName_String]:=
  CurrentValue[EvaluationNotebook[],
    {TaggingRules,"persistentCounters",counterName}
  ]=1+getCounter[counterName];

deleteAllCounters[]:=
  CurrentValue[EvaluationNotebook[],
    {TaggingRules,"persistentCounters"}]=None;

I can then display any counter value anywhere (such as in a docked cell), like this:

Dynamic[getCounter[{"one", "two", "three"}]]

(* ==> {0, 0, 0} *)

All counters are created and initialized to zero when they are first checked. I can then increment any counter value like this:

incrementCounter["one"];

and the Dynamic display will automatically update. If the notebook is closed and reopened, and/or the kernel is quit, the counter values persist (once the initialization cells have been evaluated or the relevant package loaded, of course).

For my purposes, this seems entirely adequate. No wormholes necessary! Thanks, @Szabolcs.

$\endgroup$
1
$\begingroup$

Some useful information are in this post from Szabolcs, however your question is a little different. Indeed, actually you only need to save an information (the counter value) inside a notebook and preserve it across frontend sessions. For such cases TaggingRules seems to be the best solution. DynamicModule could eventually be another option to save information across kernels sessions, but I have not been able to achieve the result with it. So, defined the use of TagginRules as metainformation keeper, what we have to focus on is how to change the value assigned to TagginRules before the notebook is saved, and without any widget interation (button, slider or anything else). I tried using something like TaggingRules->Dynamic[mycounter] with no success. Finally, I moved to another idea: have a Dynamic object that sets the TaggingRules for the EvaluationNotebook each time the counter changes the value. I used DynamicWrapper to trigger the SetOptions evaluation as soon as the counter is updated. Here is the code

SetOptions[InputNotebook[], DockedCells -> Cell[BoxData@ToBoxes[
     DynamicModule[{}, 
      Row[{DynamicWrapper["my counter value is ", 
         SetOptions[InputNotebook[], 
          TaggingRules -> {"counter" -> mycounter}], 
         TrackedSymbols :> {mycounter}], Dynamic[mycounter]}], 
      Initialization :> {mycounter = 
         CurrentValue[InputNotebook[], {TaggingRules, "counter"}]}]]]]

I used DynamicModule just to have the possibility to set Initialization, so to read the current value saved into the notebook as soon as the notebook is opened. After evaluate the above code, try changing the value of variable mycounter and see is is updated in the DockedCell. Close the notebook and exit Mathematica and when reopen the notebook the counter is still there. I hope this is what you was looking for.

$\endgroup$
6
  • $\begingroup$ of course I used the global variable mycounter, that should be preserved from accidental changes, so you can write more accurate code according to what you actually need to do whit counters in your notebook $\endgroup$
    – bobknight
    Commented Jul 16, 2015 at 7:45
  • $\begingroup$ Hmm, interesting. I suspect, however, that you are solving a more complicated problem than I intended. I don't need to have the TaggingRules value follow the counter; I can have the counter follow the TaggingRules value. That seems to allow a simpler solution. (Or am I missing a subtlety?) $\endgroup$
    – ibeatty
    Commented Jul 17, 2015 at 14:52
  • $\begingroup$ @ibeatty it's exactly what I have done. In order to allow the counter to start from a value saved in the last sessions, I used to save the counter changes (any new value) in the TagginRules. So, when you close the notebook (and perhaps the Mathematica) the value is automatically save inside the notebook. Then, the next time you open the notebook the counter will "follow" the TagginRules. Isn't what you are asking for? $\endgroup$
    – bobknight
    Commented Jul 17, 2015 at 15:16
  • $\begingroup$ Why use DynamicWrapper to make the counter update the TaggingRules value when it changes? Why not instead use Dynamic[ CurrentValue[ EvaluationNotebook[], {TaggingRules, counterName}]] to make TaggingRules changes update the counter? Then one can change the TaggingRules value whenever and however one wishes. $\endgroup$
    – ibeatty
    Commented Jul 17, 2015 at 15:23
  • $\begingroup$ My idea was to manage TaggingRules automatically and independently from the counter. I thought the counter was something managed by a package and not directly involved into the management of TaggingRules. So, my DockedCell works in this direction, it tracks the counter movements, and save them inside the notebook. Your code does more or less the same things, but you have to specifically call functions to initialize, increase, read and delete the counter and that function have to manage TaggingRules as well. This is just another way to do the same things, I guess. $\endgroup$
    – bobknight
    Commented Jul 17, 2015 at 15:40

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.