I was busy exploring this problem on my own for some time now, and was not satisfied with any of the answers. They both work for some extent, but, concerning Szabolcs's answer, I would like to avoid image-processing and in case of Mr.Wizard's answer there are slight problems with it during startup (see comments) and it breaks down when the scroll position is defined as a dynamic variable itself. Thus I set out to find a better solution.
The problem
First, I want to clearly state the problem (at least what my problem was originally): given a Pane
that listens to a dynamic variable text
, I have to update it such that whenever text
changes by a controller (e.g. a button), the Pane
is automatically scrolled down to the actual end of content. But I also want to keep both the manipulable scrollbar of the Pane
and the possibility to manipulate the vertical scroll position pos
via another external controler (e.g. a slider). Thus the resulting Pane
must be updated differently, depending on whether text
or pos
triggers an update.
I present two solutions, the second one being the better one in my opinion. For both cases, I exploit the fact that if the initial vertical scroll position value (pos
) is set large enough, Mathematica automatically finds and sets the scroll position to the end of content.
1. Injecting code in the external controller
The simpler method is to separate the different updates by assigning them to the external controllers. Here, a button is provided that updates text
externally (external to the dynamic Pane
object), but it also directly redraws the Pane
to find the end-of-content position.
pos = 1000; (* vertical scroll position *)
c = 5; (* text update counter *)
text = "1\n2\n3333333333333333\n44";
(* note that this button explicitly contains the update[] code *)
Button["Update text", cc = c++; text = text <> "\n" <>
StringJoin@Table[ToString@cc, {RandomInteger@{1, 15}}] <> "<"; pane = update[]]
update[] := (
pos = pos + 1000;
Framed@Pane[Dynamic@text, ImageSize -> {120, 60},
Scrollbars -> {False, True},
ScrollPosition -> Dynamic[{0, pos}, (pos = Last@#) &]]
);
pane = update[]; (* initialize Pane *)
{Dynamic@pane, Dynamic@pos}
Note that updating text
via the button or via the scrollbar correctly updates pane
and pos
. The only problem here is that one has to manually put the pane = update[]
code into the controller, which is unwanted (at least in my case), as one only wants to change text
via the button.
2. Separating updates via selective triggers
The beautiful answer by @Leonid provides a method to selectively separate different update methods of a dynamic expression depending on which "parent" variable triggers an update.
SetAttributes[makeTrigger, HoldAll];
makeTrigger[res_, var_, updateCode_] := Module[{varOld = var, trigger},
trigger /; varOld =!= var := (varOld = var; res = updateCode);
trigger];
pos = 1000;
c = 5;
text = "1\n2\n3333333333333333\n44";
size = {120, 60};
Button["Update text", cc = c++; text = text <> "\n" <>
StringJoin@Table[ToString@cc, {RandomInteger@{1, 15}}] <> "<";]
Row@{"Update pos: ", Slider[Dynamic@pos, {0, 200}]}
update[] := Framed@Pane[text, ImageSize -> Dynamic@size,
Scrollbars -> {False, True},
ScrollPosition -> Dynamic[{0, pos}, (pos = Last@#) &]];
(* triggers ONLY if text is changed! *)
trigger = makeTrigger[pane, text, (pos = pos + 1000; update[])];
pane = update[]; (* initialize pane *)
{With[{tr = trigger}, Dynamic[tr; pane, TrackedSymbols :> {text, pos}]], Dynamic@pos}

(The "<"
character indicates the end of newly added content.)
First, a trigger
is set up in the final line, that listens to whether text
or pos
is changed. If only pos
is changed, then the resulting expression becomes Dynamic[pane]
, updating the vertical scroll position as required. However, if text
is changed, then trigger
triggers and update[]
kicks in, redrawing the whole Pane
object from scratch, finding and setting the correct end-of-content position.
Now it became possibly to have various controllers only updating independent variables (like text
, pos
, etc.) and a dependent expression that listens to the independent variables and updates according to which of them was triggered.
Print
function? $\endgroup$