14
$\begingroup$

Is it possible to cause a notebook to be hidden (Visible -> False) instead of closing it when pressing the close button in the title bar?

The Messages window seems to work this way. The Notebooks[] function will list it even when it is not visible (or it has been "closed"). Is this special behaviour, or is it possible to make any notebook window behave like this?


Motivation: As part of a user interface, I would like to gradually collect some information into a notebook (lets call it a "log-notebook"). The visibility of the log-notebook would be toggled with a button. This log-notebook should persist across kernel sessions, but not across front end sessions (it will never be saved).

$\endgroup$
2
  • 5
    $\begingroup$ I don't know about changing the close button functionality, but you could turn the close button off by removing "CloseBox" from list of WindowFrameElements of the log notebook and add a docked cell with a button that sets Visible->False when pressed. $\endgroup$
    – Heike
    Jan 19, 2012 at 11:07
  • $\begingroup$ @Heike I was thinking the same while out to lunch, it might be the simplest way. $\endgroup$
    – Szabolcs
    Jan 19, 2012 at 12:35

5 Answers 5

8
$\begingroup$

Not a perfect solution, but it works for some extent. It definitely needs some further foolproofing though.

nb = CreateDocument[{}, WindowTitle -> "Log", 
   WindowFrameElements -> {}, NotebookEventActions -> {
     "WindowClose" :> DialogReturn[],
     "EscapeKeyDown" :> (
       SetOptions[EvaluationNotebook[], Visible -> False];
       )}];

Button["Show", SetOptions[nb, Visible -> True]]

I used the hack Heike provided to remove the closebox from the window frame, since no matter how hard I tried to integrate the hiding functionality under "WindowClose" instead of "ExcapeKeyDown" without actually closing the window, it was always closed. Now the window is kept hidden if Esc is hit, and it is Alt+F4 that really terminates the window. If hidden, the provided "Show" button can be used to unhide the notebook.

$\endgroup$
2
  • $\begingroup$ +1: I didn't know about the WindowFrameElements option - that would have been useful! $\endgroup$
    – Simon
    Jan 19, 2012 at 12:05
  • $\begingroup$ @Simon: This was Heike's finding, not mine. $\endgroup$ Jan 19, 2012 at 12:11
4
$\begingroup$

I think that the Messages notebook might be a magic one. For example, If I run Notebooks[] in a new session, I see something like

{NotebookObject["Untitled-1"], NotebookObject["Messages"]} 

I can then investigate the messages notebook by

NotebookGet[Notebooks[][[-1]]]

It looks basically normal, except

  • it has no WindowTitle option, yet it has a title,
  • it has the "Delete all Messages" docked cell, although it has no DockedCells option.

That is they are both in their default states and so don't show up in the returned NotebookGet[] expression. (Although in the option inspector, a nonempty expression for DockedCells does show up, it does not have a checkmark next to it, so it is actually it's default state, which for normal notebooks should be {}. I'm not sure where it inherits this default from.)

And of course, the property that you noticed, "closing" the notebook merely sets the option
Visible -> False, and does not actually close the notebook.
Note that normal notebooks when made invisible do not show up in the Window menu. The message window always has an entry there. I could not figure out how to replicate these properties.


However, here is a work around.

Store all of your log information in the global variable $Log, e.g.,

$Log = {1, 2, 3};

And define

CreateLog[vis:(True|False):False, title_String:"LogNotebook"] :=
  CreateWindow[DocumentNotebook[Dynamic[Column@$Log]],
  Visible -> vis, WindowTitle -> title, Saveable -> False, 
  NotebookEventActions -> {"WindowClose" :> ($LogNotebook = CreateLog[])}]

Then you can create you log window using

$LogNotebook = CreateLog[True];

and everytime you close it, it is recreated, but in a hidden state. To show it again, you just need to run

SetOptions[$LogNotebook, Visible -> True]

To close it for real, just run

NotebookClose[$LogNotebook]

This can all be made to act and look nicer (but I've spent enough time on this already!)

$\endgroup$
2
  • $\begingroup$ Simon, you might be able to guess where this is going ;-) Stay tuned! $\endgroup$
    – Szabolcs
    Jan 19, 2012 at 12:34
  • $\begingroup$ @Szabolcs: Now that you point it out, I can. Btw, I used your palette in a Mma.SE question for the first just a second ago - was really handy, thanks! $\endgroup$
    – Simon
    Jan 19, 2012 at 12:35
3
$\begingroup$

This creates a palette with a button to show the log notebook, creating it if it doesn't exists yet. The log notebook comes with a button in a docked cell which hides it when pressed.

CreatePalette[
  DynamicModule[{},
    Button["Show log",
      If[Head[$lognb] =!= NotebookObject,
        $lognb = CreateWindow[DockedCells -> 
          Cell[BoxData[ToBoxes[
           Button["Close", SetOptions[ButtonNotebook[], Visible -> False]]]], 
           "DockedCell"
          ],
          WindowFrameElements -> {}, WindowTitle -> "Log", 
          Saveable -> False, Visible -> True
        ],
        SetOptions[$lognb, Visible -> True]
      ];
      SetSelectedNotebook[$lognb]
    ]
  ],
  WindowTitle -> "Show Log"
]

To write data to the log notebook you could then do something like

NotebookWrite[$lognb, Cell["Some text", "Text"]]
$\endgroup$
2
  • $\begingroup$ This is very nice. The remaining question is: how to detect that the notebook is already open if the kernel has quit in the meantime (I need to make this feel like a part of the Front End as much as possible, and definitely survive kernel restarts). Do we need to get the notebook name (NotebookTools`NotebookName) or is there a better way? Can we "tag" notebooks like cells? $\endgroup$
    – Szabolcs
    Jan 26, 2012 at 23:26
  • $\begingroup$ @Szabolcs Using NotebookTools`NotebookName as an identification for the log should work as long as the user doesn't decide to reset the window title of the log notebook or set the title of another notebook to the title of the log notebook. Maybe as a more robust option you could save a dummy log notebook to disk and use NotebookOpen to make sure you select the right notebook. $\endgroup$
    – Heike
    Jan 28, 2012 at 13:46
2
$\begingroup$

According to the documentation, NotebookEventActions allows you to specify an event handler for the "WindowClose" event, which will get triggered when a notebook window closes. But I haven't been able to figure out or find any way to prevent the close event from being sent to Mathematica's builtin handler. So as far as I know right now, it's not possible to actually prevent a notebook from closing.

What you can do (I think - I haven't tried) is register an event handler which will save the contents of the notebook when you close it.

nb = (* the notebook *)
SetOptions[nb, NotebookEventActions -> {"WindowClose" :> NotebookSave[nb]}]

Alternatively you could have your event handler copy the contents of the notebook object into a new notebook, possibly an invisible one, before the existing notebook gets closed. Or do something else to preserve the notebook contents. It doesn't seem like a particularly satisfactory solution to me, though.

I can understand why WRI might have incentive not to allow a notebook to prevent itself from being closed: there's a security risk for a resource denial attack. In other words, if a Mathematica notebook is eating up too much CPU/RAM, you need to be able to shut the process down. (A real general-purpose GUI programming language will allow you to customize the behavior of the close button, though.)

$\endgroup$
1
  • $\begingroup$ if you do LaunchKernels[4], then ParallelDo[Sin[i], {i, 0, 10^6., .01}] (say) and then close the notebook from which you executed this, the kernels don't stop running. So I am not sure closing notebooks is a security thing. Of course I could be missing lots of subtleties. $\endgroup$
    – acl
    Jan 19, 2012 at 12:12
2
$\begingroup$

As I also don't know a way how to avoid passing the window close event, here is another variant of the recreate notebook solution. It will survive kernel quits and probably could be combined with Rolfs TaggingRule trick to be more realiable than when just depending on the WindowTitle:

CreateDocument[{"some content"}, WindowTitle -> "Log", 
 Saveable -> False,
 NotebookEventActions -> {
   "WindowClose" :> (
     SetOptions[EvaluationNotebook[], Visible -> False];
     NotebookPut[NotebookGet[EvaluationNotebook[]]]
     )
   }]

When you need it, make it visible with something like this:

SetOptions[#, Visible -> True] & /@ 
 Select[Notebooks[], CurrentValue[#, WindowTitle] == "Log" &]
$\endgroup$

Your Answer

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

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