Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

What are the best (most robust and most convenient) ways to create palettes that can be installed permanently (using Palettes -> Install Palette...) and are safe to use? I'd be interested in how other people have done this in the past to learn more about idiomatic front end programming.

I put some code illustrating the pattern I am using now at the end of this post. I'd appreciate some comments on it.

Notes and requirements:

  1. The palette should always work, regardless of whether the kernel has been quit or an evaluation is running.

  2. The palette may have more than one button which share some code between them.

  3. It must not pollute the Global` context or change the kernel state in a way that might break something unexpectedly.

  4. I'm looking for an easy way to define palettes (minimal boilerplate code and extra work)

  5. It would be nice (non-essential) if several versions of the palette could coexist independently (my current approach doesn't have this because it uses its own context to hide its function definition, but everything in this context is shared)

  6. It would also be nice (non-essential) to integrate documentation in an easy way (help button bringing it up maybe?)


My current approach is illustrated below. It "localizes" its symbols by putting them in a separate context, and uses DynamicModule to ensure that all the definitions are done before any button code is run.

SetAttributes[paletteButton, HoldAll]
paletteButton[name_, tooltip_, func_, opt : OptionsPattern[]] := 
 Tooltip[Button[name, Unevaluated[func], Appearance -> "Palette", 
   opt], tooltip, TooltipDelay -> Automatic]

Begin["SomePalette`"];

PaletteNotebook[
 DynamicModule[{},
  Column[{
    paletteButton["One", "Button one", function[1]],
    paletteButton["Two", "Button two", function[2]]
    }],

  Initialization :> (
    function[x_] := MessageDialog[x]
    )
  ],
 WindowTitle -> "Some Palette"
 ]

End[];
share|improve this question
3  
BTW I always found it odd that you there isn't an 'uninstall palette' menu item. – Sjoerd C. de Vries Jan 18 '12 at 15:54
2  
@Sjoerd It's more difficult to implement, but I agree, it should be present. Palettes usually go into FileNameJoin[{$UserBaseDirectory, "SystemFiles", "FrontEnd", "Palettes"}] – Szabolcs Jan 18 '12 at 16:02
    
Note: this is a question asking about "best-practices" / "what is idiomatic" and what different ways are there to achieve this. I already have a solution which satisfies most requirements I have, but it would be tremendously helpful to at least hear from others who have made palettes in the past. I am not sure if these types of questions will be welcome on the site or not. – Szabolcs Jan 18 '12 at 18:02
    
I saw the title of this question and thought: "This sounds like a good question for Szabolcs!" I think I learned about non-trivial palettes from your web site. :-) – Mr.Wizard Jan 22 '12 at 7:01
    
@Mr.Wizard Unfortunately not. I only made that simple little TablePaste palette because I was tired of importing data from website table manually. But as you can see the code is even included three times in that one (so ugly!) I didn't know anything about making palettes properly, I'm just learning now – Szabolcs Jan 22 '12 at 8:47
up vote 23 down vote accepted
+100

All palette state (i.e., variables which affect the palette and should be remembered between sessions) should be vectored through the palette's TaggingRules option, and its initialization should be done in the palette's NotebookDynamicExpression option. That, plus context isolation of any kernel functions you need to define should solve all of the points you raise, excepting the documentation issue.

An example palette which demonstrates these principles:

CreatePalette[
 Column[{Button["Print opener state", 
    MyPalette`Private`DoSomething[
     "The opener is " <> 
      If[CurrentValue[EvaluationNotebook[], {TaggingRules, "opener"}],
        "open", "closed"]]],
   OpenerView[{"Group of buttons", Column[{Button[1], Button[2]}]}, 
    Dynamic[CurrentValue[
      EvaluationNotebook[], {TaggingRules, "opener"}, False]]]}],
 NotebookDynamicExpression :> 
  Refresh[MyPalette`Private`DoSomething[MyPalette`Private`x_] := 
    Print[MyPalette`Private`x], None]]

Mathematica graphics

Let's hit the items raised in this code one by one...

  • The palette uses a kernel-defined function which is in NotebookDynamicExpression. The code is wrapped in Refresh[_,None] to ensure that it evaluates once only when the notebook is opened. The code is context isolated by hand. Note that Begin and End won't work here, although they would work inside of a package, or if you wrapped the code in ToExpression (e.g., Begin["foo`"];ToExpression["code"];End[]).
  • A palette-wide state variable is stored in the palette's TaggingRules, which can be accessed by using CurrentValue[EvaluationNotebook[],{TaggingRules,"opener"}]. Because "opener" is a string, no symbols are introduced into any context.
  • State variables will typically need to be initialized. I could do that in various standard ways, but I used the undocumented third argument to CurrentValue which sets it to False if it doesn't already have a value.
  • Once the palette is installed, the TaggingRules setting will persist between instances of the palette, even if you quit Mathematica. Mathematica automatically serializes an installed palette's TaggingRules settings when you close it by storing the value into the global option PalettesMenuSettings.
  • If you have multiple versions of the palette open, they'll each operate using independent state variables because the state variable is attached to the palette notebook. If multiple versions of the palette are installed under different names then the PalettesMenuSettings trick will store the TaggingRules separately.
share|improve this answer
2  
Again, the highlight is TaggingRules. I have one concern about using NotebookDynamicExpression with Refresh[..., None] instead of the Initialization option of a DynamicModule wrapping everything: the definitions won't survive a kernel restart if we use Refresh[..., None]. Is there any reason not to use a DynamicModule[..., Initialization :> ...] here? Regarding Begin and End: I think if we evaluate Begin["..."] before evaluating the CreatePalette[...], then evaluate End[] after, everything new symbol in the palette will be correctly localized into the proper... – Szabolcs Feb 1 '12 at 16:33
    
... context. Is this correct? (We just need to take care that Begin, CreatePalette and End are sent to the kernel as separate expressions to ensure that Begin will affect the parsing of CreatePalette). – Szabolcs Feb 1 '12 at 16:34
    
@Szabolcs The Initialization option of DynamicModule is fine if all of the code you're initializing for is inside that DynamicModule. What you cannot do is to make assumptions about the order in which subsequent DynamicModule or Dynamic expressions will fire, or even if they will fire (if they're not in the same position). If NotebookDynamicExpression doesn't fire in new kernel sessions, it certainly should. I admit to not having tried this recently. – John Fultz Feb 6 '12 at 15:12
2  
I hope the bug of NotebookDynamicExpression not firing in new kernel sessions gets fixed soon – Rojo Jan 27 '14 at 5:24
1  
@Rojolalalalalalalalalalalalala 2 years and counting so I suppose this answer is obsolete since one have to manually close and open a palette to triger definitions load. Not to mention that Dynamic[DateString[], UpdateInterval -> 1] put on top of the column here will freeze forever after kernel quit. – Kuba Mar 15 at 9:14

You could generate the palette from code in a separate notebook, and have the generated palette use a unique context by setting CellContext -> Notebook when creating the palette notebook.

I think this should help with items 2, 3, and 5.


Example (there may be better ways..)

CreateDocument[
   {Cell[BoxData[MakeBoxes[x = 2]], "Input"]}, 
   CellContext -> Notebook]

If you then look at Context[x] in the created notebook, you get something like Notebook$$21$666892`

share|improve this answer
2  
I think you should mention that this can be done with setting CellContext->Notebook for the PaletteNotebook. – Albert Retey Jan 21 '12 at 21:21
    
@AlbertRetey Agreed, it's good to be detailed about that. – Brett Champion Jan 21 '12 at 21:29
    
Could you give a code example? If I simply add CellContex -> Notebook to PaletteNotebook in my example above, function will still be created in the SomePalette` context (or Global` if I remove Begin/End). This is because contexts are decided at parse time. – Szabolcs Jan 27 '12 at 11:25

This is a supplementary answer to what John Fultz has provided.

Problem 1:

The problem is that sometimes I can't include all functionality inside the palette nor I can call Needs to load it after kernel restart because the package initialization is a little bit complicated.

What I'm fine with though is to be able to close the Palette/Dialog/GUInotebook as soon as the new session starts.

Solution:

Warning - it is based on not stable behavior that NotebookDynamicExpression is loaded only when the notebook is opened, and not when the kernel restarts. As said in comments in accepted answer, this should not be the case. But it is for more than 3 years.

So I will abuse here the fact that NotebookDynamicExpression only fires when the notebook is opened while Initialization every time the session was terminated. So we will check if Initialization is done in the same session and we can close the notebook otherwise.

This notebook will close itself after you close the kernel (which is restarted by dynamics):

CreateDocument[
 DynamicModule[{},
  Dynamic[{"Date :", DateString[]}, UpdateInterval -> 1],
  Initialization :> (
    If[CurrentValue[
       EvaluationNotebook[], {TaggingRules, "Opened"}] =!= $SessionID,
          NotebookClose[]]
        )
      ],
     TaggingRules -> {ParentList, "Opened" -> $SessionID},
     NotebookDynamicExpression :> Refresh[
       CurrentValue[
         EvaluationNotebook[], {TaggingRules, "Opened"}] = $SessionID
   , None]
 ]

Problem 2

I don't like to write full names like: NotebookDynamicExpression :> Refresh[MyPalette`Private`DoSomething[MyPalette`Private`x_] := Print[MyPalette`Private`x], None]]

Method:

You can inject the package to the palette the same way I've done this for a CDF: How can I include functions from a package into a CDF file?

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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