How to center a dialog with respect to a parent notebook

If you have a mess on e.g. three screens, you work on the very left one and get a MessageDialog centered with respect to the main screen, it can get unnoticed.

How to center a dialog with respect to the parent notebook? E.g.:

DialogInput[ DefaultButton[ DialogReturn[True] ] ]


Here's my fix but it certainly isn't neat:

With[{apc = AbsoluteCurrentValue},
With[{parentCenter = Transpose[
{apc[WindowMargins][[;; , 1]] + .5 apc[WindowSize], {Automatic, Automatic}
}]
},
DefaultButton[DialogReturn[True]],
NotebookDynamicExpression :> Refresh[
SetOptions[EvaluationNotebook[],
WindowMargins -> (parentCenter - Transpose[{.5 apc[WindowSize], {0, 0}}]),
NotebookDynamicExpression -> None
]
, None
]
]
]
]

• It might be simpler to add a beep sound to your dialog , so that it won't go unnoticed – magma May 19 '17 at 8:15
• @magma maybe for the simplest case. for a sequence of dialogs monitoring/managing flow in more complex procedures it will be annoying. It is annoying even for simplest cases for some people, I disabled all the beeps. – Kuba May 19 '17 at 8:19

So I'm not sure if there's a trivial way to do this, but it's all easy enough to compute (caveat emptor: it could be slow). Basically we'll just determine what the left and bottom WindowMargins will have to be to put a point anc at a point pt in our new dialog.

First we need a basic function to compute scaled window positions (even though it's long it's really a boring, simple function). It uses the trueWindowSize from this answer.

scaledNotebookPositions // Clear
scaledNotebookPositions[
nb_NotebookObject,
point : {_, _} | Automatic : Automatic
] :=
Module[{
ws = trueWindowSize[nb],
pt = Replace[point, Automatic :> {Center, Center}]
},
Floor /@ {
Replace[
Replace[pt[[1]],
{Center :> Scaled[.5], Left :> Scaled[0], Right :> Scaled[1]}
],
{
Scaled[i_?NumericQ] :> ws[[1]]*i,
i_?NumericQ :>
ws[[1]]/2 + i,
_ :> ws[[1]]/2
}
],
Replace[
Replace[pt[[2]],
{Center :> Scaled[.5], Bottom :> Scaled[0], Top :> Scaled[1]}
],
{
Scaled[i_?NumericQ] :> ws[[2]]*i,
i_?NumericQ :>
ws[[2]]/2 + i,
_ :> ws[[2]]/2
}
]
}
]


Then a function to align a point in the dialog with a point in the original notebook:

positionedDialog // Clear
positionedDialog[
parent_NotebookObject,
dialog_Notebook,
point : {_, _} | Automatic : Automatic,
anchor : {_, _} | Automatic : Automatic,
return : True | False : False
] :=
Module[{
marg = {#[[1]], Automatic} & /@
AbsoluteCurrentValue[parent, WindowMargins],
dia = CreateDocument[dialog, Visible -> False,
SystemDynamicUpdating -> False],
pt, anc
},
pt = scaledNotebookPositions[parent, point];
anc = scaledNotebookPositions[dia, anchor];
marg =
ReplacePart[#, 1 -> #[[1]] + #2 - #3] &,
{marg, pt, anc}
];
If[! return,
SetOptions[dia, {
WindowMargins -> marg,
WindowFloating -> True,
Visible -> Lookup[Options[dialog], Visible, True],
SystemDynamicUpdating ->
Lookup[Options[dialog], SystemDynamicUpdating, True]
}];
SetOptions[dia,
WindowFloating -> False
];,
NotebookClose[dia];
Insert[dialog,
WindowMargins -> marg,
2
]
]
]


And here's a basic test case:

$dialog = CloudImport@ CloudObject[ "https://www.wolframcloud.com/objects/b3m2a1/banner-dialog-example.nb"]; (* If that fails use this *)$dialog =
Notebook[{},
WindowSize -> {300, 350},
StyleDefinitions -> "Palette.nb",
SystemClosingSaveDialog -> False
];


And here's our test case:

positionedDialog[EvaluationNotebook[],
dialog ]  And here's another, with a different alignment: positionedDialog[EvaluationNotebook[],dialog,
{Right, Top},
{Scaled[.65], Top}
]


Here's an example where you simply pull out the WindowSize and WindowMargins options from positionedDialog and pass them to a different function:

ChoiceDialog["Pick",
{},
Options[
positionedDialog[EvaluationNotebook[],
Notebook[{},
WindowSize -> {350, 100}
],
True
],
{
WindowMargins,
WindowSize
}
]
]


Note that I supplied a WindowSize for the ChoiceDialog implicitly in that Notebook: i.e.

   Notebook[{},
WindowSize -> {350, 100}
]


For dialogs that supply some minimum size (think ChoiceDialog, MessageDialog) this type of thing is necessary.

You may use the WindowMargins and WindowSize options of the EvaluationNotebook to set the position of the DialogInput by setting its WindowMargins option.

DialogInput[DefaultButton[DialogReturn[True]],
WindowMargins ->
With[{win = {WindowMargins, WindowSize} /.
AbsoluteOptions[EvaluationNotebook[], {WindowMargins, WindowSize}]},
{Automatic, #} & /@ (win[[1, All, 2]] + win[[2]]/2)]
]


The above calculates the centre position of the EvaluationNotebook in screen coordinates and sets the WindowMargins of DialogInput such that its right-top corner is at that location.

If you know the WindowSize of the DialogInput (or set it by its WindowSize option) then you could adjust the calculation such that its centre is located at the centre of the EvaluationNotebook.

Hope this helps.

• That is what I'm doing, and the code is ugly because i can't assume anything about dialog size :) – Kuba May 18 '17 at 19:58
• @Kuba I am not assuming anything about dialog size either. I just place the right-top corner of the dialogue in the centre of the evaluation notebook. The absence of dynamics should be a good thing as well, correct? – Edmund May 18 '17 at 19:59
• You are not assuming but your dialog is not centered. Its top-right corner is, which will look bad in case of larger examples. I needed to use dynamics to overcome this issue, and yes without dynamics it would be great. Because notice that your With does what my With does but in a slightly different way. – Kuba May 19 '17 at 5:43
• @Kuba is your dialog huge? Could you put in a quick Rasterize[_,"RasterSize"] to precompute the dialog size? For Edmund's dialog, opening, rasterizing, and closing takes about .12 seconds, which is noticeable, but not awful (or generally disqualifying, I think). You lose the frame elements sizes, but those may be computed once at load time and you can just reuse them. – b3m2a1 Jun 17 '17 at 21:19

Here is a compact version of the example from original post:

CenterToParent::usage =  "CenterToParent[DialogInput[...]] etc, will make the dialog centered with respect to the parent notebook";

CenterToParent // Attributes = {HoldFirst};

CenterToParent[ dialog_[whatever__, opts : OptionsPattern[]]  ] := With[
{apc = AbsoluteCurrentValue}
, With[
{ parentCenter = Transpose @ {
apc[WindowMargins][[;; , 1]] + .5 apc[ WindowSize]
, {Automatic, Automatic}
}
}
, dialog[
whatever
, NotebookDynamicExpression :> Refresh[
SetOptions[
EvaluationNotebook[]
, WindowMargins -> (parentCenter - Transpose[{.5 apc[WindowSize], {0, 0}}])
, NotebookDynamicExpression -> None
]
, None
]
, opts
]
]
];


It is very raw but I prefer gigo approach than putting to many constraints.

It works well so far, big plus is that it works with kernel blocking dialogs:

CenterToParent @ ChoiceDialog["Pick"]


The only downside, rare though, is it interferes with dialog's NotebookDynamicExpression but the code is compact enough that can be taken are explicitly customized when one needs to deal with additional NDE.