Running AutoScroll with ScrollViewer.ScrollToEnd () only works when debugging, the event handler is too simple

Looking at this solution for better autoscanning, I considered myself so smart as to find an easier solution, but it only works in a debugging session:

private void scrollviewer_Messages_ScrollChanged(object sender, ScrollChangedEventArgs e) { ScrollViewer sv = sender as ScrollViewer; if (sv.VerticalOffset == sv.ScrollableHeight) { sv.ScrollToEnd();//debug breakpoint } return; } 

When adding content to a text block, this ScrollViewer has autoscrolling, the bottom of the text remains in sight. When the user scrolls up and more content is added, the lower part leaves the field of view, autoscrolling is turned off, and this is normal. When the user scrolls back, ScrollToEnd () should turn on auto-scanning again, but when more content is added, the bottom still scrolls out of sight.

When I set a breakpoint, I can check if ScrollToEnd () is actually called. Then, by removing the breakpoint and adding more content, autoscrolling works again.

I add content by clicking a button, with code in ViewModel and Binding. Therefore, I am sure that there is no problem with concurrency. There is a lot of time between adding content and manually scrolling.

It really puzzled me, while I was so pleased with my simple auto-scroll solution. How can this not work?




edit:

I found out that autoscroll works again after scrolling back, but for some reason it’s not so easy to hit from below. I need to move the slider down, And press the down arrow on the scroll bar. Now I will experiment with replacing the == sign in my code to resolve a few pixel difference.




edit:

Could this be a problem because the content is a TextBlock with multi-line text string and TextWrap?

  <ScrollViewer Name="scrollviewer_Messages" DockPanel.Dock="Top" Height="100" Width="200" ScrollChanged="scrollviewer_Messages_ScrollChanged"> <TextBlock Name="tb_Message" Margin="10" TextWrapping="Wrap" Text="{Binding Path=Messages}"> </TextBlock> </ScrollViewer> 



edit:

The problem disappeared with a formula change in the event handler:

  sv.ScrollableHeight - sv.VerticalOffset < 20 

I already experimented with < 10 , but pushpraj (see answer below) made me try big numbers. It is not yet clear why this works, since the problem is not that ScrollToEnd() not called.




About the decision:

<20 not required, because we are talking about fractional numbers. In general, two real numbers are never equal, but this is not true here. The double numbers for offset and height are really equal when the slider is at the end.

The problem is that ScrollToEnd/Bottom() does not seem to work when scrolling with the slider. It. I would call it a mistake, but it can also be a “feature”: you cannot change the behavior of the slider while the user is sliding and waiting for control.

The fix is ​​that first we move the slider to the end, doing an offset == Height. The second step is that adding content will increase the height, due to the above error the slider will move a little, in my case about 15 points. This raises the ScrollChanged event, and the threshold value <20 is large enough to receive a second ScrollToBottom call. This step two occurs every time the content is added.

My previous edit regarding pressing the down button works the same way. ScrollToEnd seems to work for the down button.

The trick, of course, is that a mistake is a mistake. If more content is added at the same time, the threshold may not work, and auto-scanning may stop.

The final solution, not as simple as I expected, but still not too complicated, should be in my answer below.

+4
debugging c # wpf scrollview
Sep 10 '14 at 9:25
source share
2 answers

The cause of the problem is that ScrollToEnd() has nothing to do with autoscroll. This call simply scrolls to the end, and that’s it. Having placed the call in the event handler, it will scroll quite often to the end, but for true autoscrolling it is necessary to determine who triggered the event: the user, moving the slider or slider, moving due to the resizing of the content. Instead of ignoring "useless" events, by looking at ExtentHeight , this property is now used to determine who or what triggered the event.

This solution saves the state of the autoscroll bit in the control tag. It would be even better to subclass the new usercontrol AutoScrollViewer.

In the end, this solution is not much “simpler” than the previous solutions, as mentioned above in the question, this is just a variation, but it (hopefully) is more accurate.

  /// <summary> /// If the scrollviewer is at the bottom, keep the bottom in view. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void scrollviewer_Messages_ScrollChanged(object sender, ScrollChangedEventArgs e) { ScrollViewer sv = sender as ScrollViewer; bool AutoScrollToEnd = true; if (sv.Tag != null) { AutoScrollToEnd = (bool)sv.Tag; } if (e.ExtentHeightChange == 0)// user scroll { AutoScrollToEnd = sv.ScrollableHeight == sv.VerticalOffset; } else// content change { if (AutoScrollToEnd) { sv.ScrollToEnd(); } } sv.Tag = AutoScrollToEnd; return; } 
+3
Sep 10 '14 at 12:19
source share

here is an improved version of the same

  private void scrollviewer_Messages_ScrollChanged(object sender, ScrollChangedEventArgs e) { ScrollViewer sv = sender as ScrollViewer; //if (e.ExtentHeightChange != 0 && Math.Abs(sv.VerticalOffset - sv.ScrollableHeight) < 20) if(sv.ScrollableHeight - sv.VerticalOffset < 20) { sv.ScrollToEnd(); } } 

I added a condition to see if the content height has changed, otherwise it will be called for each scroll event, and secondly, in this case I added some tolerance (20), because it is not always possible to exactly fulfill the condition sv.VerticalOffset == sv.ScrollableHeight . 20 is just a figure that gives good results.

in your example, it stops working after dragging the scroll bar, but if you press the scroll button down to reach the last, you can make it work the way you expected, which you usually see after debugging.

+1
Sep 10 '14 at 10:08
source share



All Articles