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]]

Mathematica graphics

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 *)
addCounter = 5; (* this counts the number of text-updates *)
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 *)
Row@{Button["Add text!", Block[{c, add},
    c = ToString[addCounter++];
    add = StringJoin@Table["\n" <> StringJoin@Table[c, {RandomInteger@{1, maxLength}}], {RandomInteger@{1, 2}}];
    text = text <> add]],
  Dynamic@Slider[Dynamic@pos, {0, (lineCounter)*lineHeight}]}

{Framed@Pane[Dynamic@text, ImageSize -> {100, 60},
   ScrollPosition -> Dynamic[
      (* Put code inside Refresh that is to be evaluated whenever pos changes directly. *)
       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}

Mathematica graphics

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?


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.

enter image description here

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@
    ImageSize -> {100, Automatic}, Scrollbars -> False]]
(* {100, 119} *)

pos = {0, 10^100};
  ImageSize -> {100, 70}, Scrollbars -> False, 
  ScrollPosition -> 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.

enter image description here

enter image description here

enter image description here

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.

  • $\begingroup$ Would you care adding your useful answer to this thread instead (as this is a duplicate question)? $\endgroup$ – István Zachar Mar 18 '13 at 13:56
  • $\begingroup$ @IstvánZachar yeah sure. but perhaps the moderators could clarify whether duplicates are automatically merged (?) $\endgroup$ – Mike Honeychurch Mar 18 '13 at 21:14

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