# How to force Pane to always scroll to end of content? [duplicate]

Mathematica seems to know about the actual end of content position in a Pane if given large enough (but not too large$^1$) initial position value. Here, the vertical position is set to a very large number, but Mathematica sets pos to the appropriate actual value.

pos = {0, 10^100};
Framed@Pane["1\n2\n333333333333333333333\n4", ImageSize -> {100, 70},
ScrollPosition -> Dynamic[pos]]
Dynamic@pos


How does Mathematica know that the correct vertical position is at 26. (on my machine), and, more importantly, how can I access this value directly? That is, given random content possibly including explicit or implicit linebreaks how can one find the appropriate scrollbar position value? Note that since there are implicit linebreaks (forced by Pane), one cannot rely on counting "\n" characters in the text. Needless to say, the solution should be robust enough to deal with different fonts, font sizes, styles and magnification automagically.

$\scriptsize{(^1 \text{Try it with pos = {0, 10^1000} and Mathematica fails to find the end.)}}$

The use case (warning, this might be a bit complicated)

I need a Pane that can be updated via a button: whenever the button is pressed, the text inside Pane is modified and I need to update the display frame to always scroll down (i.e. set Scrollbar position) to the end of content. Thus I need a 'listener' inside Pane that calculates the new end-of-content position whenever the content is changed from the outside.

The following minimal, though still convoluted example is what I came up with. This tracks the end-of-content correctly by simply counting "\n" characters in the dynamic text variable. Update text using the button to see how the Pane is always scrolled down to the end. Even if the slider (acting for a vertical scrollbar) is moved, any new update immediately scrolls down the content. A Refresh inside Pane (with specific TrackedSymbols option) acts as a listener to the external changes of text. I use a line height of 20 but lineHeight might be set manually for each platform.

pos = 10^3; (* set initial position value large enough *)
text = "1\n2\n3\n4"; (* text variable that is updated directly *)
evalCounter = 0; (* count evaluations of the Refresh part of the code *)
lineHeight = 20; (* Magic number to specify line height *)
maxLength = 5; (* maximal line length *)

(* Update text with button, manually set position with slider *)
add = StringJoin@Table["\n" <> StringJoin@Table[c, {RandomInteger@{1, maxLength}}], {RandomInteger@{1, 2}}];
Dynamic@Slider[Dynamic@pos, {0, (lineCounter)*lineHeight}]}

{Framed@Pane[Dynamic@text, ImageSize -> {100, 60},
ScrollPosition -> Dynamic[
{0,
(* Put code inside Refresh that is to be evaluated whenever pos changes directly. *)
Refresh[
If[evalCounter < 5, evalCounter++; Print[{evalCounter, pos}]];
pos, TrackedSymbols :> {text, pos}]
},
(* Put code inside second argument of Dynamic that is to be evaluated whenever pos changes indirectly. *)
(lineCounter = StringCount[text, "\n"];
pos = If[evalCounter < 5,
Last@# (* use this to find end the Mathematica-way *),
lineHeight*(lineCounter - 2)] (* use this to find end counting linebreaks *)
) &
]],
Dynamic@pos, Dynamic@lineCounter}


There are two major drawbacks:

1. This code cannot deal with implicit linebreaks: if maxLength is increased, there will be long lines that are wrapped by Pane, pushing further down the end of content. Such linewraps cannot be counted AFAIK.
2. The lucky way how one can find the end-of-content position for the first time cannot be used at successive updates. This is exactly the problem for which I've posted this question. I've included a Print statement inside Refresh to check on successive evaluations during initialization. Interestingly, this code is evaluated 5 times during start, and results are either printed to the actual or to the Messages notebook. One can check that it is actually the 5th evaluation where the correct position value is assigned.
{1, 1000}    (* ==> to Messages notebook *)

{2, 1000}    (* ==> to actual notebook *)

{3, 0.}      (* ==> to Messages notebook *)

{4, 0.}      (* ==> to actual notebook *)

{5, 16.}     (* ==> to Messages notebook *)


Question: How can one figure out the actual vertical end-of-content position inside Pane whenever content is changed from the outside?

-

## marked as duplicate by István Zachar, Yves Klett, rm -rf♦Mar 18 '13 at 16:54

–  Mr.Wizard Mar 14 '13 at 0:00

I think I have a long winded solution ...but not sure how practical. Firstly I tried Trace, which you probably did as well, but that doesn't shed any light. But it does appear as though the dimensions of the pane, if all content were visible, i.e. ImageSize->{x, All}, must be calculated somewhere along the way. Unfortunately when trying to do some tests I found that CurrentValue and Options where ineffectual with Pane -- this is disappointing because it would be straight forward to develop a method if either of those worked.

and it didn't matter if I used All or Full instead of Automatic for the image size. My reason for going down this path was that if you can establish the dimensions of the pane when all content is displayed for a given width, then you can work out the end position of the scroller. An alternative, but like I said, probably not practical, is to get the dimensions of a rastered image of the full pane:

dim = ImageDimensions@
Rasterize[
Pane["1\n2\n333333333333333333333\n4\n5\n6\n7",
ImageSize -> {100, Automatic}, Scrollbars -> False]]
(* {100, 119} *)

pos = {0, 10^100};
Framed@Pane["1\n2\n333333333333333333333\n4\n5\n6\n7",
ImageSize -> {100, 70}, Scrollbars -> False,
ScrollPosition -> Dynamic[pos]]
Dynamic@pos
(* {0, 47} *)


So with an image height set to 70 the vertical scroll position is "full height" - 70 (-2). The extra 2 pixels I am not sure about but seem minor in the overall context.

I have tried this with a small number of examples and it seems to work, e.g.

In other words the scroll position is dim - image size, i.e. dim - {100, 70}, or even dim - image size - {0, 2} if you want to be precise.

I am doing this with 8.0.4 on mac 10.6.8. This approach may not be practical for you but does seem to work. Obviously if current value or similar worked (try V9?) this could be more straightward.

-