5
$\begingroup$

Update:

I'll restate the question in hopefully more terse and clearer terms.

To illustrate the issue, run this and save the resulting notebook:

CreateDocument[
  DynamicModule[
   { ultimateAnswer = 42,
    x = False},
   Pane@Row@{
      Checkbox[Dynamic[x]], " Notebook modified ",Checkbox[Dynamic[Refresh["ModifiedInMemory" /. NotebookInformation[], UpdateInterval -> 0.1]]]}]
  , WindowSize -> {320, 80}, WindowElements -> {}, WindowToolbars -> {}, WindowFrameElements -> {"CloseBox", "DocumentIcon", "ResizeArea"}];

saved

Add a cell and close the notebook: modified

Reopen it, play a few with the checkbox: re-modified

Lastly, be sure the checkbox is on, and close the notebook.

Note that there's no external or internal clue about the notebook saved state. What will the Front End do? What should it do? Shouldn't the checkbox remain checked when you re-open the notebook?

Primary question: Should the Front End close the window without any warning the notebook has been modified?

Note that in this contrived example, the flag var is there to illustrate the issue, but perfectly could also be another near it with the result of a 7.2 billion years computation.

Secondary-- nice-to-have,-- question: in case this issue bothers you, how would you resolve the problem in a nice, elegant, concise Mathematica way?

Bonus question: you classify this as

  1. you're-very-very-picky-and-a-little-paranoid, irrelevant
  2. very minor
  3. mild curiosity
  4. serious

Now seriously, I've actually been bitten by this, and have spent significant time looking for a solution. If you attempt any involved GUI work, it's probable you won't notice this during development because the notebook morphs a lot. Only at the end, you clean up nice to discover all your persistent changes aren't being saved. You must then notify users that they have to save even though the window says nothing has changed.


Old post:

The documentation of DynamicModule states:

Symbols specified in a DynamicModule will by default have their values maintained even across Mathematica sessions.

So, any change in DynamicValues vars able to persist should be reflected somehow. In particular, notebook saved state should be modified in this case, as if the user had created a new cell.

As a contrived example, evaluate this:

CreateDocument[
  DynamicModule[{x = False},
   Pane@Row@{
      Checkbox[Dynamic[x]],
       " Notebook modified ",
      Checkbox[
       Dynamic[Refresh["ModifiedInMemory" /. NotebookInformation[], UpdateInterval -> 0.1]]]}]
  , WindowSize -> {230, 80}, WindowElements -> {}, WindowToolbars -> {},
  WindowFrameElements -> {"CloseBox", "DocumentIcon", "ResizeArea"}];

Observe the resulting notebook, in particular the close button and document icon. The first Checkbox is backed by a DynamicModule var, second one monitors if there're unsaved changes.

Toying with DynamicModule backed UI

Adding a new cell (1st-2nd images) triggers the normal UI clues of unsaved data: in OSX, the filled close button and the grayed-out document icon.
Changing the checkbox (3rd-4th images), though, doesn't change the UI nor, more importantly, the internal document state. In fact, you can close the document without FE prompting to save it and observe subsequently that the first checkbox doesn't remember its setting. If you want UI persistence, you must force the saving. Other widgets behave similarly. The culprit here is surely DynamicModule, not the widgets or Dynamic.

This is probably well known, and I see two explanations here. One, a bug/glitch (tested on Mathematica 9.0.1/OSX 10.9.2); two, this behavior is by design or tough to implement by the FE. Note that window resizing or zoom doesn't modify the notebook saved state also, even though the document metadata has changed.

My take is the second case seems somewhat inconsistent from a UX point of view. The old controversy of data vs metadata remains unresolved and impractical, but in my opinion DynamicModule vars should be considered full data citizens. DynamicModule raison d'être is session persistence. Some the strong points of Mathematica are the homoiconic, literate programming aspects, the unholy mix of code/data/metadata/application/IDE/document, and arguably DynamicModule vars are so data as any user-entered expression. If you make a Cell/Show Expression, it's easily observable that the DynamicModule vars are being modified and do change the cell expression:

Cell[BoxData[
 DynamicModuleBox[{$\
CellContext`x$$ = True}, 
  PaneBox[
   TemplateBox[{CheckboxBox[
  ...

Whether it's a bug or designed behaviour, I believe WRI won't change this in the near future. But I like my notebooks reflecting DynamicModule state changes. As a newbie, I see two quick'n'dirty very unsatisfactory solutions:

  • Track the vars and save programmatically the document,

    Checkbox[Dynamic[x,(x=#;FrontEndTokenExecute["Save"])&]]
    

    or

    Checkbox[Dynamic[x,(x=#;NotebookSave[])&]]
    

    But, it's the user who should choose how and when to save the document, not the application.

  • Create/delete ghost content, e.g.,

    With[{nb = EvaluationNotebook[], scr = AutoScroll -> False},
        SelectionMove[nb, After, Cell, scr];
        NotebookWrite[nb, Cell["", "Text"], All, scr];
        NotebookDelete[nb, scr]];
    

    Ugly, and ungainly, and error-prone, not only for the fake changes, but for the selection resetting. I haven't found a way to fully restore the selection state (and this is something the FE does well: dynamic interactivity usually respects the selection point.)

My question, then: is there a more elegant (concise, convenient) way to mark the notebook as changed so both UI and internal notebook state reflect the fact?

$\endgroup$
4
  • $\begingroup$ I believe it's more complicated. Try single Checkbox[Dynamic@x] and Checkbox[x] which tells us that it's not about DynamicModuleVariables but Dynamic cells in general. $\endgroup$
    – Kuba
    Commented May 4, 2014 at 15:12
  • $\begingroup$ Moreover, your Checkbox with save option will be useless if you change x somewhere else. You way is only a response to interactive change via checkbox. More general aproach would be to add some up values with x and Set but stil ugly. $\endgroup$
    – Kuba
    Commented May 4, 2014 at 15:20
  • $\begingroup$ @Kuba Yeah, general Dynamic interactivity exhibits the same behaviour, for sure. But the founding reason of DynamicModule existence is (per the documentation) "Symbols specified in a DynamicModule will by default have their values maintained even across Mathematica sessions." So, I'm only interested in DynamicModule symbols, and being notified that a state that should be persistent has changed. $\endgroup$
    – Fallible
    Commented May 4, 2014 at 15:30
  • $\begingroup$ @Kuba, You're right. Done, thanks. $\endgroup$
    – Fallible
    Commented May 4, 2014 at 15:59

1 Answer 1

2
$\begingroup$

I classify your question as "Serious" thus, here is my answer. In many cases I have found myself in situations where a "canonical solution" does not seem to exist for a given problem found while creating Mathematica solutions. In those cases I tend to create a reasonable enough solution. When I read your question I remembered that a couple of years ago I had exactly the same problem and at that time I didn't provide a good solution to the problem. Thus, for our benefit, the code below I think solves our problem in an acceptably elegant way. It is indeed hacky but while we get a better solution we can use this one.

CreateDocument[{
ExpressionCell[
Panel@
DynamicModule[
{x=False, sensiblyUpdateNotebookState,showCellLabelQ=False}
,
sensiblyUpdateNotebookState[]:=Block[{},
NotebookFind[EvaluationNotebook[],"MyApp",Next,CellTags];
SetOptions[NotebookSelection[EvaluationNotebook[]],ShowCellLabel->Not[showCellLabelQ]];
SetOptions[NotebookSelection[EvaluationNotebook[]],ShowCellLabel->showCellLabelQ]
];
Pane@
Grid[
{{
"My control"
,
Checkbox[Dynamic[x,
(x=#;
sensiblyUpdateNotebookState[]
)&
]
]
}
}
,ItemSize->Full
,Alignment->Left
]
]
,"Output"
,ShowCellLabel->False
,CellTags->"MyApp"
,CellLabel->""
]
}
,WindowSize->{350,80}
,WindowElements->{}
,WindowToolbars->{}
,WindowFrameElements->{"CloseBox","DocumentIcon","ResizeArea"}
,Selectable->False
,Deployed->True
,ShowCellBracket->False
]
$\endgroup$

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.